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