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