• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
23 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
24 import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON;
25 import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK;
26 import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON;
27 import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_UNLOCK;
28 import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
29 import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE;
30 
31 import android.app.ActivityManager;
32 import android.app.ActivityOptions;
33 import android.app.ActivityTaskManager;
34 import android.app.admin.DevicePolicyManager;
35 import android.content.BroadcastReceiver;
36 import android.content.ComponentName;
37 import android.content.Context;
38 import android.content.Intent;
39 import android.content.IntentFilter;
40 import android.content.ServiceConnection;
41 import android.content.pm.ActivityInfo;
42 import android.content.pm.PackageManager;
43 import android.content.pm.ResolveInfo;
44 import android.content.res.ColorStateList;
45 import android.content.res.Configuration;
46 import android.graphics.drawable.Drawable;
47 import android.os.AsyncTask;
48 import android.os.Bundle;
49 import android.os.IBinder;
50 import android.os.Message;
51 import android.os.Messenger;
52 import android.os.RemoteException;
53 import android.os.UserHandle;
54 import android.provider.MediaStore;
55 import android.service.media.CameraPrewarmService;
56 import android.service.quickaccesswallet.GetWalletCardsError;
57 import android.service.quickaccesswallet.GetWalletCardsResponse;
58 import android.service.quickaccesswallet.QuickAccessWalletClient;
59 import android.telecom.TelecomManager;
60 import android.text.TextUtils;
61 import android.util.AttributeSet;
62 import android.util.Log;
63 import android.util.TypedValue;
64 import android.view.View;
65 import android.view.ViewGroup;
66 import android.view.WindowInsets;
67 import android.view.WindowManager;
68 import android.view.accessibility.AccessibilityNodeInfo;
69 import android.widget.FrameLayout;
70 import android.widget.ImageView;
71 import android.widget.TextView;
72 
73 import androidx.annotation.NonNull;
74 
75 import com.android.internal.annotations.VisibleForTesting;
76 import com.android.internal.widget.LockPatternUtils;
77 import com.android.keyguard.KeyguardUpdateMonitor;
78 import com.android.keyguard.KeyguardUpdateMonitorCallback;
79 import com.android.settingslib.Utils;
80 import com.android.systemui.ActivityIntentHelper;
81 import com.android.systemui.Dependency;
82 import com.android.systemui.R;
83 import com.android.systemui.animation.Interpolators;
84 import com.android.systemui.assist.AssistManager;
85 import com.android.systemui.camera.CameraIntents;
86 import com.android.systemui.controls.ControlsServiceInfo;
87 import com.android.systemui.controls.dagger.ControlsComponent;
88 import com.android.systemui.controls.management.ControlsListingController;
89 import com.android.systemui.controls.ui.ControlsActivity;
90 import com.android.systemui.controls.ui.ControlsUiController;
91 import com.android.systemui.plugins.ActivityStarter;
92 import com.android.systemui.plugins.FalsingManager;
93 import com.android.systemui.plugins.IntentButtonProvider;
94 import com.android.systemui.plugins.IntentButtonProvider.IntentButton;
95 import com.android.systemui.plugins.IntentButtonProvider.IntentButton.IconState;
96 import com.android.systemui.statusbar.KeyguardAffordanceView;
97 import com.android.systemui.statusbar.policy.AccessibilityController;
98 import com.android.systemui.statusbar.policy.ExtensionController;
99 import com.android.systemui.statusbar.policy.ExtensionController.Extension;
100 import com.android.systemui.statusbar.policy.FlashlightController;
101 import com.android.systemui.statusbar.policy.KeyguardStateController;
102 import com.android.systemui.statusbar.policy.PreviewInflater;
103 import com.android.systemui.tuner.LockscreenFragment.LockButtonFactory;
104 import com.android.systemui.tuner.TunerService;
105 import com.android.systemui.wallet.controller.QuickAccessWalletController;
106 import com.android.systemui.wallet.ui.WalletActivity;
107 
108 import java.util.List;
109 
110 /**
111  * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
112  * text.
113  */
114 public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener,
115         KeyguardStateController.Callback,
116         AccessibilityController.AccessibilityStateChangedCallback {
117 
118     final static String TAG = "StatusBar/KeyguardBottomAreaView";
119 
120     public static final String CAMERA_LAUNCH_SOURCE_AFFORDANCE = "lockscreen_affordance";
121     public static final String CAMERA_LAUNCH_SOURCE_WIGGLE = "wiggle_gesture";
122     public static final String CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = "power_double_tap";
123     public static final String CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = "lift_to_launch_ml";
124 
125     public static final String EXTRA_CAMERA_LAUNCH_SOURCE
126             = "com.android.systemui.camera_launch_source";
127 
128     private static final String LEFT_BUTTON_PLUGIN
129             = "com.android.systemui.action.PLUGIN_LOCKSCREEN_LEFT_BUTTON";
130     private static final String RIGHT_BUTTON_PLUGIN
131             = "com.android.systemui.action.PLUGIN_LOCKSCREEN_RIGHT_BUTTON";
132 
133     private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);
134     private static final int DOZE_ANIMATION_STAGGER_DELAY = 48;
135     private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250;
136 
137     // TODO(b/179494051): May no longer be needed
138     private final boolean mShowLeftAffordance;
139     private final boolean mShowCameraAffordance;
140 
141     private KeyguardAffordanceView mRightAffordanceView;
142     private KeyguardAffordanceView mLeftAffordanceView;
143 
144     private ImageView mWalletButton;
145     private ImageView mControlsButton;
146     private boolean mHasCard = false;
147     private WalletCardRetriever mCardRetriever = new WalletCardRetriever();
148     private QuickAccessWalletController mQuickAccessWalletController;
149     private ControlsComponent mControlsComponent;
150     private boolean mControlServicesAvailable = false;
151 
152     private ViewGroup mIndicationArea;
153     private TextView mIndicationText;
154     private TextView mIndicationTextBottom;
155     private ViewGroup mPreviewContainer;
156     private ViewGroup mOverlayContainer;
157 
158     private View mLeftPreview;
159     private View mCameraPreview;
160 
161     private ActivityStarter mActivityStarter;
162     private KeyguardStateController mKeyguardStateController;
163     private FlashlightController mFlashlightController;
164     private PreviewInflater mPreviewInflater;
165     private AccessibilityController mAccessibilityController;
166     private StatusBar mStatusBar;
167     private KeyguardAffordanceHelper mAffordanceHelper;
168     private FalsingManager mFalsingManager;
169     private boolean mUserSetupComplete;
170     private boolean mPrewarmBound;
171     private Messenger mPrewarmMessenger;
172     private final ServiceConnection mPrewarmConnection = new ServiceConnection() {
173 
174         @Override
175         public void onServiceConnected(ComponentName name, IBinder service) {
176             mPrewarmMessenger = new Messenger(service);
177         }
178 
179         @Override
180         public void onServiceDisconnected(ComponentName name) {
181             mPrewarmMessenger = null;
182         }
183     };
184 
185     private boolean mLeftIsVoiceAssist;
186     private Drawable mLeftAssistIcon;
187 
188     private IntentButton mRightButton = new DefaultRightButton();
189     private Extension<IntentButton> mRightExtension;
190     private String mRightButtonStr;
191     private IntentButton mLeftButton = new DefaultLeftButton();
192     private Extension<IntentButton> mLeftExtension;
193     private String mLeftButtonStr;
194     private boolean mDozing;
195     private int mIndicationBottomMargin;
196     private int mIndicationPadding;
197     private float mDarkAmount;
198     private int mBurnInXOffset;
199     private int mBurnInYOffset;
200     private ActivityIntentHelper mActivityIntentHelper;
201     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
202 
203     private ControlsListingController.ControlsListingCallback mListingCallback =
204             new ControlsListingController.ControlsListingCallback() {
205                 public void onServicesUpdated(List<ControlsServiceInfo> serviceInfos) {
206                     boolean available = !serviceInfos.isEmpty();
207 
208                     if (available != mControlServicesAvailable) {
209                         mControlServicesAvailable = available;
210                         updateControlsVisibility();
211                         updateAffordanceColors();
212                     }
213                 }
214             };
215 
KeyguardBottomAreaView(Context context)216     public KeyguardBottomAreaView(Context context) {
217         this(context, null);
218     }
219 
KeyguardBottomAreaView(Context context, AttributeSet attrs)220     public KeyguardBottomAreaView(Context context, AttributeSet attrs) {
221         this(context, attrs, 0);
222     }
223 
KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr)224     public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr) {
225         this(context, attrs, defStyleAttr, 0);
226     }
227 
KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)228     public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr,
229             int defStyleRes) {
230         super(context, attrs, defStyleAttr, defStyleRes);
231         mShowLeftAffordance = getResources().getBoolean(R.bool.config_keyguardShowLeftAffordance);
232         mShowCameraAffordance = getResources()
233                 .getBoolean(R.bool.config_keyguardShowCameraAffordance);
234     }
235 
236     private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
237         @Override
238         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
239             super.onInitializeAccessibilityNodeInfo(host, info);
240             String label = null;
241             if (host == mRightAffordanceView) {
242                 label = getResources().getString(R.string.camera_label);
243             } else if (host == mLeftAffordanceView) {
244                 if (mLeftIsVoiceAssist) {
245                     label = getResources().getString(R.string.voice_assist_label);
246                 } else {
247                     label = getResources().getString(R.string.phone_label);
248                 }
249             }
250             info.addAction(new AccessibilityAction(ACTION_CLICK, label));
251         }
252 
253         @Override
254         public boolean performAccessibilityAction(View host, int action, Bundle args) {
255             if (action == ACTION_CLICK) {
256                 if (host == mRightAffordanceView) {
257                     launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
258                     return true;
259                 } else if (host == mLeftAffordanceView) {
260                     launchLeftAffordance();
261                     return true;
262                 }
263             }
264             return super.performAccessibilityAction(host, action, args);
265         }
266     };
267 
initFrom(KeyguardBottomAreaView oldBottomArea)268     public void initFrom(KeyguardBottomAreaView oldBottomArea) {
269         setStatusBar(oldBottomArea.mStatusBar);
270     }
271 
272     @Override
onFinishInflate()273     protected void onFinishInflate() {
274         super.onFinishInflate();
275         mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext),
276                 new ActivityIntentHelper(mContext));
277         mOverlayContainer = findViewById(R.id.overlay_container);
278         mRightAffordanceView = findViewById(R.id.camera_button);
279         mLeftAffordanceView = findViewById(R.id.left_button);
280         mWalletButton = findViewById(R.id.wallet_button);
281         mControlsButton = findViewById(R.id.controls_button);
282         mIndicationArea = findViewById(R.id.keyguard_indication_area);
283         mIndicationText = findViewById(R.id.keyguard_indication_text);
284         mIndicationTextBottom = findViewById(R.id.keyguard_indication_text_bottom);
285         mIndicationBottomMargin = getResources().getDimensionPixelSize(
286                 R.dimen.keyguard_indication_margin_bottom);
287         mBurnInYOffset = getResources().getDimensionPixelSize(
288                 R.dimen.default_burn_in_prevention_offset);
289         updateCameraVisibility();
290         mKeyguardStateController = Dependency.get(KeyguardStateController.class);
291         mKeyguardStateController.addCallback(this);
292         setClipChildren(false);
293         setClipToPadding(false);
294         mRightAffordanceView.setOnClickListener(this);
295         mLeftAffordanceView.setOnClickListener(this);
296         initAccessibility();
297         mActivityStarter = Dependency.get(ActivityStarter.class);
298         mFlashlightController = Dependency.get(FlashlightController.class);
299         mAccessibilityController = Dependency.get(AccessibilityController.class);
300         mActivityIntentHelper = new ActivityIntentHelper(getContext());
301 
302         mIndicationPadding = getResources().getDimensionPixelSize(
303                 R.dimen.keyguard_indication_area_padding);
304         updateWalletVisibility();
305         updateControlsVisibility();
306     }
307 
308     /**
309      * Set the container where the previews are rendered.
310      */
setPreviewContainer(ViewGroup previewContainer)311     public void setPreviewContainer(ViewGroup previewContainer) {
312         mPreviewContainer = previewContainer;
313         inflateCameraPreview();
314         updateLeftAffordance();
315     }
316 
317     @Override
onAttachedToWindow()318     protected void onAttachedToWindow() {
319         super.onAttachedToWindow();
320         mAccessibilityController.addStateChangedCallback(this);
321         mRightExtension = Dependency.get(ExtensionController.class).newExtension(IntentButton.class)
322                 .withPlugin(IntentButtonProvider.class, RIGHT_BUTTON_PLUGIN,
323                         p -> p.getIntentButton())
324                 .withTunerFactory(new LockButtonFactory(mContext, LOCKSCREEN_RIGHT_BUTTON))
325                 .withDefault(() -> new DefaultRightButton())
326                 .withCallback(button -> setRightButton(button))
327                 .build();
328         mLeftExtension = Dependency.get(ExtensionController.class).newExtension(IntentButton.class)
329                 .withPlugin(IntentButtonProvider.class, LEFT_BUTTON_PLUGIN,
330                         p -> p.getIntentButton())
331                 .withTunerFactory(new LockButtonFactory(mContext, LOCKSCREEN_LEFT_BUTTON))
332                 .withDefault(() -> new DefaultLeftButton())
333                 .withCallback(button -> setLeftButton(button))
334                 .build();
335         final IntentFilter filter = new IntentFilter();
336         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
337         getContext().registerReceiverAsUser(mDevicePolicyReceiver,
338                 UserHandle.ALL, filter, null, null);
339         mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
340         mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
341         mKeyguardStateController.addCallback(this);
342     }
343 
344     @Override
onDetachedFromWindow()345     protected void onDetachedFromWindow() {
346         super.onDetachedFromWindow();
347         mKeyguardStateController.removeCallback(this);
348         mAccessibilityController.removeStateChangedCallback(this);
349         mRightExtension.destroy();
350         mLeftExtension.destroy();
351         getContext().unregisterReceiver(mDevicePolicyReceiver);
352         mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
353 
354         if (mQuickAccessWalletController != null) {
355             mQuickAccessWalletController.unregisterWalletChangeObservers(
356                     WALLET_PREFERENCE_CHANGE, DEFAULT_PAYMENT_APP_CHANGE);
357         }
358 
359         if (mControlsComponent != null) {
360             mControlsComponent.getControlsListingController().ifPresent(
361                     c -> c.removeCallback(mListingCallback));
362         }
363     }
364 
initAccessibility()365     private void initAccessibility() {
366         mLeftAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
367         mRightAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
368     }
369 
370     @Override
onConfigurationChanged(Configuration newConfig)371     protected void onConfigurationChanged(Configuration newConfig) {
372         super.onConfigurationChanged(newConfig);
373         mIndicationBottomMargin = getResources().getDimensionPixelSize(
374                 R.dimen.keyguard_indication_margin_bottom);
375         mBurnInYOffset = getResources().getDimensionPixelSize(
376                 R.dimen.default_burn_in_prevention_offset);
377         MarginLayoutParams mlp = (MarginLayoutParams) mIndicationArea.getLayoutParams();
378         if (mlp.bottomMargin != mIndicationBottomMargin) {
379             mlp.bottomMargin = mIndicationBottomMargin;
380             mIndicationArea.setLayoutParams(mlp);
381         }
382 
383         // Respect font size setting.
384         mIndicationTextBottom.setTextSize(TypedValue.COMPLEX_UNIT_PX,
385                 getResources().getDimensionPixelSize(
386                         com.android.internal.R.dimen.text_size_small_material));
387         mIndicationText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
388                 getResources().getDimensionPixelSize(
389                         com.android.internal.R.dimen.text_size_small_material));
390 
391         ViewGroup.LayoutParams lp = mRightAffordanceView.getLayoutParams();
392         lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
393         lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
394         mRightAffordanceView.setLayoutParams(lp);
395         updateRightAffordanceIcon();
396 
397         lp = mLeftAffordanceView.getLayoutParams();
398         lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
399         lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
400         mLeftAffordanceView.setLayoutParams(lp);
401         updateLeftAffordanceIcon();
402 
403         lp = mWalletButton.getLayoutParams();
404         lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width);
405         lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height);
406         mWalletButton.setLayoutParams(lp);
407 
408         lp = mControlsButton.getLayoutParams();
409         lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width);
410         lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height);
411         mControlsButton.setLayoutParams(lp);
412 
413         mIndicationPadding = getResources().getDimensionPixelSize(
414                 R.dimen.keyguard_indication_area_padding);
415 
416         updateWalletVisibility();
417         updateAffordanceColors();
418     }
419 
updateRightAffordanceIcon()420     private void updateRightAffordanceIcon() {
421         IconState state = mRightButton.getIcon();
422         mRightAffordanceView.setVisibility(!mDozing && state.isVisible ? View.VISIBLE : View.GONE);
423         if (state.drawable != mRightAffordanceView.getDrawable()
424                 || state.tint != mRightAffordanceView.shouldTint()) {
425             mRightAffordanceView.setImageDrawable(state.drawable, state.tint);
426         }
427         mRightAffordanceView.setContentDescription(state.contentDescription);
428     }
429 
setStatusBar(StatusBar statusBar)430     public void setStatusBar(StatusBar statusBar) {
431         mStatusBar = statusBar;
432         updateCameraVisibility(); // in case onFinishInflate() was called too early
433     }
434 
setAffordanceHelper(KeyguardAffordanceHelper affordanceHelper)435     public void setAffordanceHelper(KeyguardAffordanceHelper affordanceHelper) {
436         mAffordanceHelper = affordanceHelper;
437     }
438 
setUserSetupComplete(boolean userSetupComplete)439     public void setUserSetupComplete(boolean userSetupComplete) {
440         mUserSetupComplete = userSetupComplete;
441         updateCameraVisibility();
442         updateLeftAffordanceIcon();
443     }
444 
getCameraIntent()445     private Intent getCameraIntent() {
446         return mRightButton.getIntent();
447     }
448 
449     /**
450      * Resolves the intent to launch the camera application.
451      */
resolveCameraIntent()452     public ResolveInfo resolveCameraIntent() {
453         return mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(),
454                 PackageManager.MATCH_DEFAULT_ONLY,
455                 KeyguardUpdateMonitor.getCurrentUser());
456     }
457 
updateCameraVisibility()458     private void updateCameraVisibility() {
459         if (mRightAffordanceView == null) {
460             // Things are not set up yet; reply hazy, ask again later
461             return;
462         }
463         mRightAffordanceView.setVisibility(!mDozing && mShowCameraAffordance
464                 && mRightButton.getIcon().isVisible ? View.VISIBLE : View.GONE);
465     }
466 
467     /**
468      * Set an alternate icon for the left assist affordance (replace the mic icon)
469      */
setLeftAssistIcon(Drawable drawable)470     public void setLeftAssistIcon(Drawable drawable) {
471         mLeftAssistIcon = drawable;
472         updateLeftAffordanceIcon();
473     }
474 
updateLeftAffordanceIcon()475     private void updateLeftAffordanceIcon() {
476         if (!mShowLeftAffordance || mDozing) {
477             mLeftAffordanceView.setVisibility(GONE);
478             return;
479         }
480 
481         IconState state = mLeftButton.getIcon();
482         mLeftAffordanceView.setVisibility(state.isVisible ? View.VISIBLE : View.GONE);
483         if (state.drawable != mLeftAffordanceView.getDrawable()
484                 || state.tint != mLeftAffordanceView.shouldTint()) {
485             mLeftAffordanceView.setImageDrawable(state.drawable, state.tint);
486         }
487         mLeftAffordanceView.setContentDescription(state.contentDescription);
488     }
489 
updateWalletVisibility()490     private void updateWalletVisibility() {
491         if (mDozing
492                 || mQuickAccessWalletController == null
493                 || !mQuickAccessWalletController.isWalletEnabled()
494                 || !mHasCard) {
495             mWalletButton.setVisibility(GONE);
496 
497             if (mControlsButton.getVisibility() == GONE) {
498                 mIndicationArea.setPadding(0, 0, 0, 0);
499             }
500         } else {
501             mWalletButton.setVisibility(VISIBLE);
502             mWalletButton.setOnClickListener(this::onWalletClick);
503             mIndicationArea.setPadding(mIndicationPadding, 0, mIndicationPadding, 0);
504         }
505     }
506 
updateControlsVisibility()507     private void updateControlsVisibility() {
508         if (mControlsComponent == null) return;
509 
510         boolean hasFavorites = mControlsComponent.getControlsController()
511                 .map(c -> c.getFavorites().size() > 0)
512                 .orElse(false);
513         if (mDozing
514                 || !hasFavorites
515                 || !mControlServicesAvailable
516                 || mControlsComponent.getVisibility() != AVAILABLE) {
517             mControlsButton.setVisibility(GONE);
518             if (mWalletButton.getVisibility() == GONE) {
519                 mIndicationArea.setPadding(0, 0, 0, 0);
520             }
521         } else {
522             mControlsButton.setVisibility(VISIBLE);
523             mControlsButton.setOnClickListener(this::onControlsClick);
524             mIndicationArea.setPadding(mIndicationPadding, 0, mIndicationPadding, 0);
525         }
526     }
527 
isLeftVoiceAssist()528     public boolean isLeftVoiceAssist() {
529         return mLeftIsVoiceAssist;
530     }
531 
isPhoneVisible()532     private boolean isPhoneVisible() {
533         PackageManager pm = mContext.getPackageManager();
534         return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
535                 && pm.resolveActivity(PHONE_INTENT, 0) != null;
536     }
537 
538     @Override
onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled)539     public void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled) {
540         mRightAffordanceView.setClickable(touchExplorationEnabled);
541         mLeftAffordanceView.setClickable(touchExplorationEnabled);
542         mRightAffordanceView.setFocusable(accessibilityEnabled);
543         mLeftAffordanceView.setFocusable(accessibilityEnabled);
544     }
545 
546     @Override
onClick(View v)547     public void onClick(View v) {
548         if (v == mRightAffordanceView) {
549             launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
550         } else if (v == mLeftAffordanceView) {
551             launchLeftAffordance();
552         }
553     }
554 
bindCameraPrewarmService()555     public void bindCameraPrewarmService() {
556         Intent intent = getCameraIntent();
557         ActivityInfo targetInfo = mActivityIntentHelper.getTargetActivityInfo(intent,
558                 KeyguardUpdateMonitor.getCurrentUser(), true /* onlyDirectBootAware */);
559         if (targetInfo != null && targetInfo.metaData != null) {
560             String clazz = targetInfo.metaData.getString(
561                     MediaStore.META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE);
562             if (clazz != null) {
563                 Intent serviceIntent = new Intent();
564                 serviceIntent.setClassName(targetInfo.packageName, clazz);
565                 serviceIntent.setAction(CameraPrewarmService.ACTION_PREWARM);
566                 try {
567                     if (getContext().bindServiceAsUser(serviceIntent, mPrewarmConnection,
568                             Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
569                             new UserHandle(UserHandle.USER_CURRENT))) {
570                         mPrewarmBound = true;
571                     }
572                 } catch (SecurityException e) {
573                     Log.w(TAG, "Unable to bind to prewarm service package=" + targetInfo.packageName
574                             + " class=" + clazz, e);
575                 }
576             }
577         }
578     }
579 
unbindCameraPrewarmService(boolean launched)580     public void unbindCameraPrewarmService(boolean launched) {
581         if (mPrewarmBound) {
582             if (mPrewarmMessenger != null && launched) {
583                 try {
584                     mPrewarmMessenger.send(Message.obtain(null /* handler */,
585                             CameraPrewarmService.MSG_CAMERA_FIRED));
586                 } catch (RemoteException e) {
587                     Log.w(TAG, "Error sending camera fired message", e);
588                 }
589             }
590             mContext.unbindService(mPrewarmConnection);
591             mPrewarmBound = false;
592         }
593     }
594 
launchCamera(String source)595     public void launchCamera(String source) {
596         final Intent intent = getCameraIntent();
597         intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source);
598         boolean wouldLaunchResolverActivity = mActivityIntentHelper.wouldLaunchResolverActivity(
599                 intent, KeyguardUpdateMonitor.getCurrentUser());
600         if (CameraIntents.isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
601             AsyncTask.execute(new Runnable() {
602                 @Override
603                 public void run() {
604                     int result = ActivityManager.START_CANCELED;
605 
606                     // Normally an activity will set it's requested rotation
607                     // animation on its window. However when launching an activity
608                     // causes the orientation to change this is too late. In these cases
609                     // the default animation is used. This doesn't look good for
610                     // the camera (as it rotates the camera contents out of sync
611                     // with physical reality). So, we ask the WindowManager to
612                     // force the crossfade animation if an orientation change
613                     // happens to occur during the launch.
614                     ActivityOptions o = ActivityOptions.makeBasic();
615                     o.setDisallowEnterPictureInPictureWhileLaunching(true);
616                     o.setRotationAnimationHint(
617                             WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
618                     try {
619                         result = ActivityTaskManager.getService().startActivityAsUser(
620                                 null, getContext().getBasePackageName(),
621                                 getContext().getAttributionTag(), intent,
622                                 intent.resolveTypeIfNeeded(getContext().getContentResolver()),
623                                 null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, o.toBundle(),
624                                 UserHandle.CURRENT.getIdentifier());
625                     } catch (RemoteException e) {
626                         Log.w(TAG, "Unable to start camera activity", e);
627                     }
628                     final boolean launched = isSuccessfulLaunch(result);
629                     post(new Runnable() {
630                         @Override
631                         public void run() {
632                             unbindCameraPrewarmService(launched);
633                         }
634                     });
635                 }
636             });
637         } else {
638             // We need to delay starting the activity because ResolverActivity finishes itself if
639             // launched behind lockscreen.
640             mActivityStarter.startActivity(intent, false /* dismissShade */,
641                     new ActivityStarter.Callback() {
642                         @Override
643                         public void onActivityStarted(int resultCode) {
644                             unbindCameraPrewarmService(isSuccessfulLaunch(resultCode));
645                         }
646                     });
647         }
648     }
649 
setDarkAmount(float darkAmount)650     public void setDarkAmount(float darkAmount) {
651         if (darkAmount == mDarkAmount) {
652             return;
653         }
654         mDarkAmount = darkAmount;
655         dozeTimeTick();
656     }
657 
isSuccessfulLaunch(int result)658     private static boolean isSuccessfulLaunch(int result) {
659         return result == ActivityManager.START_SUCCESS
660                 || result == ActivityManager.START_DELIVERED_TO_TOP
661                 || result == ActivityManager.START_TASK_TO_FRONT;
662     }
663 
launchLeftAffordance()664     public void launchLeftAffordance() {
665         if (mLeftIsVoiceAssist) {
666             launchVoiceAssist();
667         } else {
668             launchPhone();
669         }
670     }
671 
672     @VisibleForTesting
launchVoiceAssist()673     void launchVoiceAssist() {
674         Runnable runnable = new Runnable() {
675             @Override
676             public void run() {
677                 Dependency.get(AssistManager.class).launchVoiceAssistFromKeyguard();
678             }
679         };
680         if (!mKeyguardStateController.canDismissLockScreen()) {
681             Dependency.get(Dependency.BACKGROUND_EXECUTOR).execute(runnable);
682         } else {
683             boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr)
684                     && Dependency.get(TunerService.class).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0;
685             mStatusBar.executeRunnableDismissingKeyguard(runnable, null /* cancelAction */,
686                     dismissShade, false /* afterKeyguardGone */, true /* deferred */);
687         }
688     }
689 
canLaunchVoiceAssist()690     private boolean canLaunchVoiceAssist() {
691         return Dependency.get(AssistManager.class).canVoiceAssistBeLaunchedFromKeyguard();
692     }
693 
launchPhone()694     private void launchPhone() {
695         final TelecomManager tm = TelecomManager.from(mContext);
696         if (tm.isInCall()) {
697             AsyncTask.execute(new Runnable() {
698                 @Override
699                 public void run() {
700                     tm.showInCallScreen(false /* showDialpad */);
701                 }
702             });
703         } else {
704             boolean dismissShade = !TextUtils.isEmpty(mLeftButtonStr)
705                     && Dependency.get(TunerService.class).getValue(LOCKSCREEN_LEFT_UNLOCK, 1) != 0;
706             mActivityStarter.startActivity(mLeftButton.getIntent(), dismissShade);
707         }
708     }
709 
710 
711     @Override
onVisibilityChanged(View changedView, int visibility)712     protected void onVisibilityChanged(View changedView, int visibility) {
713         super.onVisibilityChanged(changedView, visibility);
714         if (changedView == this && visibility == VISIBLE) {
715             updateCameraVisibility();
716         }
717     }
718 
getLeftView()719     public KeyguardAffordanceView getLeftView() {
720         return mLeftAffordanceView;
721     }
722 
getRightView()723     public KeyguardAffordanceView getRightView() {
724         return mRightAffordanceView;
725     }
726 
getLeftPreview()727     public View getLeftPreview() {
728         return mLeftPreview;
729     }
730 
getRightPreview()731     public View getRightPreview() {
732         return mCameraPreview;
733     }
734 
getIndicationArea()735     public View getIndicationArea() {
736         return mIndicationArea;
737     }
738 
739     @Override
hasOverlappingRendering()740     public boolean hasOverlappingRendering() {
741         return false;
742     }
743 
744     @Override
onUnlockedChanged()745     public void onUnlockedChanged() {
746         updateCameraVisibility();
747     }
748 
749     @Override
onKeyguardShowingChanged()750     public void onKeyguardShowingChanged() {
751         if (mKeyguardStateController.isShowing()) {
752             if (mQuickAccessWalletController != null) {
753                 mQuickAccessWalletController.queryWalletCards(mCardRetriever);
754             }
755         }
756     }
757 
inflateCameraPreview()758     private void inflateCameraPreview() {
759         if (mPreviewContainer == null) {
760             return;
761         }
762         View previewBefore = mCameraPreview;
763         boolean visibleBefore = false;
764         if (previewBefore != null) {
765             mPreviewContainer.removeView(previewBefore);
766             visibleBefore = previewBefore.getVisibility() == View.VISIBLE;
767         }
768         mCameraPreview = mPreviewInflater.inflatePreview(getCameraIntent());
769         if (mCameraPreview != null) {
770             mPreviewContainer.addView(mCameraPreview);
771             mCameraPreview.setVisibility(visibleBefore ? View.VISIBLE : View.INVISIBLE);
772         }
773         if (mAffordanceHelper != null) {
774             mAffordanceHelper.updatePreviews();
775         }
776     }
777 
updateLeftPreview()778     private void updateLeftPreview() {
779         if (mPreviewContainer == null) {
780             return;
781         }
782         View previewBefore = mLeftPreview;
783         if (previewBefore != null) {
784             mPreviewContainer.removeView(previewBefore);
785         }
786 
787         if (mLeftIsVoiceAssist) {
788             if (Dependency.get(AssistManager.class).getVoiceInteractorComponentName() != null) {
789                 mLeftPreview = mPreviewInflater.inflatePreviewFromService(
790                         Dependency.get(AssistManager.class).getVoiceInteractorComponentName());
791             }
792         } else {
793             mLeftPreview = mPreviewInflater.inflatePreview(mLeftButton.getIntent());
794         }
795         if (mLeftPreview != null) {
796             mPreviewContainer.addView(mLeftPreview);
797             mLeftPreview.setVisibility(View.INVISIBLE);
798         }
799         if (mAffordanceHelper != null) {
800             mAffordanceHelper.updatePreviews();
801         }
802     }
803 
startFinishDozeAnimation()804     public void startFinishDozeAnimation() {
805         long delay = 0;
806         if (mWalletButton.getVisibility() == View.VISIBLE) {
807             startFinishDozeAnimationElement(mWalletButton, delay);
808         }
809         if (mControlsButton.getVisibility() == View.VISIBLE) {
810             startFinishDozeAnimationElement(mControlsButton, delay);
811         }
812         if (mLeftAffordanceView.getVisibility() == View.VISIBLE) {
813             startFinishDozeAnimationElement(mLeftAffordanceView, delay);
814             delay += DOZE_ANIMATION_STAGGER_DELAY;
815         }
816         if (mRightAffordanceView.getVisibility() == View.VISIBLE) {
817             startFinishDozeAnimationElement(mRightAffordanceView, delay);
818         }
819     }
820 
startFinishDozeAnimationElement(View element, long delay)821     private void startFinishDozeAnimationElement(View element, long delay) {
822         element.setAlpha(0f);
823         element.setTranslationY(element.getHeight() / 2);
824         element.animate()
825                 .alpha(1f)
826                 .translationY(0f)
827                 .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
828                 .setStartDelay(delay)
829                 .setDuration(DOZE_ANIMATION_ELEMENT_DURATION);
830     }
831 
832     private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() {
833         @Override
834         public void onReceive(Context context, Intent intent) {
835             post(new Runnable() {
836                 @Override
837                 public void run() {
838                     updateCameraVisibility();
839                 }
840             });
841         }
842     };
843 
844     private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
845             new KeyguardUpdateMonitorCallback() {
846                 @Override
847                 public void onUserSwitchComplete(int userId) {
848                     updateCameraVisibility();
849                 }
850 
851                 @Override
852                 public void onUserUnlocked() {
853                     inflateCameraPreview();
854                     updateCameraVisibility();
855                     updateLeftAffordance();
856                 }
857             };
858 
updateLeftAffordance()859     public void updateLeftAffordance() {
860         updateLeftAffordanceIcon();
861         updateLeftPreview();
862     }
863 
setRightButton(IntentButton button)864     private void setRightButton(IntentButton button) {
865         mRightButton = button;
866         updateRightAffordanceIcon();
867         updateCameraVisibility();
868         inflateCameraPreview();
869     }
870 
setLeftButton(IntentButton button)871     private void setLeftButton(IntentButton button) {
872         mLeftButton = button;
873         if (!(mLeftButton instanceof DefaultLeftButton)) {
874             mLeftIsVoiceAssist = false;
875         }
876         updateLeftAffordance();
877     }
878 
setDozing(boolean dozing, boolean animate)879     public void setDozing(boolean dozing, boolean animate) {
880         mDozing = dozing;
881 
882         updateCameraVisibility();
883         updateLeftAffordanceIcon();
884         updateWalletVisibility();
885         updateControlsVisibility();
886 
887         if (dozing) {
888             mOverlayContainer.setVisibility(INVISIBLE);
889         } else {
890             mOverlayContainer.setVisibility(VISIBLE);
891             if (animate) {
892                 startFinishDozeAnimation();
893             }
894         }
895     }
896 
dozeTimeTick()897     public void dozeTimeTick() {
898         int burnInYOffset = getBurnInOffset(mBurnInYOffset * 2, false /* xAxis */)
899                 - mBurnInYOffset;
900         mIndicationArea.setTranslationY(burnInYOffset * mDarkAmount);
901     }
902 
setAntiBurnInOffsetX(int burnInXOffset)903     public void setAntiBurnInOffsetX(int burnInXOffset) {
904         if (mBurnInXOffset == burnInXOffset) {
905             return;
906         }
907         mBurnInXOffset = burnInXOffset;
908         mIndicationArea.setTranslationX(burnInXOffset);
909     }
910 
911     /**
912      * Sets the alpha of the indication areas and affordances, excluding the lock icon.
913      */
setAffordanceAlpha(float alpha)914     public void setAffordanceAlpha(float alpha) {
915         mLeftAffordanceView.setAlpha(alpha);
916         mRightAffordanceView.setAlpha(alpha);
917         mIndicationArea.setAlpha(alpha);
918         mWalletButton.setAlpha(alpha);
919         mControlsButton.setAlpha(alpha);
920     }
921 
922     private class DefaultLeftButton implements IntentButton {
923 
924         private IconState mIconState = new IconState();
925 
926         @Override
getIcon()927         public IconState getIcon() {
928             mLeftIsVoiceAssist = canLaunchVoiceAssist();
929             if (mLeftIsVoiceAssist) {
930                 mIconState.isVisible = mUserSetupComplete && mShowLeftAffordance;
931                 if (mLeftAssistIcon == null) {
932                     mIconState.drawable = mContext.getDrawable(R.drawable.ic_mic_26dp);
933                 } else {
934                     mIconState.drawable = mLeftAssistIcon;
935                 }
936                 mIconState.contentDescription = mContext.getString(
937                         R.string.accessibility_voice_assist_button);
938             } else {
939                 mIconState.isVisible = mUserSetupComplete && mShowLeftAffordance
940                         && isPhoneVisible();
941                 mIconState.drawable = mContext.getDrawable(
942                         com.android.internal.R.drawable.ic_phone);
943                 mIconState.contentDescription = mContext.getString(
944                         R.string.accessibility_phone_button);
945             }
946             return mIconState;
947         }
948 
949         @Override
getIntent()950         public Intent getIntent() {
951             return PHONE_INTENT;
952         }
953     }
954 
955     private class DefaultRightButton implements IntentButton {
956 
957         private IconState mIconState = new IconState();
958 
959         @Override
getIcon()960         public IconState getIcon() {
961             boolean isCameraDisabled = (mStatusBar != null) && !mStatusBar.isCameraAllowedByAdmin();
962             mIconState.isVisible = !isCameraDisabled
963                     && mShowCameraAffordance
964                     && mUserSetupComplete
965                     && resolveCameraIntent() != null;
966             mIconState.drawable = mContext.getDrawable(R.drawable.ic_camera_alt_24dp);
967             mIconState.contentDescription =
968                     mContext.getString(R.string.accessibility_camera_button);
969             return mIconState;
970         }
971 
972         @Override
getIntent()973         public Intent getIntent() {
974             boolean canDismissLs = mKeyguardStateController.canDismissLockScreen();
975             boolean secure = mKeyguardStateController.isMethodSecure();
976             if (secure && !canDismissLs) {
977                 return CameraIntents.getSecureCameraIntent(getContext());
978             } else {
979                 return CameraIntents.getInsecureCameraIntent(getContext());
980             }
981         }
982     }
983 
984     @Override
onApplyWindowInsets(WindowInsets insets)985     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
986         int bottom = insets.getDisplayCutout() != null
987                 ? insets.getDisplayCutout().getSafeInsetBottom() : 0;
988         if (isPaddingRelative()) {
989             setPaddingRelative(getPaddingStart(), getPaddingTop(), getPaddingEnd(), bottom);
990         } else {
991             setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), bottom);
992         }
993         return insets;
994     }
995 
996     /** Set the falsing manager */
setFalsingManager(FalsingManager falsingManager)997     public void setFalsingManager(FalsingManager falsingManager) {
998         mFalsingManager = falsingManager;
999     }
1000 
1001     /**
1002      * Initialize the wallet feature, only enabling if the feature is enabled within the platform.
1003      */
initWallet( QuickAccessWalletController controller)1004     public void initWallet(
1005             QuickAccessWalletController controller) {
1006         mQuickAccessWalletController = controller;
1007         mQuickAccessWalletController.setupWalletChangeObservers(
1008                 mCardRetriever, WALLET_PREFERENCE_CHANGE, DEFAULT_PAYMENT_APP_CHANGE);
1009         mQuickAccessWalletController.updateWalletPreference();
1010         mQuickAccessWalletController.queryWalletCards(mCardRetriever);
1011 
1012         updateWalletVisibility();
1013         updateAffordanceColors();
1014     }
1015 
updateAffordanceColors()1016     private void updateAffordanceColors() {
1017         int iconColor = Utils.getColorAttrDefaultColor(
1018                 mContext,
1019                 com.android.internal.R.attr.textColorPrimary);
1020         mWalletButton.getDrawable().setTint(iconColor);
1021         mControlsButton.getDrawable().setTint(iconColor);
1022 
1023         ColorStateList bgColor = Utils.getColorAttr(
1024                 mContext,
1025                 com.android.internal.R.attr.colorSurface);
1026         mWalletButton.setBackgroundTintList(bgColor);
1027         mControlsButton.setBackgroundTintList(bgColor);
1028     }
1029 
1030     /**
1031       * Initialize controls via the ControlsComponent
1032       */
initControls(ControlsComponent controlsComponent)1033     public void initControls(ControlsComponent controlsComponent) {
1034         mControlsComponent = controlsComponent;
1035         mControlsComponent.getControlsListingController().ifPresent(
1036                 c -> c.addCallback(mListingCallback));
1037 
1038         updateAffordanceColors();
1039     }
1040 
onWalletClick(View v)1041     private void onWalletClick(View v) {
1042         // More coming here; need to inform the user about how to proceed
1043         if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
1044             return;
1045         }
1046 
1047         if (mHasCard) {
1048             Intent intent = new Intent(mContext, WalletActivity.class)
1049                     .setAction(Intent.ACTION_VIEW)
1050                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
1051             mContext.startActivity(intent);
1052         } else {
1053             if (mQuickAccessWalletController.getWalletClient().createWalletIntent() == null) {
1054                 Log.w(TAG, "Could not get intent of the wallet app.");
1055                 return;
1056             }
1057             mActivityStarter.postStartActivityDismissingKeyguard(
1058                     mQuickAccessWalletController.getWalletClient().createWalletIntent(),
1059                     /* delay= */ 0);
1060         }
1061     }
1062 
onControlsClick(View v)1063     private void onControlsClick(View v) {
1064         if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
1065             return;
1066         }
1067 
1068         Intent intent = new Intent(mContext, ControlsActivity.class)
1069                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
1070                 .putExtra(ControlsUiController.EXTRA_ANIMATE, true);
1071 
1072         if (mControlsComponent.getVisibility() == AVAILABLE) {
1073             mContext.startActivity(intent);
1074         } else {
1075             mActivityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */);
1076         }
1077     }
1078 
1079     private class WalletCardRetriever implements
1080             QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
1081 
1082         @Override
onWalletCardsRetrieved(@onNull GetWalletCardsResponse response)1083         public void onWalletCardsRetrieved(@NonNull GetWalletCardsResponse response) {
1084             mHasCard = !response.getWalletCards().isEmpty();
1085             Drawable tileIcon = mQuickAccessWalletController.getWalletClient().getTileIcon();
1086             if (tileIcon != null) {
1087                 mWalletButton.setImageDrawable(tileIcon);
1088             }
1089             updateWalletVisibility();
1090             updateAffordanceColors();
1091         }
1092 
1093         @Override
onWalletCardRetrievalError(@onNull GetWalletCardsError error)1094         public void onWalletCardRetrievalError(@NonNull GetWalletCardsError error) {
1095             mHasCard = false;
1096             updateWalletVisibility();
1097             updateAffordanceColors();
1098         }
1099     }
1100 }
1101