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.ActivityManager;
20 import android.app.ActivityManagerNative;
21 import android.app.admin.DevicePolicyManager;
22 import android.content.BroadcastReceiver;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.ServiceConnection;
28 import android.content.pm.ActivityInfo;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ResolveInfo;
31 import android.content.res.Configuration;
32 import android.os.AsyncTask;
33 import android.os.Bundle;
34 import android.os.IBinder;
35 import android.os.Message;
36 import android.os.Messenger;
37 import android.os.RemoteException;
38 import android.os.UserHandle;
39 import android.provider.MediaStore;
40 import android.service.media.CameraPrewarmService;
41 import android.telecom.TelecomManager;
42 import android.util.AttributeSet;
43 import android.util.Log;
44 import android.util.TypedValue;
45 import android.view.View;
46 import android.view.ViewGroup;
47 import android.view.accessibility.AccessibilityNodeInfo;
48 import android.view.animation.AnimationUtils;
49 import android.view.animation.Interpolator;
50 import android.widget.FrameLayout;
51 import android.widget.TextView;
53 import com.android.internal.widget.LockPatternUtils;
54 import com.android.keyguard.KeyguardUpdateMonitor;
55 import com.android.keyguard.KeyguardUpdateMonitorCallback;
56 import com.android.systemui.EventLogConstants;
57 import com.android.systemui.EventLogTags;
58 import com.android.systemui.R;
59 import com.android.systemui.assist.AssistManager;
60 import com.android.systemui.statusbar.CommandQueue;
61 import com.android.systemui.statusbar.KeyguardAffordanceView;
62 import com.android.systemui.statusbar.KeyguardIndicationController;
63 import com.android.systemui.statusbar.policy.AccessibilityController;
64 import com.android.systemui.statusbar.policy.FlashlightController;
65 import com.android.systemui.statusbar.policy.PreviewInflater;
67 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
68 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
71 * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
74 public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener,
75 UnlockMethodCache.OnUnlockMethodChangedListener,
76 AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener {
78 final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView";
80 public static final String CAMERA_LAUNCH_SOURCE_AFFORDANCE = "lockscreen_affordance";
81 public static final String CAMERA_LAUNCH_SOURCE_WIGGLE = "wiggle_gesture";
82 public static final String CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = "power_double_tap";
84 public static final String EXTRA_CAMERA_LAUNCH_SOURCE
85 = "com.android.systemui.camera_launch_source";
87 private static final Intent SECURE_CAMERA_INTENT =
88 new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
89 .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
90 public static final Intent INSECURE_CAMERA_INTENT =
91 new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
92 private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);
93 private static final int DOZE_ANIMATION_STAGGER_DELAY = 48;
94 private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250;
96 private KeyguardAffordanceView mCameraImageView;
97 private KeyguardAffordanceView mLeftAffordanceView;
98 private LockIcon mLockIcon;
99 private TextView mIndicationText;
100 private ViewGroup mPreviewContainer;
102 private View mLeftPreview;
103 private View mCameraPreview;
105 private ActivityStarter mActivityStarter;
106 private UnlockMethodCache mUnlockMethodCache;
107 private LockPatternUtils mLockPatternUtils;
108 private FlashlightController mFlashlightController;
109 private PreviewInflater mPreviewInflater;
110 private KeyguardIndicationController mIndicationController;
111 private AccessibilityController mAccessibilityController;
112 private PhoneStatusBar mPhoneStatusBar;
114 private final Interpolator mLinearOutSlowInInterpolator;
115 private boolean mUserSetupComplete;
116 private boolean mPrewarmBound;
117 private Messenger mPrewarmMessenger;
118 private final ServiceConnection mPrewarmConnection = new ServiceConnection() {
121 public void onServiceConnected(ComponentName name, IBinder service) {
122 mPrewarmMessenger = new Messenger(service);
126 public void onServiceDisconnected(ComponentName name) {
127 mPrewarmMessenger = null;
131 private boolean mLeftIsVoiceAssist;
132 private AssistManager mAssistManager;
134 public KeyguardBottomAreaView(Context context) {
138 public KeyguardBottomAreaView(Context context, AttributeSet attrs) {
139 this(context, attrs, 0);
142 public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr) {
143 this(context, attrs, defStyleAttr, 0);
146 public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr,
148 super(context, attrs, defStyleAttr, defStyleRes);
149 mLinearOutSlowInInterpolator =
150 AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
153 private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
155 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
156 super.onInitializeAccessibilityNodeInfo(host, info);
158 if (host == mLockIcon) {
159 label = getResources().getString(R.string.unlock_label);
160 } else if (host == mCameraImageView) {
161 label = getResources().getString(R.string.camera_label);
162 } else if (host == mLeftAffordanceView) {
163 if (mLeftIsVoiceAssist) {
164 label = getResources().getString(R.string.voice_assist_label);
166 label = getResources().getString(R.string.phone_label);
169 info.addAction(new AccessibilityAction(ACTION_CLICK, label));
173 public boolean performAccessibilityAction(View host, int action, Bundle args) {
174 if (action == ACTION_CLICK) {
175 if (host == mLockIcon) {
176 mPhoneStatusBar.animateCollapsePanels(
177 CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
179 } else if (host == mCameraImageView) {
180 launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
182 } else if (host == mLeftAffordanceView) {
183 launchLeftAffordance();
187 return super.performAccessibilityAction(host, action, args);
192 protected void onFinishInflate() {
193 super.onFinishInflate();
194 mLockPatternUtils = new LockPatternUtils(mContext);
195 mPreviewContainer = (ViewGroup) findViewById(R.id.preview_container);
196 mCameraImageView = (KeyguardAffordanceView) findViewById(R.id.camera_button);
197 mLeftAffordanceView = (KeyguardAffordanceView) findViewById(R.id.left_button);
198 mLockIcon = (LockIcon) findViewById(R.id.lock_icon);
199 mIndicationText = (TextView) findViewById(R.id.keyguard_indication_text);
200 watchForCameraPolicyChanges();
201 updateCameraVisibility();
202 mUnlockMethodCache = UnlockMethodCache.getInstance(getContext());
203 mUnlockMethodCache.addListener(this);
205 setClipChildren(false);
206 setClipToPadding(false);
207 mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext));
208 inflateCameraPreview();
209 mLockIcon.setOnClickListener(this);
210 mLockIcon.setOnLongClickListener(this);
211 mCameraImageView.setOnClickListener(this);
212 mLeftAffordanceView.setOnClickListener(this);
216 private void initAccessibility() {
217 mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
218 mLeftAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
219 mCameraImageView.setAccessibilityDelegate(mAccessibilityDelegate);
223 protected void onConfigurationChanged(Configuration newConfig) {
224 super.onConfigurationChanged(newConfig);
225 int indicationBottomMargin = getResources().getDimensionPixelSize(
226 R.dimen.keyguard_indication_margin_bottom);
227 MarginLayoutParams mlp = (MarginLayoutParams) mIndicationText.getLayoutParams();
228 if (mlp.bottomMargin != indicationBottomMargin) {
229 mlp.bottomMargin = indicationBottomMargin;
230 mIndicationText.setLayoutParams(mlp);
233 // Respect font size setting.
234 mIndicationText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
235 getResources().getDimensionPixelSize(
236 com.android.internal.R.dimen.text_size_small_material));
239 public void setActivityStarter(ActivityStarter activityStarter) {
240 mActivityStarter = activityStarter;
243 public void setFlashlightController(FlashlightController flashlightController) {
244 mFlashlightController = flashlightController;
247 public void setAccessibilityController(AccessibilityController accessibilityController) {
248 mAccessibilityController = accessibilityController;
249 mLockIcon.setAccessibilityController(accessibilityController);
250 accessibilityController.addStateChangedCallback(this);
253 public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
254 mPhoneStatusBar = phoneStatusBar;
255 updateCameraVisibility(); // in case onFinishInflate() was called too early
258 public void setUserSetupComplete(boolean userSetupComplete) {
259 mUserSetupComplete = userSetupComplete;
260 updateCameraVisibility();
261 updateLeftAffordanceIcon();
264 private Intent getCameraIntent() {
265 KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
266 boolean canSkipBouncer = updateMonitor.getUserCanSkipBouncer(
267 KeyguardUpdateMonitor.getCurrentUser());
268 boolean secure = mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser());
269 return (secure && !canSkipBouncer) ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
273 * Resolves the intent to launch the camera application.
275 public ResolveInfo resolveCameraIntent() {
276 return mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(),
277 PackageManager.MATCH_DEFAULT_ONLY,
278 KeyguardUpdateMonitor.getCurrentUser());
281 private void updateCameraVisibility() {
282 if (mCameraImageView == null) {
283 // Things are not set up yet; reply hazy, ask again later
286 ResolveInfo resolved = resolveCameraIntent();
287 boolean visible = !isCameraDisabledByDpm() && resolved != null
288 && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance)
289 && mUserSetupComplete;
290 mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
293 private void updateLeftAffordanceIcon() {
294 mLeftIsVoiceAssist = canLaunchVoiceAssist();
296 int contentDescription;
297 boolean visible = mUserSetupComplete;
298 if (mLeftIsVoiceAssist) {
299 drawableId = R.drawable.ic_mic_26dp;
300 contentDescription = R.string.accessibility_voice_assist_button;
302 visible &= isPhoneVisible();
303 drawableId = R.drawable.ic_phone_24dp;
304 contentDescription = R.string.accessibility_phone_button;
306 mLeftAffordanceView.setVisibility(visible ? View.VISIBLE : View.GONE);
307 mLeftAffordanceView.setImageDrawable(mContext.getDrawable(drawableId));
308 mLeftAffordanceView.setContentDescription(mContext.getString(contentDescription));
311 public boolean isLeftVoiceAssist() {
312 return mLeftIsVoiceAssist;
315 private boolean isPhoneVisible() {
316 PackageManager pm = mContext.getPackageManager();
317 return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
318 && pm.resolveActivity(PHONE_INTENT, 0) != null;
321 private boolean isCameraDisabledByDpm() {
322 final DevicePolicyManager dpm =
323 (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
324 if (dpm != null && mPhoneStatusBar != null) {
326 final int userId = ActivityManagerNative.getDefault().getCurrentUser().id;
327 final int disabledFlags = dpm.getKeyguardDisabledFeatures(null, userId);
328 final boolean disabledBecauseKeyguardSecure =
329 (disabledFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0
330 && mPhoneStatusBar.isKeyguardSecure();
331 return dpm.getCameraDisabled(null) || disabledBecauseKeyguardSecure;
332 } catch (RemoteException e) {
333 Log.e(TAG, "Can't get userId", e);
339 private void watchForCameraPolicyChanges() {
340 final IntentFilter filter = new IntentFilter();
341 filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
342 getContext().registerReceiverAsUser(mDevicePolicyReceiver,
343 UserHandle.ALL, filter, null, null);
344 KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
348 public void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled) {
349 mCameraImageView.setClickable(touchExplorationEnabled);
350 mLeftAffordanceView.setClickable(touchExplorationEnabled);
351 mCameraImageView.setFocusable(accessibilityEnabled);
352 mLeftAffordanceView.setFocusable(accessibilityEnabled);
357 public void onClick(View v) {
358 if (v == mCameraImageView) {
359 launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
360 } else if (v == mLeftAffordanceView) {
361 launchLeftAffordance();
362 } if (v == mLockIcon) {
363 if (!mAccessibilityController.isAccessibilityEnabled()) {
364 handleTrustCircleClick();
366 mPhoneStatusBar.animateCollapsePanels(
367 CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
373 public boolean onLongClick(View v) {
374 handleTrustCircleClick();
378 private void handleTrustCircleClick() {
379 EventLogTags.writeSysuiLockscreenGesture(
380 EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_LOCK, 0 /* lengthDp - N/A */,
381 0 /* velocityDp - N/A */);
382 mIndicationController.showTransientIndication(
383 R.string.keyguard_indication_trust_disabled);
384 mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
387 public void bindCameraPrewarmService() {
388 Intent intent = getCameraIntent();
389 ActivityInfo targetInfo = PreviewInflater.getTargetActivityInfo(mContext, intent,
390 KeyguardUpdateMonitor.getCurrentUser());
391 if (targetInfo != null && targetInfo.metaData != null) {
392 String clazz = targetInfo.metaData.getString(
393 MediaStore.META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE);
395 Intent serviceIntent = new Intent();
396 serviceIntent.setClassName(targetInfo.packageName, clazz);
397 serviceIntent.setAction(CameraPrewarmService.ACTION_PREWARM);
399 if (getContext().bindServiceAsUser(serviceIntent, mPrewarmConnection,
400 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
401 new UserHandle(UserHandle.USER_CURRENT))) {
402 mPrewarmBound = true;
404 } catch (SecurityException e) {
405 Log.w(TAG, "Unable to bind to prewarm service package=" + targetInfo.packageName
406 + " class=" + clazz, e);
412 public void unbindCameraPrewarmService(boolean launched) {
414 if (mPrewarmMessenger != null && launched) {
416 mPrewarmMessenger.send(Message.obtain(null /* handler */,
417 CameraPrewarmService.MSG_CAMERA_FIRED));
418 } catch (RemoteException e) {
419 Log.w(TAG, "Error sending camera fired message", e);
422 mContext.unbindService(mPrewarmConnection);
423 mPrewarmBound = false;
427 public void launchCamera(String source) {
428 final Intent intent = getCameraIntent();
429 intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source);
430 boolean wouldLaunchResolverActivity = PreviewInflater.wouldLaunchResolverActivity(
431 mContext, intent, KeyguardUpdateMonitor.getCurrentUser());
432 if (intent == SECURE_CAMERA_INTENT && !wouldLaunchResolverActivity) {
433 AsyncTask.execute(new Runnable() {
436 int result = ActivityManager.START_CANCELED;
438 result = ActivityManagerNative.getDefault().startActivityAsUser(
439 null, getContext().getBasePackageName(),
441 intent.resolveTypeIfNeeded(getContext().getContentResolver()),
442 null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, null,
443 UserHandle.CURRENT.getIdentifier());
444 } catch (RemoteException e) {
445 Log.w(TAG, "Unable to start camera activity", e);
447 mActivityStarter.preventNextAnimation();
448 final boolean launched = isSuccessfulLaunch(result);
449 post(new Runnable() {
452 unbindCameraPrewarmService(launched);
459 // We need to delay starting the activity because ResolverActivity finishes itself if
460 // launched behind lockscreen.
461 mActivityStarter.startActivity(intent, false /* dismissShade */,
462 new ActivityStarter.Callback() {
464 public void onActivityStarted(int resultCode) {
465 unbindCameraPrewarmService(isSuccessfulLaunch(resultCode));
471 private static boolean isSuccessfulLaunch(int result) {
472 return result == ActivityManager.START_SUCCESS
473 || result == ActivityManager.START_DELIVERED_TO_TOP
474 || result == ActivityManager.START_TASK_TO_FRONT;
477 public void launchLeftAffordance() {
478 if (mLeftIsVoiceAssist) {
485 private void launchVoiceAssist() {
486 Runnable runnable = new Runnable() {
489 mAssistManager.launchVoiceAssistFromKeyguard();
490 mActivityStarter.preventNextAnimation();
493 if (mPhoneStatusBar.isKeyguardCurrentlySecure()) {
494 AsyncTask.execute(runnable);
496 mPhoneStatusBar.executeRunnableDismissingKeyguard(runnable, null /* cancelAction */,
497 false /* dismissShade */, false /* afterKeyguardGone */);
501 private boolean canLaunchVoiceAssist() {
502 return mAssistManager.canVoiceAssistBeLaunchedFromKeyguard();
505 private void launchPhone() {
506 final TelecomManager tm = TelecomManager.from(mContext);
508 AsyncTask.execute(new Runnable() {
511 tm.showInCallScreen(false /* showDialpad */);
515 mActivityStarter.startActivity(PHONE_INTENT, false /* dismissShade */);
521 protected void onVisibilityChanged(View changedView, int visibility) {
522 super.onVisibilityChanged(changedView, visibility);
523 if (changedView == this && visibility == VISIBLE) {
525 updateCameraVisibility();
529 public KeyguardAffordanceView getLeftView() {
530 return mLeftAffordanceView;
533 public KeyguardAffordanceView getRightView() {
534 return mCameraImageView;
537 public View getLeftPreview() {
541 public View getRightPreview() {
542 return mCameraPreview;
545 public LockIcon getLockIcon() {
549 public View getIndicationView() {
550 return mIndicationText;
554 public boolean hasOverlappingRendering() {
559 public void onUnlockMethodStateChanged() {
561 updateCameraVisibility();
564 private void inflateCameraPreview() {
565 mCameraPreview = mPreviewInflater.inflatePreview(getCameraIntent());
566 if (mCameraPreview != null) {
567 mPreviewContainer.addView(mCameraPreview);
568 mCameraPreview.setVisibility(View.INVISIBLE);
572 private void updateLeftPreview() {
573 View previewBefore = mLeftPreview;
574 if (previewBefore != null) {
575 mPreviewContainer.removeView(previewBefore);
577 if (mLeftIsVoiceAssist) {
578 mLeftPreview = mPreviewInflater.inflatePreviewFromService(
579 mAssistManager.getVoiceInteractorComponentName());
581 mLeftPreview = mPreviewInflater.inflatePreview(PHONE_INTENT);
583 if (mLeftPreview != null) {
584 mPreviewContainer.addView(mLeftPreview);
585 mLeftPreview.setVisibility(View.INVISIBLE);
589 public void startFinishDozeAnimation() {
591 if (mLeftAffordanceView.getVisibility() == View.VISIBLE) {
592 startFinishDozeAnimationElement(mLeftAffordanceView, delay);
593 delay += DOZE_ANIMATION_STAGGER_DELAY;
595 startFinishDozeAnimationElement(mLockIcon, delay);
596 delay += DOZE_ANIMATION_STAGGER_DELAY;
597 if (mCameraImageView.getVisibility() == View.VISIBLE) {
598 startFinishDozeAnimationElement(mCameraImageView, delay);
600 mIndicationText.setAlpha(0f);
601 mIndicationText.animate()
603 .setInterpolator(mLinearOutSlowInInterpolator)
604 .setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
607 private void startFinishDozeAnimationElement(View element, long delay) {
608 element.setAlpha(0f);
609 element.setTranslationY(element.getHeight() / 2);
613 .setInterpolator(mLinearOutSlowInInterpolator)
614 .setStartDelay(delay)
615 .setDuration(DOZE_ANIMATION_ELEMENT_DURATION);
618 private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() {
620 public void onReceive(Context context, Intent intent) {
621 post(new Runnable() {
624 updateCameraVisibility();
630 private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
631 new KeyguardUpdateMonitorCallback() {
633 public void onUserSwitchComplete(int userId) {
634 updateCameraVisibility();
638 public void onStartedWakingUp() {
639 mLockIcon.setDeviceInteractive(true);
643 public void onFinishedGoingToSleep(int why) {
644 mLockIcon.setDeviceInteractive(false);
648 public void onScreenTurnedOn() {
649 mLockIcon.setScreenOn(true);
653 public void onScreenTurnedOff() {
654 mLockIcon.setScreenOn(false);
658 public void onKeyguardVisibilityChanged(boolean showing) {
663 public void onFingerprintRunningStateChanged(boolean running) {
668 public void onStrongAuthStateChanged(int userId) {
673 public void setKeyguardIndicationController(
674 KeyguardIndicationController keyguardIndicationController) {
675 mIndicationController = keyguardIndicationController;
678 public void setAssistManager(AssistManager assistManager) {
679 mAssistManager = assistManager;
680 updateLeftAffordance();
683 public void updateLeftAffordance() {
684 updateLeftAffordanceIcon();