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