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