1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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
15  */
16 
17 package com.android.systemui.statusbar.phone;
18 
19 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
20 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
21 
22 import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON;
23 import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK;
24 import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON;
25 import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_UNLOCK;
26 
27 import android.app.ActivityManager;
28 import android.app.ActivityOptions;
29 import android.app.admin.DevicePolicyManager;
30 import android.content.BroadcastReceiver;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.IntentFilter;
35 import android.content.ServiceConnection;
36 import android.content.pm.ActivityInfo;
37 import android.content.pm.PackageManager;
38 import android.content.pm.ResolveInfo;
39 import android.content.res.Configuration;
40 import android.graphics.drawable.Drawable;
41 import android.os.AsyncTask;
42 import android.os.Bundle;
43 import android.os.IBinder;
44 import android.os.Message;
45 import android.os.Messenger;
46 import android.os.RemoteException;
47 import android.os.UserHandle;
48 import android.provider.MediaStore;
49 import android.service.media.CameraPrewarmService;
50 import android.telecom.TelecomManager;
51 import android.text.TextUtils;
52 import android.util.AttributeSet;
53 import android.util.Log;
54 import android.util.MathUtils;
55 import android.util.TypedValue;
56 import android.view.View;
57 import android.view.ViewGroup;
58 import android.view.WindowInsets;
59 import android.view.WindowManager;
60 import android.view.accessibility.AccessibilityNodeInfo;
61 import android.widget.FrameLayout;
62 import android.widget.TextView;
63 
64 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
65 import com.android.internal.widget.LockPatternUtils;
66 import com.android.keyguard.KeyguardUpdateMonitor;
67 import com.android.keyguard.KeyguardUpdateMonitorCallback;
68 import com.android.systemui.EventLogTags;
69 import com.android.systemui.Dependency;
70 import com.android.systemui.Interpolators;
71 import com.android.systemui.R;
72 import com.android.systemui.assist.AssistManager;
73 import com.android.systemui.plugins.IntentButtonProvider;
74 import com.android.systemui.plugins.IntentButtonProvider.IntentButton;
75 import com.android.systemui.plugins.IntentButtonProvider.IntentButton.IconState;
76 import com.android.systemui.plugins.PluginListener;
77 import com.android.systemui.plugins.ActivityStarter;
78 import com.android.systemui.statusbar.CommandQueue;
79 import com.android.systemui.statusbar.KeyguardAffordanceView;
80 import com.android.systemui.statusbar.KeyguardIndicationController;
81 import com.android.systemui.statusbar.policy.AccessibilityController;
82 import com.android.systemui.statusbar.policy.ExtensionController;
83 import com.android.systemui.statusbar.policy.ExtensionController.Extension;
84 import com.android.systemui.statusbar.policy.FlashlightController;
85 import com.android.systemui.statusbar.policy.PreviewInflater;
86 import com.android.systemui.tuner.LockscreenFragment.LockButtonFactory;
87 import com.android.systemui.tuner.TunerService;
88 
89 /**
90  * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
91  * text.
92  */
93 public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener,
94         UnlockMethodCache.OnUnlockMethodChangedListener,
95         AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener {
96 
97     final static String TAG = "StatusBar/KeyguardBottomAreaView";
98 
99     public static final String CAMERA_LAUNCH_SOURCE_AFFORDANCE = "lockscreen_affordance";
100     public static final String CAMERA_LAUNCH_SOURCE_WIGGLE = "wiggle_gesture";
101     public static final String CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = "power_double_tap";
102     public static final String CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = "lift_to_launch_ml";
103 
104     public static final String EXTRA_CAMERA_LAUNCH_SOURCE
105             = "com.android.systemui.camera_launch_source";
106 
107     private static final String LEFT_BUTTON_PLUGIN
108             = "com.android.systemui.action.PLUGIN_LOCKSCREEN_LEFT_BUTTON";
109     private static final String RIGHT_BUTTON_PLUGIN
110             = "com.android.systemui.action.PLUGIN_LOCKSCREEN_RIGHT_BUTTON";
111 
112     private static final Intent SECURE_CAMERA_INTENT =
113             new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
114                     .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
115     public static final Intent INSECURE_CAMERA_INTENT =
116             new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
117     private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);
118     private static final int DOZE_ANIMATION_STAGGER_DELAY = 48;
119     private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250;
120 
121     private KeyguardAffordanceView mRightAffordanceView;
122     private KeyguardAffordanceView mLeftAffordanceView;
123     private LockIcon mLockIcon;
124     private ViewGroup mIndicationArea;
125     private TextView mEnterpriseDisclosure;
126     private TextView mIndicationText;
127     private ViewGroup mPreviewContainer;
128     private ViewGroup mOverlayContainer;
129 
130     private View mLeftPreview;
131     private View mCameraPreview;
132 
133     private ActivityStarter mActivityStarter;
134     private UnlockMethodCache mUnlockMethodCache;
135     private LockPatternUtils mLockPatternUtils;
136     private FlashlightController mFlashlightController;
137     private PreviewInflater mPreviewInflater;
138     private KeyguardIndicationController mIndicationController;
139     private AccessibilityController mAccessibilityController;
140     private StatusBar mStatusBar;
141     private KeyguardAffordanceHelper mAffordanceHelper;
142 
143     private boolean mUserSetupComplete;
144     private boolean mPrewarmBound;
145     private Messenger mPrewarmMessenger;
146     private final ServiceConnection mPrewarmConnection = new ServiceConnection() {
147 
148         @Override
149         public void onServiceConnected(ComponentName name, IBinder service) {
150             mPrewarmMessenger = new Messenger(service);
151         }
152 
153         @Override
154         public void onServiceDisconnected(ComponentName name) {
155             mPrewarmMessenger = null;
156         }
157     };
158 
159     private boolean mLeftIsVoiceAssist;
160     private AssistManager mAssistManager;
161     private Drawable mLeftAssistIcon;
162 
163     private IntentButton mRightButton = new DefaultRightButton();
164     private Extension<IntentButton> mRightExtension;
165     private String mRightButtonStr;
166     private IntentButton mLeftButton = new DefaultLeftButton();
167     private Extension<IntentButton> mLeftExtension;
168     private String mLeftButtonStr;
169     private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
170     private boolean mDozing;
171     private int mIndicationBottomMargin;
172     private int mIndicationBottomMarginAmbient;
173     private float mDarkAmount;
174     private int mBurnInXOffset;
175 
KeyguardBottomAreaView(Context context)176     public KeyguardBottomAreaView(Context context) {
177         this(context, null);
178     }
179 
KeyguardBottomAreaView(Context context, AttributeSet attrs)180     public KeyguardBottomAreaView(Context context, AttributeSet attrs) {
181         this(context, attrs, 0);
182     }
183 
KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr)184     public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr) {
185         this(context, attrs, defStyleAttr, 0);
186     }
187 
KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)188     public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr,
189             int defStyleRes) {
190         super(context, attrs, defStyleAttr, defStyleRes);
191     }
192 
193     private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
194         @Override
195         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
196             super.onInitializeAccessibilityNodeInfo(host, info);
197             String label = null;
198             if (host == mLockIcon) {
199                 label = getResources().getString(R.string.unlock_label);
200             } else if (host == mRightAffordanceView) {
201                 label = getResources().getString(R.string.camera_label);
202             } else if (host == mLeftAffordanceView) {
203                 if (mLeftIsVoiceAssist) {
204                     label = getResources().getString(R.string.voice_assist_label);
205                 } else {
206                     label = getResources().getString(R.string.phone_label);
207                 }
208             }
209             info.addAction(new AccessibilityAction(ACTION_CLICK, label));
210         }
211 
212         @Override
213         public boolean performAccessibilityAction(View host, int action, Bundle args) {
214             if (action == ACTION_CLICK) {
215                 if (host == mLockIcon) {
216                     mStatusBar.animateCollapsePanels(
217                             CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
218                     return true;
219                 } else if (host == mRightAffordanceView) {
220                     launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
221                     return true;
222                 } else if (host == mLeftAffordanceView) {
223                     launchLeftAffordance();
224                     return true;
225                 }
226             }
227             return super.performAccessibilityAction(host, action, args);
228         }
229     };
230 
231     @Override
onFinishInflate()232     protected void onFinishInflate() {
233         super.onFinishInflate();
234         mLockPatternUtils = new LockPatternUtils(mContext);
235         mPreviewContainer = findViewById(R.id.preview_container);
236         mOverlayContainer = findViewById(R.id.overlay_container);
237         mRightAffordanceView = findViewById(R.id.camera_button);
238         mLeftAffordanceView = findViewById(R.id.left_button);
239         mLockIcon = findViewById(R.id.lock_icon);
240         mIndicationArea = findViewById(R.id.keyguard_indication_area);
241         mEnterpriseDisclosure = findViewById(
242                 R.id.keyguard_indication_enterprise_disclosure);
243         mIndicationText = findViewById(R.id.keyguard_indication_text);
244         mIndicationBottomMargin = getResources().getDimensionPixelSize(
245                 R.dimen.keyguard_indication_margin_bottom);
246         mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize(
247                 R.dimen.keyguard_indication_margin_bottom_ambient);
248         updateCameraVisibility();
249         mUnlockMethodCache = UnlockMethodCache.getInstance(getContext());
250         mUnlockMethodCache.addListener(this);
251         KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
252         mLockIcon.setScreenOn(updateMonitor.isScreenOn());
253         mLockIcon.setDeviceInteractive(updateMonitor.isDeviceInteractive());
254         mLockIcon.update();
255         setClipChildren(false);
256         setClipToPadding(false);
257         mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext));
258         inflateCameraPreview();
259         mLockIcon.setOnClickListener(this);
260         mLockIcon.setOnLongClickListener(this);
261         mRightAffordanceView.setOnClickListener(this);
262         mLeftAffordanceView.setOnClickListener(this);
263         initAccessibility();
264         mActivityStarter = Dependency.get(ActivityStarter.class);
265         mFlashlightController = Dependency.get(FlashlightController.class);
266         mAccessibilityController = Dependency.get(AccessibilityController.class);
267         mAssistManager = Dependency.get(AssistManager.class);
268         mLockIcon.setAccessibilityController(mAccessibilityController);
269         updateLeftAffordance();
270     }
271 
272     @Override
onAttachedToWindow()273     protected void onAttachedToWindow() {
274         super.onAttachedToWindow();
275         mAccessibilityController.addStateChangedCallback(this);
276         mRightExtension = Dependency.get(ExtensionController.class).newExtension(IntentButton.class)
277                 .withPlugin(IntentButtonProvider.class, RIGHT_BUTTON_PLUGIN,
278                         p -> p.getIntentButton())
279                 .withTunerFactory(new LockButtonFactory(mContext, LOCKSCREEN_RIGHT_BUTTON))
280                 .withDefault(() -> new DefaultRightButton())
281                 .withCallback(button -> setRightButton(button))
282                 .build();
283         mLeftExtension = Dependency.get(ExtensionController.class).newExtension(IntentButton.class)
284                 .withPlugin(IntentButtonProvider.class, LEFT_BUTTON_PLUGIN,
285                         p -> p.getIntentButton())
286                 .withTunerFactory(new LockButtonFactory(mContext, LOCKSCREEN_LEFT_BUTTON))
287                 .withDefault(() -> new DefaultLeftButton())
288                 .withCallback(button -> setLeftButton(button))
289                 .build();
290         final IntentFilter filter = new IntentFilter();
291         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
292         getContext().registerReceiverAsUser(mDevicePolicyReceiver,
293                 UserHandle.ALL, filter, null, null);
294         KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
295     }
296 
297     @Override
onDetachedFromWindow()298     protected void onDetachedFromWindow() {
299         super.onDetachedFromWindow();
300         mAccessibilityController.removeStateChangedCallback(this);
301         mRightExtension.destroy();
302         mLeftExtension.destroy();
303         getContext().unregisterReceiver(mDevicePolicyReceiver);
304         KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback);
305     }
306 
initAccessibility()307     private void initAccessibility() {
308         mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
309         mLeftAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
310         mRightAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
311     }
312 
313     @Override
onConfigurationChanged(Configuration newConfig)314     protected void onConfigurationChanged(Configuration newConfig) {
315         super.onConfigurationChanged(newConfig);
316         mIndicationBottomMargin = getResources().getDimensionPixelSize(
317                 R.dimen.keyguard_indication_margin_bottom);
318         mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize(
319                 R.dimen.keyguard_indication_margin_bottom_ambient);
320         MarginLayoutParams mlp = (MarginLayoutParams) mIndicationArea.getLayoutParams();
321         if (mlp.bottomMargin != mIndicationBottomMargin) {
322             mlp.bottomMargin = mIndicationBottomMargin;
323             mIndicationArea.setLayoutParams(mlp);
324         }
325 
326         // Respect font size setting.
327         mEnterpriseDisclosure.setTextSize(TypedValue.COMPLEX_UNIT_PX,
328                 getResources().getDimensionPixelSize(
329                         com.android.internal.R.dimen.text_size_small_material));
330         mIndicationText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
331                 getResources().getDimensionPixelSize(
332                         com.android.internal.R.dimen.text_size_small_material));
333 
334         ViewGroup.LayoutParams lp = mRightAffordanceView.getLayoutParams();
335         lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
336         lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
337         mRightAffordanceView.setLayoutParams(lp);
338         updateRightAffordanceIcon();
339 
340         lp = mLockIcon.getLayoutParams();
341         lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
342         lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
343         mLockIcon.setLayoutParams(lp);
344         mLockIcon.setContentDescription(getContext().getText(R.string.accessibility_unlock_button));
345         mLockIcon.update(true /* force */);
346 
347         lp = mLeftAffordanceView.getLayoutParams();
348         lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
349         lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
350         mLeftAffordanceView.setLayoutParams(lp);
351         updateLeftAffordanceIcon();
352     }
353 
updateRightAffordanceIcon()354     private void updateRightAffordanceIcon() {
355         IconState state = mRightButton.getIcon();
356         mRightAffordanceView.setVisibility(!mDozing && state.isVisible ? View.VISIBLE : View.GONE);
357         mRightAffordanceView.setImageDrawable(state.drawable, state.tint);
358         mRightAffordanceView.setContentDescription(state.contentDescription);
359     }
360 
setStatusBar(StatusBar statusBar)361     public void setStatusBar(StatusBar statusBar) {
362         mStatusBar = statusBar;
363         updateCameraVisibility(); // in case onFinishInflate() was called too early
364     }
365 
setAffordanceHelper(KeyguardAffordanceHelper affordanceHelper)366     public void setAffordanceHelper(KeyguardAffordanceHelper affordanceHelper) {
367         mAffordanceHelper = affordanceHelper;
368     }
369 
setUserSetupComplete(boolean userSetupComplete)370     public void setUserSetupComplete(boolean userSetupComplete) {
371         mUserSetupComplete = userSetupComplete;
372         updateCameraVisibility();
373         updateLeftAffordanceIcon();
374     }
375 
getCameraIntent()376     private Intent getCameraIntent() {
377         return mRightButton.getIntent();
378     }
379 
380     /**
381      * Resolves the intent to launch the camera application.
382      */
resolveCameraIntent()383     public ResolveInfo resolveCameraIntent() {
384         return mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(),
385                 PackageManager.MATCH_DEFAULT_ONLY,
386                 KeyguardUpdateMonitor.getCurrentUser());
387     }
388 
updateCameraVisibility()389     private void updateCameraVisibility() {
390         if (mRightAffordanceView == null) {
391             // Things are not set up yet; reply hazy, ask again later
392             return;
393         }
394         mRightAffordanceView.setVisibility(!mDozing && mRightButton.getIcon().isVisible
395                 ? View.VISIBLE : View.GONE);
396     }
397 
398     /**
399      * Set an alternate icon for the left assist affordance (replace the mic icon)
400      */
setLeftAssistIcon(Drawable drawable)401     public void setLeftAssistIcon(Drawable drawable) {
402         mLeftAssistIcon = drawable;
403         updateLeftAffordanceIcon();
404     }
405 
updateLeftAffordanceIcon()406     private void updateLeftAffordanceIcon() {
407         IconState state = mLeftButton.getIcon();
408         mLeftAffordanceView.setVisibility(!mDozing && state.isVisible ? View.VISIBLE : View.GONE);
409         mLeftAffordanceView.setImageDrawable(state.drawable, state.tint);
410         mLeftAffordanceView.setContentDescription(state.contentDescription);
411     }
412 
isLeftVoiceAssist()413     public boolean isLeftVoiceAssist() {
414         return mLeftIsVoiceAssist;
415     }
416 
isPhoneVisible()417     private boolean isPhoneVisible() {
418         PackageManager pm = mContext.getPackageManager();
419         return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
420                 && pm.resolveActivity(PHONE_INTENT, 0) != null;
421     }
422 
423     @Override
onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled)424     public void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled) {
425         mRightAffordanceView.setClickable(touchExplorationEnabled);
426         mLeftAffordanceView.setClickable(touchExplorationEnabled);
427         mRightAffordanceView.setFocusable(accessibilityEnabled);
428         mLeftAffordanceView.setFocusable(accessibilityEnabled);
429         mLockIcon.update();
430     }
431 
432     @Override
onClick(View v)433     public void onClick(View v) {
434         if (v == mRightAffordanceView) {
435             launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
436         } else if (v == mLeftAffordanceView) {
437             launchLeftAffordance();
438         } if (v == mLockIcon) {
439             if (!mAccessibilityController.isAccessibilityEnabled()) {
440                 handleTrustCircleClick();
441             } else {
442                 mStatusBar.animateCollapsePanels(
443                         CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
444             }
445         }
446     }
447 
448     @Override
onLongClick(View v)449     public boolean onLongClick(View v) {
450         handleTrustCircleClick();
451         return true;
452     }
453 
handleTrustCircleClick()454     private void handleTrustCircleClick() {
455         mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_LOCK, 0 /* lengthDp - N/A */,
456                 0 /* velocityDp - N/A */);
457         mIndicationController.showTransientIndication(
458                 R.string.keyguard_indication_trust_disabled);
459         mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
460     }
461 
bindCameraPrewarmService()462     public void bindCameraPrewarmService() {
463         Intent intent = getCameraIntent();
464         ActivityInfo targetInfo = PreviewInflater.getTargetActivityInfo(mContext, intent,
465                 KeyguardUpdateMonitor.getCurrentUser(), true /* onlyDirectBootAware */);
466         if (targetInfo != null && targetInfo.metaData != null) {
467             String clazz = targetInfo.metaData.getString(
468                     MediaStore.META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE);
469             if (clazz != null) {
470                 Intent serviceIntent = new Intent();
471                 serviceIntent.setClassName(targetInfo.packageName, clazz);
472                 serviceIntent.setAction(CameraPrewarmService.ACTION_PREWARM);
473                 try {
474                     if (getContext().bindServiceAsUser(serviceIntent, mPrewarmConnection,
475                             Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
476                             new UserHandle(UserHandle.USER_CURRENT))) {
477                         mPrewarmBound = true;
478                     }
479                 } catch (SecurityException e) {
480                     Log.w(TAG, "Unable to bind to prewarm service package=" + targetInfo.packageName
481                             + " class=" + clazz, e);
482                 }
483             }
484         }
485     }
486 
unbindCameraPrewarmService(boolean launched)487     public void unbindCameraPrewarmService(boolean launched) {
488         if (mPrewarmBound) {
489             if (mPrewarmMessenger != null && launched) {
490                 try {
491                     mPrewarmMessenger.send(Message.obtain(null /* handler */,
492                             CameraPrewarmService.MSG_CAMERA_FIRED));
493                 } catch (RemoteException e) {
494                     Log.w(TAG, "Error sending camera fired message", e);
495                 }
496             }
497             mContext.unbindService(mPrewarmConnection);
498             mPrewarmBound = false;
499         }
500     }
501 
launchCamera(String source)502     public void launchCamera(String source) {
503         final Intent intent = getCameraIntent();
504         intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source);
505         boolean wouldLaunchResolverActivity = PreviewInflater.wouldLaunchResolverActivity(
506                 mContext, intent, KeyguardUpdateMonitor.getCurrentUser());
507         if (intent == SECURE_CAMERA_INTENT && !wouldLaunchResolverActivity) {
508             AsyncTask.execute(new Runnable() {
509                 @Override
510                 public void run() {
511                     int result = ActivityManager.START_CANCELED;
512 
513                     // Normally an activity will set it's requested rotation
514                     // animation on its window. However when launching an activity
515                     // causes the orientation to change this is too late. In these cases
516                     // the default animation is used. This doesn't look good for
517                     // the camera (as it rotates the camera contents out of sync
518                     // with physical reality). So, we ask the WindowManager to
519                     // force the crossfade animation if an orientation change
520                     // happens to occur during the launch.
521                     ActivityOptions o = ActivityOptions.makeBasic();
522                     o.setDisallowEnterPictureInPictureWhileLaunching(true);
523                     o.setRotationAnimationHint(
524                             WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
525                     try {
526                         result = ActivityManager.getService().startActivityAsUser(
527                                 null, getContext().getBasePackageName(),
528                                 intent,
529                                 intent.resolveTypeIfNeeded(getContext().getContentResolver()),
530                                 null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, o.toBundle(),
531                                 UserHandle.CURRENT.getIdentifier());
532                     } catch (RemoteException e) {
533                         Log.w(TAG, "Unable to start camera activity", e);
534                     }
535                     final boolean launched = isSuccessfulLaunch(result);
536                     post(new Runnable() {
537                         @Override
538                         public void run() {
539                             unbindCameraPrewarmService(launched);
540                         }
541                     });
542                 }
543             });
544         } else {
545 
546             // We need to delay starting the activity because ResolverActivity finishes itself if
547             // launched behind lockscreen.
548             mActivityStarter.startActivity(intent, false /* dismissShade */,
549                     new ActivityStarter.Callback() {
550                         @Override
551                         public void onActivityStarted(int resultCode) {
552                             unbindCameraPrewarmService(isSuccessfulLaunch(resultCode));
553                         }
554                     });
555         }
556     }
557 
setDarkAmount(float darkAmount)558     public void setDarkAmount(float darkAmount) {
559         if (darkAmount == mDarkAmount) {
560             return;
561         }
562         mDarkAmount = darkAmount;
563         // Let's randomize the bottom margin every time we wake up to avoid burn-in.
564         if (darkAmount == 0) {
565             mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize(
566                     R.dimen.keyguard_indication_margin_bottom_ambient)
567                     + (int) (Math.random() * mIndicationText.getTextSize());
568         }
569         mIndicationArea.setAlpha(MathUtils.lerp(1f, 0.7f, darkAmount));
570         mIndicationArea.setTranslationY(MathUtils.lerp(0,
571                 mIndicationBottomMargin - mIndicationBottomMarginAmbient, darkAmount));
572     }
573 
isSuccessfulLaunch(int result)574     private static boolean isSuccessfulLaunch(int result) {
575         return result == ActivityManager.START_SUCCESS
576                 || result == ActivityManager.START_DELIVERED_TO_TOP
577                 || result == ActivityManager.START_TASK_TO_FRONT;
578     }
579 
launchLeftAffordance()580     public void launchLeftAffordance() {
581         if (mLeftIsVoiceAssist) {
582             launchVoiceAssist();
583         } else {
584             launchPhone();
585         }
586     }
587 
launchVoiceAssist()588     private void launchVoiceAssist() {
589         Runnable runnable = new Runnable() {
590             @Override
591             public void run() {
592                 mAssistManager.launchVoiceAssistFromKeyguard();
593             }
594         };
595         if (mStatusBar.isKeyguardCurrentlySecure()) {
596             AsyncTask.execute(runnable);
597         } else {
598             boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr)
599                     && Dependency.get(TunerService.class).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0;
600             mStatusBar.executeRunnableDismissingKeyguard(runnable, null /* cancelAction */,
601                     dismissShade, false /* afterKeyguardGone */, true /* deferred */);
602         }
603     }
604 
canLaunchVoiceAssist()605     private boolean canLaunchVoiceAssist() {
606         return mAssistManager.canVoiceAssistBeLaunchedFromKeyguard();
607     }
608 
launchPhone()609     private void launchPhone() {
610         final TelecomManager tm = TelecomManager.from(mContext);
611         if (tm.isInCall()) {
612             AsyncTask.execute(new Runnable() {
613                 @Override
614                 public void run() {
615                     tm.showInCallScreen(false /* showDialpad */);
616                 }
617             });
618         } else {
619             boolean dismissShade = !TextUtils.isEmpty(mLeftButtonStr)
620                     && Dependency.get(TunerService.class).getValue(LOCKSCREEN_LEFT_UNLOCK, 1) != 0;
621             mActivityStarter.startActivity(mLeftButton.getIntent(), dismissShade);
622         }
623     }
624 
625 
626     @Override
onVisibilityChanged(View changedView, int visibility)627     protected void onVisibilityChanged(View changedView, int visibility) {
628         super.onVisibilityChanged(changedView, visibility);
629         if (changedView == this && visibility == VISIBLE) {
630             mLockIcon.update();
631             updateCameraVisibility();
632         }
633     }
634 
getLeftView()635     public KeyguardAffordanceView getLeftView() {
636         return mLeftAffordanceView;
637     }
638 
getRightView()639     public KeyguardAffordanceView getRightView() {
640         return mRightAffordanceView;
641     }
642 
getLeftPreview()643     public View getLeftPreview() {
644         return mLeftPreview;
645     }
646 
getRightPreview()647     public View getRightPreview() {
648         return mCameraPreview;
649     }
650 
getLockIcon()651     public LockIcon getLockIcon() {
652         return mLockIcon;
653     }
654 
getIndicationArea()655     public View getIndicationArea() {
656         return mIndicationArea;
657     }
658 
659     @Override
hasOverlappingRendering()660     public boolean hasOverlappingRendering() {
661         return false;
662     }
663 
664     @Override
onUnlockMethodStateChanged()665     public void onUnlockMethodStateChanged() {
666         mLockIcon.update();
667         updateCameraVisibility();
668     }
669 
inflateCameraPreview()670     private void inflateCameraPreview() {
671         View previewBefore = mCameraPreview;
672         boolean visibleBefore = false;
673         if (previewBefore != null) {
674             mPreviewContainer.removeView(previewBefore);
675             visibleBefore = previewBefore.getVisibility() == View.VISIBLE;
676         }
677         mCameraPreview = mPreviewInflater.inflatePreview(getCameraIntent());
678         if (mCameraPreview != null) {
679             mPreviewContainer.addView(mCameraPreview);
680             mCameraPreview.setVisibility(visibleBefore ? View.VISIBLE : View.INVISIBLE);
681         }
682         if (mAffordanceHelper != null) {
683             mAffordanceHelper.updatePreviews();
684         }
685     }
686 
updateLeftPreview()687     private void updateLeftPreview() {
688         View previewBefore = mLeftPreview;
689         if (previewBefore != null) {
690             mPreviewContainer.removeView(previewBefore);
691         }
692         if (mLeftIsVoiceAssist) {
693             mLeftPreview = mPreviewInflater.inflatePreviewFromService(
694                     mAssistManager.getVoiceInteractorComponentName());
695         } else {
696             mLeftPreview = mPreviewInflater.inflatePreview(mLeftButton.getIntent());
697         }
698         if (mLeftPreview != null) {
699             mPreviewContainer.addView(mLeftPreview);
700             mLeftPreview.setVisibility(View.INVISIBLE);
701         }
702         if (mAffordanceHelper != null) {
703             mAffordanceHelper.updatePreviews();
704         }
705     }
706 
startFinishDozeAnimation()707     public void startFinishDozeAnimation() {
708         long delay = 0;
709         if (mLeftAffordanceView.getVisibility() == View.VISIBLE) {
710             startFinishDozeAnimationElement(mLeftAffordanceView, delay);
711             delay += DOZE_ANIMATION_STAGGER_DELAY;
712         }
713         startFinishDozeAnimationElement(mLockIcon, delay);
714         delay += DOZE_ANIMATION_STAGGER_DELAY;
715         if (mRightAffordanceView.getVisibility() == View.VISIBLE) {
716             startFinishDozeAnimationElement(mRightAffordanceView, delay);
717         }
718     }
719 
startFinishDozeAnimationElement(View element, long delay)720     private void startFinishDozeAnimationElement(View element, long delay) {
721         element.setAlpha(0f);
722         element.setTranslationY(element.getHeight() / 2);
723         element.animate()
724                 .alpha(1f)
725                 .translationY(0f)
726                 .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
727                 .setStartDelay(delay)
728                 .setDuration(DOZE_ANIMATION_ELEMENT_DURATION);
729     }
730 
731     private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() {
732         @Override
733         public void onReceive(Context context, Intent intent) {
734             post(new Runnable() {
735                 @Override
736                 public void run() {
737                     updateCameraVisibility();
738                 }
739             });
740         }
741     };
742 
743     private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
744             new KeyguardUpdateMonitorCallback() {
745                 @Override
746                 public void onUserSwitchComplete(int userId) {
747                     updateCameraVisibility();
748                 }
749 
750                 @Override
751                 public void onStartedWakingUp() {
752                     mLockIcon.setDeviceInteractive(true);
753                 }
754 
755                 @Override
756                 public void onFinishedGoingToSleep(int why) {
757                     mLockIcon.setDeviceInteractive(false);
758                 }
759 
760                 @Override
761                 public void onScreenTurnedOn() {
762                     mLockIcon.setScreenOn(true);
763                 }
764 
765                 @Override
766                 public void onScreenTurnedOff() {
767                     mLockIcon.setScreenOn(false);
768                 }
769 
770                 @Override
771                 public void onKeyguardVisibilityChanged(boolean showing) {
772                     mLockIcon.update();
773                 }
774 
775                 @Override
776                 public void onFingerprintRunningStateChanged(boolean running) {
777                     mLockIcon.update();
778                 }
779 
780                 @Override
781                 public void onStrongAuthStateChanged(int userId) {
782                     mLockIcon.update();
783         }
784 
785                 @Override
786                 public void onUserUnlocked() {
787                     inflateCameraPreview();
788                     updateCameraVisibility();
789                     updateLeftAffordance();
790                 }
791             };
792 
setKeyguardIndicationController( KeyguardIndicationController keyguardIndicationController)793     public void setKeyguardIndicationController(
794             KeyguardIndicationController keyguardIndicationController) {
795         mIndicationController = keyguardIndicationController;
796     }
797 
updateLeftAffordance()798     public void updateLeftAffordance() {
799         updateLeftAffordanceIcon();
800         updateLeftPreview();
801     }
802 
onKeyguardShowingChanged()803     public void onKeyguardShowingChanged() {
804         updateLeftAffordance();
805         inflateCameraPreview();
806     }
807 
setRightButton(IntentButton button)808     private void setRightButton(IntentButton button) {
809         mRightButton = button;
810         updateRightAffordanceIcon();
811         updateCameraVisibility();
812         inflateCameraPreview();
813     }
814 
setLeftButton(IntentButton button)815     private void setLeftButton(IntentButton button) {
816         mLeftButton = button;
817         if (!(mLeftButton instanceof DefaultLeftButton)) {
818             mLeftIsVoiceAssist = false;
819         }
820         updateLeftAffordance();
821     }
822 
setDozing(boolean dozing, boolean animate)823     public void setDozing(boolean dozing, boolean animate) {
824         mDozing = dozing;
825 
826         updateCameraVisibility();
827         updateLeftAffordanceIcon();
828 
829         if (dozing) {
830             mLockIcon.setVisibility(INVISIBLE);
831             mOverlayContainer.setVisibility(INVISIBLE);
832         } else {
833             mLockIcon.setVisibility(VISIBLE);
834             mOverlayContainer.setVisibility(VISIBLE);
835             if (animate) {
836                 startFinishDozeAnimation();
837             }
838         }
839     }
840 
dozeTimeTick()841     public void dozeTimeTick() {
842         if (mDarkAmount == 1) {
843             // Move indication every minute to avoid burn-in
844             final int dozeTranslation = mIndicationBottomMargin - mIndicationBottomMarginAmbient;
845             mIndicationArea.setTranslationY(dozeTranslation + (float) Math.random() * 5);
846         }
847     }
848 
setBurnInXOffset(int burnInXOffset)849     public void setBurnInXOffset(int burnInXOffset) {
850         if (mBurnInXOffset == burnInXOffset) {
851             return;
852         }
853         mBurnInXOffset = burnInXOffset;
854         mIndicationArea.setTranslationX(burnInXOffset);
855     }
856 
857     private class DefaultLeftButton implements IntentButton {
858 
859         private IconState mIconState = new IconState();
860 
861         @Override
getIcon()862         public IconState getIcon() {
863             mLeftIsVoiceAssist = canLaunchVoiceAssist();
864             final boolean showAffordance =
865                     getResources().getBoolean(R.bool.config_keyguardShowLeftAffordance);
866             if (mLeftIsVoiceAssist) {
867                 mIconState.isVisible = mUserSetupComplete && showAffordance;
868                 if (mLeftAssistIcon == null) {
869                     mIconState.drawable = mContext.getDrawable(R.drawable.ic_mic_26dp);
870                 } else {
871                     mIconState.drawable = mLeftAssistIcon;
872                 }
873                 mIconState.contentDescription = mContext.getString(
874                         R.string.accessibility_voice_assist_button);
875             } else {
876                 mIconState.isVisible = mUserSetupComplete && showAffordance && isPhoneVisible();
877                 mIconState.drawable = mContext.getDrawable(R.drawable.ic_phone_24dp);
878                 mIconState.contentDescription = mContext.getString(
879                         R.string.accessibility_phone_button);
880             }
881             return mIconState;
882         }
883 
884         @Override
getIntent()885         public Intent getIntent() {
886             return PHONE_INTENT;
887         }
888     }
889 
890     private class DefaultRightButton implements IntentButton {
891 
892         private IconState mIconState = new IconState();
893 
894         @Override
getIcon()895         public IconState getIcon() {
896             ResolveInfo resolved = resolveCameraIntent();
897             boolean isCameraDisabled = (mStatusBar != null) && !mStatusBar.isCameraAllowedByAdmin();
898             mIconState.isVisible = !isCameraDisabled && resolved != null
899                     && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance)
900                     && mUserSetupComplete;
901             mIconState.drawable = mContext.getDrawable(R.drawable.ic_camera_alt_24dp);
902             mIconState.contentDescription =
903                     mContext.getString(R.string.accessibility_camera_button);
904             return mIconState;
905         }
906 
907         @Override
getIntent()908         public Intent getIntent() {
909             KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
910             boolean canSkipBouncer = updateMonitor.getUserCanSkipBouncer(
911                     KeyguardUpdateMonitor.getCurrentUser());
912             boolean secure = mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser());
913             return (secure && !canSkipBouncer) ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
914         }
915     }
916 
917     @Override
onApplyWindowInsets(WindowInsets insets)918     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
919         int bottom = insets.getDisplayCutout() != null
920                 ? insets.getDisplayCutout().getSafeInsetBottom() : 0;
921         if (isPaddingRelative()) {
922             setPaddingRelative(getPaddingStart(), getPaddingTop(), getPaddingEnd(), bottom);
923         } else {
924             setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), bottom);
925         }
926         return insets;
927     }
928 }
929