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