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