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