| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.android.systemui.statusbar.phone; |
| |
| import android.app.ActivityManagerNative; |
| import android.app.admin.DevicePolicyManager; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.content.res.Configuration; |
| import android.graphics.drawable.Drawable; |
| import android.graphics.drawable.InsetDrawable; |
| import android.os.AsyncTask; |
| import android.os.Bundle; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.provider.MediaStore; |
| import android.telecom.TelecomManager; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.util.TypedValue; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.accessibility.AccessibilityNodeInfo; |
| import android.view.animation.AnimationUtils; |
| import android.view.animation.Interpolator; |
| import android.widget.FrameLayout; |
| import android.widget.TextView; |
| |
| import com.android.internal.widget.LockPatternUtils; |
| import com.android.keyguard.KeyguardUpdateMonitor; |
| import com.android.keyguard.KeyguardUpdateMonitorCallback; |
| import com.android.systemui.EventLogConstants; |
| import com.android.systemui.EventLogTags; |
| import com.android.systemui.R; |
| import com.android.systemui.assist.AssistManager; |
| import com.android.systemui.statusbar.CommandQueue; |
| import com.android.systemui.statusbar.KeyguardAffordanceView; |
| import com.android.systemui.statusbar.KeyguardIndicationController; |
| import com.android.systemui.statusbar.policy.AccessibilityController; |
| import com.android.systemui.statusbar.policy.FlashlightController; |
| import com.android.systemui.statusbar.policy.PreviewInflater; |
| |
| import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; |
| import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; |
| |
| /** |
| * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status |
| * text. |
| */ |
| public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener, |
| UnlockMethodCache.OnUnlockMethodChangedListener, |
| AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener { |
| |
| final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView"; |
| |
| private static final Intent SECURE_CAMERA_INTENT = |
| new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE) |
| .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); |
| private static final Intent INSECURE_CAMERA_INTENT = |
| new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); |
| private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL); |
| private static final int DOZE_ANIMATION_STAGGER_DELAY = 48; |
| private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250; |
| private static final long TRANSIENT_FP_ERROR_TIMEOUT = 1300; |
| |
| private KeyguardAffordanceView mCameraImageView; |
| private KeyguardAffordanceView mLeftAffordanceView; |
| private LockIcon mLockIcon; |
| private TextView mIndicationText; |
| private ViewGroup mPreviewContainer; |
| |
| private View mLeftPreview; |
| private View mCameraPreview; |
| |
| private ActivityStarter mActivityStarter; |
| private UnlockMethodCache mUnlockMethodCache; |
| private LockPatternUtils mLockPatternUtils; |
| private FlashlightController mFlashlightController; |
| private PreviewInflater mPreviewInflater; |
| private KeyguardIndicationController mIndicationController; |
| private AccessibilityController mAccessibilityController; |
| private PhoneStatusBar mPhoneStatusBar; |
| |
| private final Interpolator mLinearOutSlowInInterpolator; |
| private boolean mPrewarmSent; |
| private boolean mLeftIsVoiceAssist; |
| private AssistManager mAssistManager; |
| |
| public KeyguardBottomAreaView(Context context) { |
| this(context, null); |
| } |
| |
| public KeyguardBottomAreaView(Context context, AttributeSet attrs) { |
| this(context, attrs, 0); |
| } |
| |
| public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr) { |
| this(context, attrs, defStyleAttr, 0); |
| } |
| |
| public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr, |
| int defStyleRes) { |
| super(context, attrs, defStyleAttr, defStyleRes); |
| mLinearOutSlowInInterpolator = |
| AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in); |
| } |
| |
| private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() { |
| @Override |
| public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { |
| super.onInitializeAccessibilityNodeInfo(host, info); |
| String label = null; |
| if (host == mLockIcon) { |
| label = getResources().getString(R.string.unlock_label); |
| } else if (host == mCameraImageView) { |
| label = getResources().getString(R.string.camera_label); |
| } else if (host == mLeftAffordanceView) { |
| if (mLeftIsVoiceAssist) { |
| label = getResources().getString(R.string.voice_assist_label); |
| } else { |
| label = getResources().getString(R.string.phone_label); |
| } |
| } |
| info.addAction(new AccessibilityAction(ACTION_CLICK, label)); |
| } |
| |
| @Override |
| public boolean performAccessibilityAction(View host, int action, Bundle args) { |
| if (action == ACTION_CLICK) { |
| if (host == mLockIcon) { |
| mPhoneStatusBar.animateCollapsePanels( |
| CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */); |
| return true; |
| } else if (host == mCameraImageView) { |
| launchCamera(); |
| return true; |
| } else if (host == mLeftAffordanceView) { |
| launchLeftAffordance(); |
| return true; |
| } |
| } |
| return super.performAccessibilityAction(host, action, args); |
| } |
| }; |
| |
| @Override |
| protected void onFinishInflate() { |
| super.onFinishInflate(); |
| mLockPatternUtils = new LockPatternUtils(mContext); |
| mPreviewContainer = (ViewGroup) findViewById(R.id.preview_container); |
| mCameraImageView = (KeyguardAffordanceView) findViewById(R.id.camera_button); |
| mLeftAffordanceView = (KeyguardAffordanceView) findViewById(R.id.left_button); |
| mLockIcon = (LockIcon) findViewById(R.id.lock_icon); |
| mIndicationText = (TextView) findViewById(R.id.keyguard_indication_text); |
| watchForCameraPolicyChanges(); |
| updateCameraVisibility(); |
| mUnlockMethodCache = UnlockMethodCache.getInstance(getContext()); |
| mUnlockMethodCache.addListener(this); |
| mLockIcon.update(); |
| setClipChildren(false); |
| setClipToPadding(false); |
| mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext)); |
| inflateCameraPreview(); |
| mLockIcon.setOnClickListener(this); |
| mLockIcon.setOnLongClickListener(this); |
| mCameraImageView.setOnClickListener(this); |
| mLeftAffordanceView.setOnClickListener(this); |
| initAccessibility(); |
| } |
| |
| private void initAccessibility() { |
| mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate); |
| mLeftAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate); |
| mCameraImageView.setAccessibilityDelegate(mAccessibilityDelegate); |
| } |
| |
| @Override |
| protected void onConfigurationChanged(Configuration newConfig) { |
| super.onConfigurationChanged(newConfig); |
| int indicationBottomMargin = getResources().getDimensionPixelSize( |
| R.dimen.keyguard_indication_margin_bottom); |
| MarginLayoutParams mlp = (MarginLayoutParams) mIndicationText.getLayoutParams(); |
| if (mlp.bottomMargin != indicationBottomMargin) { |
| mlp.bottomMargin = indicationBottomMargin; |
| mIndicationText.setLayoutParams(mlp); |
| } |
| |
| // Respect font size setting. |
| mIndicationText.setTextSize(TypedValue.COMPLEX_UNIT_PX, |
| getResources().getDimensionPixelSize( |
| com.android.internal.R.dimen.text_size_small_material)); |
| } |
| |
| public void setActivityStarter(ActivityStarter activityStarter) { |
| mActivityStarter = activityStarter; |
| } |
| |
| public void setFlashlightController(FlashlightController flashlightController) { |
| mFlashlightController = flashlightController; |
| } |
| |
| public void setAccessibilityController(AccessibilityController accessibilityController) { |
| mAccessibilityController = accessibilityController; |
| mLockIcon.setAccessibilityController(accessibilityController); |
| accessibilityController.addStateChangedCallback(this); |
| } |
| |
| public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) { |
| mPhoneStatusBar = phoneStatusBar; |
| updateCameraVisibility(); // in case onFinishInflate() was called too early |
| } |
| |
| private Intent getCameraIntent() { |
| KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); |
| boolean currentUserHasTrust = updateMonitor.getUserHasTrust( |
| KeyguardUpdateMonitor.getCurrentUser()); |
| boolean secure = mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser()); |
| return (secure && !currentUserHasTrust) ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT; |
| } |
| |
| private void updateCameraVisibility() { |
| if (mCameraImageView == null) { |
| // Things are not set up yet; reply hazy, ask again later |
| return; |
| } |
| ResolveInfo resolved = mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(), |
| PackageManager.MATCH_DEFAULT_ONLY, |
| KeyguardUpdateMonitor.getCurrentUser()); |
| boolean visible = !isCameraDisabledByDpm() && resolved != null |
| && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance); |
| mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE); |
| } |
| |
| private void updateLeftAffordanceIcon() { |
| mLeftIsVoiceAssist = canLaunchVoiceAssist(); |
| int drawableId; |
| int contentDescription; |
| if (mLeftIsVoiceAssist) { |
| mLeftAffordanceView.setVisibility(View.VISIBLE); |
| drawableId = R.drawable.ic_mic_26dp; |
| contentDescription = R.string.accessibility_voice_assist_button; |
| } else { |
| boolean visible = isPhoneVisible(); |
| mLeftAffordanceView.setVisibility(visible ? View.VISIBLE : View.GONE); |
| drawableId = R.drawable.ic_phone_24dp; |
| contentDescription = R.string.accessibility_phone_button; |
| } |
| mLeftAffordanceView.setImageDrawable(mContext.getDrawable(drawableId)); |
| mLeftAffordanceView.setContentDescription(mContext.getString(contentDescription)); |
| } |
| |
| public boolean isLeftVoiceAssist() { |
| return mLeftIsVoiceAssist; |
| } |
| |
| private boolean isPhoneVisible() { |
| PackageManager pm = mContext.getPackageManager(); |
| return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) |
| && pm.resolveActivity(PHONE_INTENT, 0) != null; |
| } |
| |
| private boolean isCameraDisabledByDpm() { |
| final DevicePolicyManager dpm = |
| (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE); |
| if (dpm != null && mPhoneStatusBar != null) { |
| try { |
| final int userId = ActivityManagerNative.getDefault().getCurrentUser().id; |
| final int disabledFlags = dpm.getKeyguardDisabledFeatures(null, userId); |
| final boolean disabledBecauseKeyguardSecure = |
| (disabledFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0 |
| && mPhoneStatusBar.isKeyguardSecure(); |
| return dpm.getCameraDisabled(null) || disabledBecauseKeyguardSecure; |
| } catch (RemoteException e) { |
| Log.e(TAG, "Can't get userId", e); |
| } |
| } |
| return false; |
| } |
| |
| private void watchForCameraPolicyChanges() { |
| final IntentFilter filter = new IntentFilter(); |
| filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); |
| getContext().registerReceiverAsUser(mDevicePolicyReceiver, |
| UserHandle.ALL, filter, null, null); |
| KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback); |
| } |
| |
| @Override |
| public void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled) { |
| mCameraImageView.setClickable(touchExplorationEnabled); |
| mLeftAffordanceView.setClickable(touchExplorationEnabled); |
| mCameraImageView.setFocusable(accessibilityEnabled); |
| mLeftAffordanceView.setFocusable(accessibilityEnabled); |
| mLockIcon.update(); |
| } |
| |
| @Override |
| public void onClick(View v) { |
| if (v == mCameraImageView) { |
| launchCamera(); |
| } else if (v == mLeftAffordanceView) { |
| launchLeftAffordance(); |
| } if (v == mLockIcon) { |
| if (!mAccessibilityController.isAccessibilityEnabled()) { |
| handleTrustCircleClick(); |
| } else { |
| mPhoneStatusBar.animateCollapsePanels( |
| CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); |
| } |
| } |
| } |
| |
| @Override |
| public boolean onLongClick(View v) { |
| handleTrustCircleClick(); |
| return true; |
| } |
| |
| private void handleTrustCircleClick() { |
| EventLogTags.writeSysuiLockscreenGesture( |
| EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_LOCK, 0 /* lengthDp - N/A */, |
| 0 /* velocityDp - N/A */); |
| mIndicationController.showTransientIndication( |
| R.string.keyguard_indication_trust_disabled); |
| mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser()); |
| } |
| |
| public void prewarmCamera() { |
| Intent intent = getCameraIntent(); |
| String targetPackage = PreviewInflater.getTargetPackage(mContext, intent, |
| KeyguardUpdateMonitor.getCurrentUser()); |
| if (targetPackage != null) { |
| Intent prewarm = new Intent(MediaStore.ACTION_STILL_IMAGE_CAMERA_PREWARM); |
| prewarm.setPackage(targetPackage); |
| mPrewarmSent = true; |
| mContext.sendBroadcast(prewarm); |
| } |
| } |
| |
| public void maybeCooldownCamera() { |
| if (!mPrewarmSent) { |
| return; |
| } |
| mPrewarmSent = false; |
| Intent intent = getCameraIntent(); |
| String targetPackage = PreviewInflater.getTargetPackage(mContext, intent, |
| KeyguardUpdateMonitor.getCurrentUser()); |
| if (targetPackage != null) { |
| Intent prewarm = new Intent(MediaStore.ACTION_STILL_IMAGE_CAMERA_COOLDOWN); |
| prewarm.setPackage(targetPackage); |
| mContext.sendBroadcast(prewarm); |
| } |
| } |
| |
| public void launchCamera() { |
| |
| // Reset prewarm state. |
| mPrewarmSent = false; |
| final Intent intent = getCameraIntent(); |
| boolean wouldLaunchResolverActivity = PreviewInflater.wouldLaunchResolverActivity( |
| mContext, intent, KeyguardUpdateMonitor.getCurrentUser()); |
| if (intent == SECURE_CAMERA_INTENT && !wouldLaunchResolverActivity) { |
| AsyncTask.execute(new Runnable() { |
| @Override |
| public void run() { |
| mContext.startActivityAsUser(intent, UserHandle.CURRENT); |
| mActivityStarter.preventNextAnimation(); |
| } |
| }); |
| } else { |
| |
| // We need to delay starting the activity because ResolverActivity finishes itself if |
| // launched behind lockscreen. |
| mActivityStarter.startActivity(intent, false /* dismissShade */); |
| } |
| } |
| |
| public void launchLeftAffordance() { |
| if (mLeftIsVoiceAssist) { |
| launchVoiceAssist(); |
| } else { |
| launchPhone(); |
| } |
| } |
| |
| private void launchVoiceAssist() { |
| Runnable runnable = new Runnable() { |
| @Override |
| public void run() { |
| mAssistManager.launchVoiceAssistFromKeyguard(); |
| mActivityStarter.preventNextAnimation(); |
| } |
| }; |
| if (mPhoneStatusBar.isKeyguardCurrentlySecure()) { |
| AsyncTask.execute(runnable); |
| } else { |
| mPhoneStatusBar.executeRunnableDismissingKeyguard(runnable, false /* dismissShade */, |
| false /* afterKeyguardGone */); |
| } |
| } |
| |
| private boolean canLaunchVoiceAssist() { |
| return mAssistManager.canVoiceAssistBeLaunchedFromKeyguard(); |
| } |
| |
| private void launchPhone() { |
| final TelecomManager tm = TelecomManager.from(mContext); |
| if (tm.isInCall()) { |
| AsyncTask.execute(new Runnable() { |
| @Override |
| public void run() { |
| tm.showInCallScreen(false /* showDialpad */); |
| } |
| }); |
| } else { |
| mActivityStarter.startActivity(PHONE_INTENT, false /* dismissShade */); |
| } |
| } |
| |
| |
| @Override |
| protected void onVisibilityChanged(View changedView, int visibility) { |
| super.onVisibilityChanged(changedView, visibility); |
| if (changedView == this && visibility == VISIBLE) { |
| mLockIcon.update(); |
| updateCameraVisibility(); |
| } |
| } |
| |
| public KeyguardAffordanceView getLeftView() { |
| return mLeftAffordanceView; |
| } |
| |
| public KeyguardAffordanceView getRightView() { |
| return mCameraImageView; |
| } |
| |
| public View getLeftPreview() { |
| return mLeftPreview; |
| } |
| |
| public View getRightPreview() { |
| return mCameraPreview; |
| } |
| |
| public KeyguardAffordanceView getLockIcon() { |
| return mLockIcon; |
| } |
| |
| public View getIndicationView() { |
| return mIndicationText; |
| } |
| |
| @Override |
| public boolean hasOverlappingRendering() { |
| return false; |
| } |
| |
| @Override |
| public void onUnlockMethodStateChanged() { |
| mLockIcon.update(); |
| updateCameraVisibility(); |
| } |
| |
| private void inflateCameraPreview() { |
| mCameraPreview = mPreviewInflater.inflatePreview(getCameraIntent()); |
| if (mCameraPreview != null) { |
| mPreviewContainer.addView(mCameraPreview); |
| mCameraPreview.setVisibility(View.INVISIBLE); |
| } |
| } |
| |
| private void updateLeftPreview() { |
| View previewBefore = mLeftPreview; |
| if (previewBefore != null) { |
| mPreviewContainer.removeView(previewBefore); |
| } |
| if (mLeftIsVoiceAssist) { |
| mLeftPreview = mPreviewInflater.inflatePreviewFromService( |
| mAssistManager.getVoiceInteractorComponentName()); |
| } else { |
| mLeftPreview = mPreviewInflater.inflatePreview(PHONE_INTENT); |
| } |
| if (mLeftPreview != null) { |
| mPreviewContainer.addView(mLeftPreview); |
| mLeftPreview.setVisibility(View.INVISIBLE); |
| } |
| } |
| |
| public void startFinishDozeAnimation() { |
| long delay = 0; |
| if (mLeftAffordanceView.getVisibility() == View.VISIBLE) { |
| startFinishDozeAnimationElement(mLeftAffordanceView, delay); |
| delay += DOZE_ANIMATION_STAGGER_DELAY; |
| } |
| startFinishDozeAnimationElement(mLockIcon, delay); |
| delay += DOZE_ANIMATION_STAGGER_DELAY; |
| if (mCameraImageView.getVisibility() == View.VISIBLE) { |
| startFinishDozeAnimationElement(mCameraImageView, delay); |
| } |
| mIndicationText.setAlpha(0f); |
| mIndicationText.animate() |
| .alpha(1f) |
| .setInterpolator(mLinearOutSlowInInterpolator) |
| .setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION); |
| } |
| |
| private void startFinishDozeAnimationElement(View element, long delay) { |
| element.setAlpha(0f); |
| element.setTranslationY(element.getHeight() / 2); |
| element.animate() |
| .alpha(1f) |
| .translationY(0f) |
| .setInterpolator(mLinearOutSlowInInterpolator) |
| .setStartDelay(delay) |
| .setDuration(DOZE_ANIMATION_ELEMENT_DURATION); |
| } |
| |
| private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() { |
| public void onReceive(Context context, Intent intent) { |
| post(new Runnable() { |
| @Override |
| public void run() { |
| updateCameraVisibility(); |
| } |
| }); |
| } |
| }; |
| |
| private final Runnable mTransientFpErrorClearRunnable = new Runnable() { |
| @Override |
| public void run() { |
| mLockIcon.setTransientFpError(false); |
| mIndicationController.hideTransientIndication(); |
| } |
| }; |
| |
| private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback = |
| new KeyguardUpdateMonitorCallback() { |
| @Override |
| public void onUserSwitchComplete(int userId) { |
| updateCameraVisibility(); |
| } |
| |
| @Override |
| public void onScreenTurnedOn() { |
| mLockIcon.setScreenOn(true); |
| } |
| |
| @Override |
| public void onScreenTurnedOff(int why) { |
| mLockIcon.setScreenOn(false); |
| } |
| |
| @Override |
| public void onKeyguardVisibilityChanged(boolean showing) { |
| mLockIcon.update(); |
| } |
| |
| @Override |
| public void onFingerprintAuthenticated(int userId) { |
| } |
| |
| @Override |
| public void onFingerprintRunningStateChanged(boolean running) { |
| mLockIcon.update(); |
| } |
| |
| @Override |
| public void onFingerprintHelp(int msgId, String helpString) { |
| mLockIcon.setTransientFpError(true); |
| mIndicationController.showTransientIndication(helpString, |
| getResources().getColor(R.color.system_warning_color, null)); |
| removeCallbacks(mTransientFpErrorClearRunnable); |
| postDelayed(mTransientFpErrorClearRunnable, TRANSIENT_FP_ERROR_TIMEOUT); |
| } |
| |
| @Override |
| public void onFingerprintError(int msgId, String errString) { |
| // TODO: Go to bouncer if this is "too many attempts" (lockout) error. |
| } |
| }; |
| |
| public void setKeyguardIndicationController( |
| KeyguardIndicationController keyguardIndicationController) { |
| mIndicationController = keyguardIndicationController; |
| } |
| |
| public void setAssistManager(AssistManager assistManager) { |
| mAssistManager = assistManager; |
| updateLeftAffordance(); |
| } |
| |
| public void updateLeftAffordance() { |
| updateLeftAffordanceIcon(); |
| updateLeftPreview(); |
| } |
| } |