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