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