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 android.app.ActivityManagerNative; 20 import android.app.admin.DevicePolicyManager; 21 import android.content.BroadcastReceiver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.content.pm.PackageManager; 26 import android.content.pm.ResolveInfo; 27 import android.content.res.Configuration; 28 import android.graphics.drawable.Drawable; 29 import android.graphics.drawable.InsetDrawable; 30 import android.os.AsyncTask; 31 import android.os.Bundle; 32 import android.os.RemoteException; 33 import android.os.UserHandle; 34 import android.provider.MediaStore; 35 import android.telecom.TelecomManager; 36 import android.util.AttributeSet; 37 import android.util.Log; 38 import android.util.TypedValue; 39 import android.view.View; 40 import android.view.ViewGroup; 41 import android.view.accessibility.AccessibilityNodeInfo; 42 import android.view.animation.AnimationUtils; 43 import android.view.animation.Interpolator; 44 import android.widget.FrameLayout; 45 import android.widget.TextView; 46 47 import com.android.internal.widget.LockPatternUtils; 48 import com.android.keyguard.KeyguardUpdateMonitor; 49 import com.android.keyguard.KeyguardUpdateMonitorCallback; 50 import com.android.systemui.EventLogConstants; 51 import com.android.systemui.EventLogTags; 52 import com.android.systemui.R; 53 import com.android.systemui.statusbar.CommandQueue; 54 import com.android.systemui.statusbar.KeyguardAffordanceView; 55 import com.android.systemui.statusbar.KeyguardIndicationController; 56 import com.android.systemui.statusbar.policy.AccessibilityController; 57 import com.android.systemui.statusbar.policy.FlashlightController; 58 import com.android.systemui.statusbar.policy.PreviewInflater; 59 60 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; 61 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 62 63 /** 64 * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status 65 * text. 66 */ 67 public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener, 68 UnlockMethodCache.OnUnlockMethodChangedListener, 69 AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener { 70 71 final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView"; 72 73 private static final Intent SECURE_CAMERA_INTENT = 74 new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE) 75 .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 76 private static final Intent INSECURE_CAMERA_INTENT = 77 new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); 78 private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL); 79 private static final int DOZE_ANIMATION_STAGGER_DELAY = 48; 80 private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250; 81 82 private KeyguardAffordanceView mCameraImageView; 83 private KeyguardAffordanceView mPhoneImageView; 84 private KeyguardAffordanceView mLockIcon; 85 private TextView mIndicationText; 86 private ViewGroup mPreviewContainer; 87 88 private View mPhonePreview; 89 private View mCameraPreview; 90 91 private ActivityStarter mActivityStarter; 92 private UnlockMethodCache mUnlockMethodCache; 93 private LockPatternUtils mLockPatternUtils; 94 private FlashlightController mFlashlightController; 95 private PreviewInflater mPreviewInflater; 96 private KeyguardIndicationController mIndicationController; 97 private AccessibilityController mAccessibilityController; 98 private PhoneStatusBar mPhoneStatusBar; 99 100 private final TrustDrawable mTrustDrawable; 101 private final Interpolator mLinearOutSlowInInterpolator; 102 private int mLastUnlockIconRes = 0; 103 KeyguardBottomAreaView(Context context)104 public KeyguardBottomAreaView(Context context) { 105 this(context, null); 106 } 107 KeyguardBottomAreaView(Context context, AttributeSet attrs)108 public KeyguardBottomAreaView(Context context, AttributeSet attrs) { 109 this(context, attrs, 0); 110 } 111 KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr)112 public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr) { 113 this(context, attrs, defStyleAttr, 0); 114 } 115 KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)116 public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr, 117 int defStyleRes) { 118 super(context, attrs, defStyleAttr, defStyleRes); 119 mTrustDrawable = new TrustDrawable(mContext); 120 mLinearOutSlowInInterpolator = 121 AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in); 122 } 123 124 private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() { 125 @Override 126 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 127 super.onInitializeAccessibilityNodeInfo(host, info); 128 String label = null; 129 if (host == mLockIcon) { 130 label = getResources().getString(R.string.unlock_label); 131 } else if (host == mCameraImageView) { 132 label = getResources().getString(R.string.camera_label); 133 } else if (host == mPhoneImageView) { 134 label = getResources().getString(R.string.phone_label); 135 } 136 info.addAction(new AccessibilityAction(ACTION_CLICK, label)); 137 } 138 139 @Override 140 public boolean performAccessibilityAction(View host, int action, Bundle args) { 141 if (action == ACTION_CLICK) { 142 if (host == mLockIcon) { 143 mPhoneStatusBar.animateCollapsePanels( 144 CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */); 145 return true; 146 } else if (host == mCameraImageView) { 147 launchCamera(); 148 return true; 149 } else if (host == mPhoneImageView) { 150 launchPhone(); 151 return true; 152 } 153 } 154 return super.performAccessibilityAction(host, action, args); 155 } 156 }; 157 158 @Override onFinishInflate()159 protected void onFinishInflate() { 160 super.onFinishInflate(); 161 mLockPatternUtils = new LockPatternUtils(mContext); 162 mPreviewContainer = (ViewGroup) findViewById(R.id.preview_container); 163 mCameraImageView = (KeyguardAffordanceView) findViewById(R.id.camera_button); 164 mPhoneImageView = (KeyguardAffordanceView) findViewById(R.id.phone_button); 165 mLockIcon = (KeyguardAffordanceView) findViewById(R.id.lock_icon); 166 mIndicationText = (TextView) findViewById(R.id.keyguard_indication_text); 167 watchForCameraPolicyChanges(); 168 updateCameraVisibility(); 169 updatePhoneVisibility(); 170 mUnlockMethodCache = UnlockMethodCache.getInstance(getContext()); 171 mUnlockMethodCache.addListener(this); 172 updateLockIcon(); 173 setClipChildren(false); 174 setClipToPadding(false); 175 mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext)); 176 inflatePreviews(); 177 mLockIcon.setOnClickListener(this); 178 mLockIcon.setBackground(mTrustDrawable); 179 mLockIcon.setOnLongClickListener(this); 180 mCameraImageView.setOnClickListener(this); 181 mPhoneImageView.setOnClickListener(this); 182 initAccessibility(); 183 } 184 initAccessibility()185 private void initAccessibility() { 186 mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate); 187 mPhoneImageView.setAccessibilityDelegate(mAccessibilityDelegate); 188 mCameraImageView.setAccessibilityDelegate(mAccessibilityDelegate); 189 } 190 191 @Override onConfigurationChanged(Configuration newConfig)192 protected void onConfigurationChanged(Configuration newConfig) { 193 super.onConfigurationChanged(newConfig); 194 int indicationBottomMargin = getResources().getDimensionPixelSize( 195 R.dimen.keyguard_indication_margin_bottom); 196 MarginLayoutParams mlp = (MarginLayoutParams) mIndicationText.getLayoutParams(); 197 if (mlp.bottomMargin != indicationBottomMargin) { 198 mlp.bottomMargin = indicationBottomMargin; 199 mIndicationText.setLayoutParams(mlp); 200 } 201 202 // Respect font size setting. 203 mIndicationText.setTextSize(TypedValue.COMPLEX_UNIT_PX, 204 getResources().getDimensionPixelSize( 205 com.android.internal.R.dimen.text_size_small_material)); 206 } 207 setActivityStarter(ActivityStarter activityStarter)208 public void setActivityStarter(ActivityStarter activityStarter) { 209 mActivityStarter = activityStarter; 210 } 211 setFlashlightController(FlashlightController flashlightController)212 public void setFlashlightController(FlashlightController flashlightController) { 213 mFlashlightController = flashlightController; 214 } 215 setAccessibilityController(AccessibilityController accessibilityController)216 public void setAccessibilityController(AccessibilityController accessibilityController) { 217 mAccessibilityController = accessibilityController; 218 accessibilityController.addStateChangedCallback(this); 219 } 220 setPhoneStatusBar(PhoneStatusBar phoneStatusBar)221 public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) { 222 mPhoneStatusBar = phoneStatusBar; 223 } 224 getCameraIntent()225 private Intent getCameraIntent() { 226 KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); 227 boolean currentUserHasTrust = updateMonitor.getUserHasTrust( 228 mLockPatternUtils.getCurrentUser()); 229 return mLockPatternUtils.isSecure() && !currentUserHasTrust 230 ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT; 231 } 232 updateCameraVisibility()233 private void updateCameraVisibility() { 234 ResolveInfo resolved = mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(), 235 PackageManager.MATCH_DEFAULT_ONLY, 236 mLockPatternUtils.getCurrentUser()); 237 boolean visible = !isCameraDisabledByDpm() && resolved != null 238 && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance); 239 mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE); 240 } 241 updatePhoneVisibility()242 private void updatePhoneVisibility() { 243 boolean visible = isPhoneVisible(); 244 mPhoneImageView.setVisibility(visible ? View.VISIBLE : View.GONE); 245 } 246 isPhoneVisible()247 private boolean isPhoneVisible() { 248 PackageManager pm = mContext.getPackageManager(); 249 return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) 250 && pm.resolveActivity(PHONE_INTENT, 0) != null; 251 } 252 isCameraDisabledByDpm()253 private boolean isCameraDisabledByDpm() { 254 final DevicePolicyManager dpm = 255 (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE); 256 if (dpm != null) { 257 try { 258 final int userId = ActivityManagerNative.getDefault().getCurrentUser().id; 259 final int disabledFlags = dpm.getKeyguardDisabledFeatures(null, userId); 260 final boolean disabledBecauseKeyguardSecure = 261 (disabledFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0 262 && mPhoneStatusBar.isKeyguardSecure(); 263 return dpm.getCameraDisabled(null) || disabledBecauseKeyguardSecure; 264 } catch (RemoteException e) { 265 Log.e(TAG, "Can't get userId", e); 266 } 267 } 268 return false; 269 } 270 watchForCameraPolicyChanges()271 private void watchForCameraPolicyChanges() { 272 final IntentFilter filter = new IntentFilter(); 273 filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); 274 getContext().registerReceiverAsUser(mDevicePolicyReceiver, 275 UserHandle.ALL, filter, null, null); 276 KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback); 277 } 278 279 @Override onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled)280 public void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled) { 281 mCameraImageView.setClickable(touchExplorationEnabled); 282 mPhoneImageView.setClickable(touchExplorationEnabled); 283 mCameraImageView.setFocusable(accessibilityEnabled); 284 mPhoneImageView.setFocusable(accessibilityEnabled); 285 updateLockIconClickability(); 286 } 287 updateLockIconClickability()288 private void updateLockIconClickability() { 289 if (mAccessibilityController == null) { 290 return; 291 } 292 boolean clickToUnlock = mAccessibilityController.isTouchExplorationEnabled(); 293 boolean clickToForceLock = mUnlockMethodCache.isTrustManaged() 294 && !mAccessibilityController.isAccessibilityEnabled(); 295 boolean longClickToForceLock = mUnlockMethodCache.isTrustManaged() 296 && !clickToForceLock; 297 mLockIcon.setClickable(clickToForceLock || clickToUnlock); 298 mLockIcon.setLongClickable(longClickToForceLock); 299 mLockIcon.setFocusable(mAccessibilityController.isAccessibilityEnabled()); 300 } 301 302 @Override onClick(View v)303 public void onClick(View v) { 304 if (v == mCameraImageView) { 305 launchCamera(); 306 } else if (v == mPhoneImageView) { 307 launchPhone(); 308 } if (v == mLockIcon) { 309 if (!mAccessibilityController.isAccessibilityEnabled()) { 310 handleTrustCircleClick(); 311 } else { 312 mPhoneStatusBar.animateCollapsePanels( 313 CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); 314 } 315 } 316 } 317 318 @Override onLongClick(View v)319 public boolean onLongClick(View v) { 320 handleTrustCircleClick(); 321 return true; 322 } 323 handleTrustCircleClick()324 private void handleTrustCircleClick() { 325 EventLogTags.writeSysuiLockscreenGesture( 326 EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_LOCK, 0 /* lengthDp - N/A */, 327 0 /* velocityDp - N/A */); 328 mIndicationController.showTransientIndication( 329 R.string.keyguard_indication_trust_disabled); 330 mLockPatternUtils.requireCredentialEntry(mLockPatternUtils.getCurrentUser()); 331 } 332 launchCamera()333 public void launchCamera() { 334 mFlashlightController.killFlashlight(); 335 Intent intent = getCameraIntent(); 336 boolean wouldLaunchResolverActivity = PreviewInflater.wouldLaunchResolverActivity( 337 mContext, intent, mLockPatternUtils.getCurrentUser()); 338 if (intent == SECURE_CAMERA_INTENT && !wouldLaunchResolverActivity) { 339 mContext.startActivityAsUser(intent, UserHandle.CURRENT); 340 } else { 341 342 // We need to delay starting the activity because ResolverActivity finishes itself if 343 // launched behind lockscreen. 344 mActivityStarter.startActivity(intent, false /* dismissShade */); 345 } 346 } 347 launchPhone()348 public void launchPhone() { 349 final TelecomManager tm = TelecomManager.from(mContext); 350 if (tm.isInCall()) { 351 AsyncTask.execute(new Runnable() { 352 @Override 353 public void run() { 354 tm.showInCallScreen(false /* showDialpad */); 355 } 356 }); 357 } else { 358 mActivityStarter.startActivity(PHONE_INTENT, false /* dismissShade */); 359 } 360 } 361 362 363 @Override onVisibilityChanged(View changedView, int visibility)364 protected void onVisibilityChanged(View changedView, int visibility) { 365 super.onVisibilityChanged(changedView, visibility); 366 if (isShown()) { 367 mTrustDrawable.start(); 368 } else { 369 mTrustDrawable.stop(); 370 } 371 if (changedView == this && visibility == VISIBLE) { 372 updateLockIcon(); 373 updateCameraVisibility(); 374 } 375 } 376 377 @Override onDetachedFromWindow()378 protected void onDetachedFromWindow() { 379 super.onDetachedFromWindow(); 380 mTrustDrawable.stop(); 381 } 382 updateLockIcon()383 private void updateLockIcon() { 384 boolean visible = isShown() && KeyguardUpdateMonitor.getInstance(mContext).isScreenOn(); 385 if (visible) { 386 mTrustDrawable.start(); 387 } else { 388 mTrustDrawable.stop(); 389 } 390 if (!visible) { 391 return; 392 } 393 // TODO: Real icon for facelock. 394 int iconRes = mUnlockMethodCache.isFaceUnlockRunning() 395 ? com.android.internal.R.drawable.ic_account_circle 396 : mUnlockMethodCache.isCurrentlyInsecure() ? R.drawable.ic_lock_open_24dp 397 : R.drawable.ic_lock_24dp; 398 if (mLastUnlockIconRes != iconRes) { 399 Drawable icon = mContext.getDrawable(iconRes); 400 int iconHeight = getResources().getDimensionPixelSize( 401 R.dimen.keyguard_affordance_icon_height); 402 int iconWidth = getResources().getDimensionPixelSize( 403 R.dimen.keyguard_affordance_icon_width); 404 if (icon.getIntrinsicHeight() != iconHeight || icon.getIntrinsicWidth() != iconWidth) { 405 icon = new IntrinsicSizeDrawable(icon, iconWidth, iconHeight); 406 } 407 mLockIcon.setImageDrawable(icon); 408 } 409 boolean trustManaged = mUnlockMethodCache.isTrustManaged(); 410 mTrustDrawable.setTrustManaged(trustManaged); 411 updateLockIconClickability(); 412 } 413 414 415 getPhoneView()416 public KeyguardAffordanceView getPhoneView() { 417 return mPhoneImageView; 418 } 419 getCameraView()420 public KeyguardAffordanceView getCameraView() { 421 return mCameraImageView; 422 } 423 getPhonePreview()424 public View getPhonePreview() { 425 return mPhonePreview; 426 } 427 getCameraPreview()428 public View getCameraPreview() { 429 return mCameraPreview; 430 } 431 getLockIcon()432 public KeyguardAffordanceView getLockIcon() { 433 return mLockIcon; 434 } 435 getIndicationView()436 public View getIndicationView() { 437 return mIndicationText; 438 } 439 440 @Override hasOverlappingRendering()441 public boolean hasOverlappingRendering() { 442 return false; 443 } 444 445 @Override onUnlockMethodStateChanged()446 public void onUnlockMethodStateChanged() { 447 updateLockIcon(); 448 updateCameraVisibility(); 449 } 450 inflatePreviews()451 private void inflatePreviews() { 452 mPhonePreview = mPreviewInflater.inflatePreview(PHONE_INTENT); 453 mCameraPreview = mPreviewInflater.inflatePreview(getCameraIntent()); 454 if (mPhonePreview != null) { 455 mPreviewContainer.addView(mPhonePreview); 456 mPhonePreview.setVisibility(View.INVISIBLE); 457 } 458 if (mCameraPreview != null) { 459 mPreviewContainer.addView(mCameraPreview); 460 mCameraPreview.setVisibility(View.INVISIBLE); 461 } 462 } 463 startFinishDozeAnimation()464 public void startFinishDozeAnimation() { 465 long delay = 0; 466 if (mPhoneImageView.getVisibility() == View.VISIBLE) { 467 startFinishDozeAnimationElement(mPhoneImageView, delay); 468 delay += DOZE_ANIMATION_STAGGER_DELAY; 469 } 470 startFinishDozeAnimationElement(mLockIcon, delay); 471 delay += DOZE_ANIMATION_STAGGER_DELAY; 472 if (mCameraImageView.getVisibility() == View.VISIBLE) { 473 startFinishDozeAnimationElement(mCameraImageView, delay); 474 } 475 mIndicationText.setAlpha(0f); 476 mIndicationText.animate() 477 .alpha(1f) 478 .setInterpolator(mLinearOutSlowInInterpolator) 479 .setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION); 480 } 481 startFinishDozeAnimationElement(View element, long delay)482 private void startFinishDozeAnimationElement(View element, long delay) { 483 element.setAlpha(0f); 484 element.setTranslationY(element.getHeight() / 2); 485 element.animate() 486 .alpha(1f) 487 .translationY(0f) 488 .setInterpolator(mLinearOutSlowInInterpolator) 489 .setStartDelay(delay) 490 .setDuration(DOZE_ANIMATION_ELEMENT_DURATION); 491 } 492 493 private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() { 494 public void onReceive(Context context, Intent intent) { 495 post(new Runnable() { 496 @Override 497 public void run() { 498 updateCameraVisibility(); 499 } 500 }); 501 } 502 }; 503 504 private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback = 505 new KeyguardUpdateMonitorCallback() { 506 @Override 507 public void onUserSwitchComplete(int userId) { 508 updateCameraVisibility(); 509 } 510 511 @Override 512 public void onScreenTurnedOn() { 513 updateLockIcon(); 514 } 515 516 @Override 517 public void onScreenTurnedOff(int why) { 518 updateLockIcon(); 519 } 520 521 @Override 522 public void onKeyguardVisibilityChanged(boolean showing) { 523 updateLockIcon(); 524 } 525 }; 526 setKeyguardIndicationController( KeyguardIndicationController keyguardIndicationController)527 public void setKeyguardIndicationController( 528 KeyguardIndicationController keyguardIndicationController) { 529 mIndicationController = keyguardIndicationController; 530 } 531 532 533 /** 534 * A wrapper around another Drawable that overrides the intrinsic size. 535 */ 536 private static class IntrinsicSizeDrawable extends InsetDrawable { 537 538 private final int mIntrinsicWidth; 539 private final int mIntrinsicHeight; 540 IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight)541 public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) { 542 super(drawable, 0); 543 mIntrinsicWidth = intrinsicWidth; 544 mIntrinsicHeight = intrinsicHeight; 545 } 546 547 @Override getIntrinsicWidth()548 public int getIntrinsicWidth() { 549 return mIntrinsicWidth; 550 } 551 552 @Override getIntrinsicHeight()553 public int getIntrinsicHeight() { 554 return mIntrinsicHeight; 555 } 556 } 557 } 558