2 * Copyright (C) 2014 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
17 package com.android.systemui.statusbar.phone;
19 import android.app.ActivityManagerNative;
20 import android.app.admin.DevicePolicyManager;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ResolveInfo;
27 import android.content.res.Configuration;
28 import android.graphics.drawable.Drawable;
29 import android.graphics.drawable.InsetDrawable;
30 import android.os.AsyncTask;
31 import android.os.Bundle;
32 import android.os.RemoteException;
33 import android.os.UserHandle;
34 import android.provider.MediaStore;
35 import android.telecom.TelecomManager;
36 import android.util.AttributeSet;
37 import android.util.Log;
38 import android.util.TypedValue;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.view.accessibility.AccessibilityNodeInfo;
42 import android.view.animation.AnimationUtils;
43 import android.view.animation.Interpolator;
44 import android.widget.FrameLayout;
45 import android.widget.TextView;
47 import com.android.internal.widget.LockPatternUtils;
48 import com.android.keyguard.KeyguardUpdateMonitor;
49 import com.android.keyguard.KeyguardUpdateMonitorCallback;
50 import com.android.systemui.EventLogConstants;
51 import com.android.systemui.EventLogTags;
52 import com.android.systemui.R;
53 import com.android.systemui.statusbar.CommandQueue;
54 import com.android.systemui.statusbar.KeyguardAffordanceView;
55 import com.android.systemui.statusbar.KeyguardIndicationController;
56 import com.android.systemui.statusbar.policy.AccessibilityController;
57 import com.android.systemui.statusbar.policy.FlashlightController;
58 import com.android.systemui.statusbar.policy.PreviewInflater;
60 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
61 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
64 * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
67 public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener,
68 UnlockMethodCache.OnUnlockMethodChangedListener,
69 AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener {
71 final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView";
73 private static final Intent SECURE_CAMERA_INTENT =
74 new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
75 .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
76 private static final Intent INSECURE_CAMERA_INTENT =
77 new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
78 private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);
79 private static final int DOZE_ANIMATION_STAGGER_DELAY = 48;
80 private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250;
82 private KeyguardAffordanceView mCameraImageView;
83 private KeyguardAffordanceView mPhoneImageView;
84 private KeyguardAffordanceView mLockIcon;
85 private TextView mIndicationText;
86 private ViewGroup mPreviewContainer;
88 private View mPhonePreview;
89 private View mCameraPreview;
91 private ActivityStarter mActivityStarter;
92 private UnlockMethodCache mUnlockMethodCache;
93 private LockPatternUtils mLockPatternUtils;
94 private FlashlightController mFlashlightController;
95 private PreviewInflater mPreviewInflater;
96 private KeyguardIndicationController mIndicationController;
97 private AccessibilityController mAccessibilityController;
98 private PhoneStatusBar mPhoneStatusBar;
100 private final TrustDrawable mTrustDrawable;
101 private final Interpolator mLinearOutSlowInInterpolator;
102 private int mLastUnlockIconRes = 0;
104 public KeyguardBottomAreaView(Context context) {
108 public KeyguardBottomAreaView(Context context, AttributeSet attrs) {
109 this(context, attrs, 0);
112 public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr) {
113 this(context, attrs, defStyleAttr, 0);
116 public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr,
118 super(context, attrs, defStyleAttr, defStyleRes);
119 mTrustDrawable = new TrustDrawable(mContext);
120 mLinearOutSlowInInterpolator =
121 AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
124 private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
126 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
127 super.onInitializeAccessibilityNodeInfo(host, info);
129 if (host == mLockIcon) {
130 label = getResources().getString(R.string.unlock_label);
131 } else if (host == mCameraImageView) {
132 label = getResources().getString(R.string.camera_label);
133 } else if (host == mPhoneImageView) {
134 label = getResources().getString(R.string.phone_label);
136 info.addAction(new AccessibilityAction(ACTION_CLICK, label));
140 public boolean performAccessibilityAction(View host, int action, Bundle args) {
141 if (action == ACTION_CLICK) {
142 if (host == mLockIcon) {
143 mPhoneStatusBar.animateCollapsePanels(
144 CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
146 } else if (host == mCameraImageView) {
149 } else if (host == mPhoneImageView) {
154 return super.performAccessibilityAction(host, action, args);
159 protected void onFinishInflate() {
160 super.onFinishInflate();
161 mLockPatternUtils = new LockPatternUtils(mContext);
162 mPreviewContainer = (ViewGroup) findViewById(R.id.preview_container);
163 mCameraImageView = (KeyguardAffordanceView) findViewById(R.id.camera_button);
164 mPhoneImageView = (KeyguardAffordanceView) findViewById(R.id.phone_button);
165 mLockIcon = (KeyguardAffordanceView) findViewById(R.id.lock_icon);
166 mIndicationText = (TextView) findViewById(R.id.keyguard_indication_text);
167 watchForCameraPolicyChanges();
168 updateCameraVisibility();
169 updatePhoneVisibility();
170 mUnlockMethodCache = UnlockMethodCache.getInstance(getContext());
171 mUnlockMethodCache.addListener(this);
173 setClipChildren(false);
174 setClipToPadding(false);
175 mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext));
177 mLockIcon.setOnClickListener(this);
178 mLockIcon.setBackground(mTrustDrawable);
179 mLockIcon.setOnLongClickListener(this);
180 mCameraImageView.setOnClickListener(this);
181 mPhoneImageView.setOnClickListener(this);
185 private void initAccessibility() {
186 mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
187 mPhoneImageView.setAccessibilityDelegate(mAccessibilityDelegate);
188 mCameraImageView.setAccessibilityDelegate(mAccessibilityDelegate);
192 protected void onConfigurationChanged(Configuration newConfig) {
193 super.onConfigurationChanged(newConfig);
194 int indicationBottomMargin = getResources().getDimensionPixelSize(
195 R.dimen.keyguard_indication_margin_bottom);
196 MarginLayoutParams mlp = (MarginLayoutParams) mIndicationText.getLayoutParams();
197 if (mlp.bottomMargin != indicationBottomMargin) {
198 mlp.bottomMargin = indicationBottomMargin;
199 mIndicationText.setLayoutParams(mlp);
202 // Respect font size setting.
203 mIndicationText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
204 getResources().getDimensionPixelSize(
205 com.android.internal.R.dimen.text_size_small_material));
208 public void setActivityStarter(ActivityStarter activityStarter) {
209 mActivityStarter = activityStarter;
212 public void setFlashlightController(FlashlightController flashlightController) {
213 mFlashlightController = flashlightController;
216 public void setAccessibilityController(AccessibilityController accessibilityController) {
217 mAccessibilityController = accessibilityController;
218 accessibilityController.addStateChangedCallback(this);
221 public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
222 mPhoneStatusBar = phoneStatusBar;
223 updateCameraVisibility(); // in case onFinishInflate() was called too early
226 private Intent getCameraIntent() {
227 KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
228 boolean currentUserHasTrust = updateMonitor.getUserHasTrust(
229 mLockPatternUtils.getCurrentUser());
230 return mLockPatternUtils.isSecure() && !currentUserHasTrust
231 ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
234 private void updateCameraVisibility() {
235 if (mCameraImageView == null) {
236 // Things are not set up yet; reply hazy, ask again later
239 ResolveInfo resolved = mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(),
240 PackageManager.MATCH_DEFAULT_ONLY,
241 mLockPatternUtils.getCurrentUser());
242 boolean visible = !isCameraDisabledByDpm() && resolved != null
243 && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance);
244 mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
247 private void updatePhoneVisibility() {
248 boolean visible = isPhoneVisible();
249 mPhoneImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
252 private boolean isPhoneVisible() {
253 PackageManager pm = mContext.getPackageManager();
254 return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
255 && pm.resolveActivity(PHONE_INTENT, 0) != null;
258 private boolean isCameraDisabledByDpm() {
259 final DevicePolicyManager dpm =
260 (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
261 if (dpm != null && mPhoneStatusBar != null) {
263 final int userId = ActivityManagerNative.getDefault().getCurrentUser().id;
264 final int disabledFlags = dpm.getKeyguardDisabledFeatures(null, userId);
265 final boolean disabledBecauseKeyguardSecure =
266 (disabledFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0
267 && mPhoneStatusBar.isKeyguardSecure();
268 return dpm.getCameraDisabled(null) || disabledBecauseKeyguardSecure;
269 } catch (RemoteException e) {
270 Log.e(TAG, "Can't get userId", e);
276 private void watchForCameraPolicyChanges() {
277 final IntentFilter filter = new IntentFilter();
278 filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
279 getContext().registerReceiverAsUser(mDevicePolicyReceiver,
280 UserHandle.ALL, filter, null, null);
281 KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
285 public void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled) {
286 mCameraImageView.setClickable(touchExplorationEnabled);
287 mPhoneImageView.setClickable(touchExplorationEnabled);
288 mCameraImageView.setFocusable(accessibilityEnabled);
289 mPhoneImageView.setFocusable(accessibilityEnabled);
290 updateLockIconClickability();
293 private void updateLockIconClickability() {
294 if (mAccessibilityController == null) {
297 boolean clickToUnlock = mAccessibilityController.isTouchExplorationEnabled();
298 boolean clickToForceLock = mUnlockMethodCache.isTrustManaged()
299 && !mAccessibilityController.isAccessibilityEnabled();
300 boolean longClickToForceLock = mUnlockMethodCache.isTrustManaged()
301 && !clickToForceLock;
302 mLockIcon.setClickable(clickToForceLock || clickToUnlock);
303 mLockIcon.setLongClickable(longClickToForceLock);
304 mLockIcon.setFocusable(mAccessibilityController.isAccessibilityEnabled());
308 public void onClick(View v) {
309 if (v == mCameraImageView) {
311 } else if (v == mPhoneImageView) {
313 } if (v == mLockIcon) {
314 if (!mAccessibilityController.isAccessibilityEnabled()) {
315 handleTrustCircleClick();
317 mPhoneStatusBar.animateCollapsePanels(
318 CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
324 public boolean onLongClick(View v) {
325 handleTrustCircleClick();
329 private void handleTrustCircleClick() {
330 EventLogTags.writeSysuiLockscreenGesture(
331 EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_LOCK, 0 /* lengthDp - N/A */,
332 0 /* velocityDp - N/A */);
333 mIndicationController.showTransientIndication(
334 R.string.keyguard_indication_trust_disabled);
335 mLockPatternUtils.requireCredentialEntry(mLockPatternUtils.getCurrentUser());
338 public void launchCamera() {
339 mFlashlightController.killFlashlight();
340 Intent intent = getCameraIntent();
341 boolean wouldLaunchResolverActivity = PreviewInflater.wouldLaunchResolverActivity(
342 mContext, intent, mLockPatternUtils.getCurrentUser());
343 if (intent == SECURE_CAMERA_INTENT && !wouldLaunchResolverActivity) {
344 mContext.startActivityAsUser(intent, UserHandle.CURRENT);
347 // We need to delay starting the activity because ResolverActivity finishes itself if
348 // launched behind lockscreen.
349 mActivityStarter.startActivity(intent, false /* dismissShade */);
353 public void launchPhone() {
354 final TelecomManager tm = TelecomManager.from(mContext);
356 AsyncTask.execute(new Runnable() {
359 tm.showInCallScreen(false /* showDialpad */);
363 mActivityStarter.startActivity(PHONE_INTENT, false /* dismissShade */);
369 protected void onVisibilityChanged(View changedView, int visibility) {
370 super.onVisibilityChanged(changedView, visibility);
372 mTrustDrawable.start();
374 mTrustDrawable.stop();
376 if (changedView == this && visibility == VISIBLE) {
378 updateCameraVisibility();
383 protected void onDetachedFromWindow() {
384 super.onDetachedFromWindow();
385 mTrustDrawable.stop();
388 private void updateLockIcon() {
389 boolean visible = isShown() && KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
391 mTrustDrawable.start();
393 mTrustDrawable.stop();
398 // TODO: Real icon for facelock.
399 int iconRes = mUnlockMethodCache.isFaceUnlockRunning()
400 ? com.android.internal.R.drawable.ic_account_circle
401 : mUnlockMethodCache.isCurrentlyInsecure() ? R.drawable.ic_lock_open_24dp
402 : R.drawable.ic_lock_24dp;
403 if (mLastUnlockIconRes != iconRes) {
404 Drawable icon = mContext.getDrawable(iconRes);
405 int iconHeight = getResources().getDimensionPixelSize(
406 R.dimen.keyguard_affordance_icon_height);
407 int iconWidth = getResources().getDimensionPixelSize(
408 R.dimen.keyguard_affordance_icon_width);
409 if (icon.getIntrinsicHeight() != iconHeight || icon.getIntrinsicWidth() != iconWidth) {
410 icon = new IntrinsicSizeDrawable(icon, iconWidth, iconHeight);
412 mLockIcon.setImageDrawable(icon);
414 boolean trustManaged = mUnlockMethodCache.isTrustManaged();
415 mTrustDrawable.setTrustManaged(trustManaged);
416 updateLockIconClickability();
421 public KeyguardAffordanceView getPhoneView() {
422 return mPhoneImageView;
425 public KeyguardAffordanceView getCameraView() {
426 return mCameraImageView;
429 public View getPhonePreview() {
430 return mPhonePreview;
433 public View getCameraPreview() {
434 return mCameraPreview;
437 public KeyguardAffordanceView getLockIcon() {
441 public View getIndicationView() {
442 return mIndicationText;
446 public boolean hasOverlappingRendering() {
451 public void onUnlockMethodStateChanged() {
453 updateCameraVisibility();
456 private void inflatePreviews() {
457 mPhonePreview = mPreviewInflater.inflatePreview(PHONE_INTENT);
458 mCameraPreview = mPreviewInflater.inflatePreview(getCameraIntent());
459 if (mPhonePreview != null) {
460 mPreviewContainer.addView(mPhonePreview);
461 mPhonePreview.setVisibility(View.INVISIBLE);
463 if (mCameraPreview != null) {
464 mPreviewContainer.addView(mCameraPreview);
465 mCameraPreview.setVisibility(View.INVISIBLE);
469 public void startFinishDozeAnimation() {
471 if (mPhoneImageView.getVisibility() == View.VISIBLE) {
472 startFinishDozeAnimationElement(mPhoneImageView, delay);
473 delay += DOZE_ANIMATION_STAGGER_DELAY;
475 startFinishDozeAnimationElement(mLockIcon, delay);
476 delay += DOZE_ANIMATION_STAGGER_DELAY;
477 if (mCameraImageView.getVisibility() == View.VISIBLE) {
478 startFinishDozeAnimationElement(mCameraImageView, delay);
480 mIndicationText.setAlpha(0f);
481 mIndicationText.animate()
483 .setInterpolator(mLinearOutSlowInInterpolator)
484 .setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
487 private void startFinishDozeAnimationElement(View element, long delay) {
488 element.setAlpha(0f);
489 element.setTranslationY(element.getHeight() / 2);
493 .setInterpolator(mLinearOutSlowInInterpolator)
494 .setStartDelay(delay)
495 .setDuration(DOZE_ANIMATION_ELEMENT_DURATION);
498 private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() {
499 public void onReceive(Context context, Intent intent) {
500 post(new Runnable() {
503 updateCameraVisibility();
509 private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
510 new KeyguardUpdateMonitorCallback() {
512 public void onUserSwitchComplete(int userId) {
513 updateCameraVisibility();
517 public void onScreenTurnedOn() {
522 public void onScreenTurnedOff(int why) {
527 public void onKeyguardVisibilityChanged(boolean showing) {
532 public void setKeyguardIndicationController(
533 KeyguardIndicationController keyguardIndicationController) {
534 mIndicationController = keyguardIndicationController;
539 * A wrapper around another Drawable that overrides the intrinsic size.
541 private static class IntrinsicSizeDrawable extends InsetDrawable {
543 private final int mIntrinsicWidth;
544 private final int mIntrinsicHeight;
546 public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) {
548 mIntrinsicWidth = intrinsicWidth;
549 mIntrinsicHeight = intrinsicHeight;
553 public int getIntrinsicWidth() {
554 return mIntrinsicWidth;
558 public int getIntrinsicHeight() {
559 return mIntrinsicHeight;