Home | History | Annotate | Download | only in phone
      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 
    104     public KeyguardBottomAreaView(Context context) {
    105         this(context, null);
    106     }
    107 
    108     public KeyguardBottomAreaView(Context context, AttributeSet attrs) {
    109         this(context, attrs, 0);
    110     }
    111 
    112     public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr) {
    113         this(context, attrs, defStyleAttr, 0);
    114     }
    115 
    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
    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 
    185     private void initAccessibility() {
    186         mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
    187         mPhoneImageView.setAccessibilityDelegate(mAccessibilityDelegate);
    188         mCameraImageView.setAccessibilityDelegate(mAccessibilityDelegate);
    189     }
    190 
    191     @Override
    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 
    208     public void setActivityStarter(ActivityStarter activityStarter) {
    209         mActivityStarter = activityStarter;
    210     }
    211 
    212     public void setFlashlightController(FlashlightController flashlightController) {
    213         mFlashlightController = flashlightController;
    214     }
    215 
    216     public void setAccessibilityController(AccessibilityController accessibilityController) {
    217         mAccessibilityController = accessibilityController;
    218         accessibilityController.addStateChangedCallback(this);
    219     }
    220 
    221     public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
    222         mPhoneStatusBar = phoneStatusBar;
    223         updateCameraVisibility(); // in case onFinishInflate() was called too early
    224     }
    225 
    226     private Intent getCameraIntent() {
    227         KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
    228         boolean currentUserHasTrust = updateMonitor.getUserHasTrust(
    229                 mLockPatternUtils.getCurrentUser());
    230         return mLockPatternUtils.isSecure() && !currentUserHasTrust
    231                 ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
    232     }
    233 
    234     private void updateCameraVisibility() {
    235         if (mCameraImageView == null) {
    236             // Things are not set up yet; reply hazy, ask again later
    237             return;
    238         }
    239         ResolveInfo resolved = mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(),
    240                 PackageManager.MATCH_DEFAULT_ONLY,
    241                 mLockPatternUtils.getCurrentUser());
    242         boolean visible = !isCameraDisabledByDpm() && resolved != null
    243                 && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance);
    244         mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
    245     }
    246 
    247     private void updatePhoneVisibility() {
    248         boolean visible = isPhoneVisible();
    249         mPhoneImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
    250     }
    251 
    252     private boolean isPhoneVisible() {
    253         PackageManager pm = mContext.getPackageManager();
    254         return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
    255                 && pm.resolveActivity(PHONE_INTENT, 0) != null;
    256     }
    257 
    258     private boolean isCameraDisabledByDpm() {
    259         final DevicePolicyManager dpm =
    260                 (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
    261         if (dpm != null && mPhoneStatusBar != null) {
    262             try {
    263                 final int userId = ActivityManagerNative.getDefault().getCurrentUser().id;
    264                 final int disabledFlags = dpm.getKeyguardDisabledFeatures(null, userId);
    265                 final  boolean disabledBecauseKeyguardSecure =
    266                         (disabledFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0
    267                                 && mPhoneStatusBar.isKeyguardSecure();
    268                 return dpm.getCameraDisabled(null) || disabledBecauseKeyguardSecure;
    269             } catch (RemoteException e) {
    270                 Log.e(TAG, "Can't get userId", e);
    271             }
    272         }
    273         return false;
    274     }
    275 
    276     private void watchForCameraPolicyChanges() {
    277         final IntentFilter filter = new IntentFilter();
    278         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
    279         getContext().registerReceiverAsUser(mDevicePolicyReceiver,
    280                 UserHandle.ALL, filter, null, null);
    281         KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
    282     }
    283 
    284     @Override
    285     public void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled) {
    286         mCameraImageView.setClickable(touchExplorationEnabled);
    287         mPhoneImageView.setClickable(touchExplorationEnabled);
    288         mCameraImageView.setFocusable(accessibilityEnabled);
    289         mPhoneImageView.setFocusable(accessibilityEnabled);
    290         updateLockIconClickability();
    291     }
    292 
    293     private void updateLockIconClickability() {
    294         if (mAccessibilityController == null) {
    295             return;
    296         }
    297         boolean clickToUnlock = mAccessibilityController.isTouchExplorationEnabled();
    298         boolean clickToForceLock = mUnlockMethodCache.isTrustManaged()
    299                 && !mAccessibilityController.isAccessibilityEnabled();
    300         boolean longClickToForceLock = mUnlockMethodCache.isTrustManaged()
    301                 && !clickToForceLock;
    302         mLockIcon.setClickable(clickToForceLock || clickToUnlock);
    303         mLockIcon.setLongClickable(longClickToForceLock);
    304         mLockIcon.setFocusable(mAccessibilityController.isAccessibilityEnabled());
    305     }
    306 
    307     @Override
    308     public void onClick(View v) {
    309         if (v == mCameraImageView) {
    310             launchCamera();
    311         } else if (v == mPhoneImageView) {
    312             launchPhone();
    313         } if (v == mLockIcon) {
    314             if (!mAccessibilityController.isAccessibilityEnabled()) {
    315                 handleTrustCircleClick();
    316             } else {
    317                 mPhoneStatusBar.animateCollapsePanels(
    318                         CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
    319             }
    320         }
    321     }
    322 
    323     @Override
    324     public boolean onLongClick(View v) {
    325         handleTrustCircleClick();
    326         return true;
    327     }
    328 
    329     private void handleTrustCircleClick() {
    330         EventLogTags.writeSysuiLockscreenGesture(
    331                 EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_LOCK, 0 /* lengthDp - N/A */,
    332                 0 /* velocityDp - N/A */);
    333         mIndicationController.showTransientIndication(
    334                 R.string.keyguard_indication_trust_disabled);
    335         mLockPatternUtils.requireCredentialEntry(mLockPatternUtils.getCurrentUser());
    336     }
    337 
    338     public void launchCamera() {
    339         mFlashlightController.killFlashlight();
    340         Intent intent = getCameraIntent();
    341         boolean wouldLaunchResolverActivity = PreviewInflater.wouldLaunchResolverActivity(
    342                 mContext, intent, mLockPatternUtils.getCurrentUser());
    343         if (intent == SECURE_CAMERA_INTENT && !wouldLaunchResolverActivity) {
    344             mContext.startActivityAsUser(intent, UserHandle.CURRENT);
    345         } else {
    346 
    347             // We need to delay starting the activity because ResolverActivity finishes itself if
    348             // launched behind lockscreen.
    349             mActivityStarter.startActivity(intent, false /* dismissShade */);
    350         }
    351     }
    352 
    353     public void launchPhone() {
    354         final TelecomManager tm = TelecomManager.from(mContext);
    355         if (tm.isInCall()) {
    356             AsyncTask.execute(new Runnable() {
    357                 @Override
    358                 public void run() {
    359                     tm.showInCallScreen(false /* showDialpad */);
    360                 }
    361             });
    362         } else {
    363             mActivityStarter.startActivity(PHONE_INTENT, false /* dismissShade */);
    364         }
    365     }
    366 
    367 
    368     @Override
    369     protected void onVisibilityChanged(View changedView, int visibility) {
    370         super.onVisibilityChanged(changedView, visibility);
    371         if (isShown()) {
    372             mTrustDrawable.start();
    373         } else {
    374             mTrustDrawable.stop();
    375         }
    376         if (changedView == this && visibility == VISIBLE) {
    377             updateLockIcon();
    378             updateCameraVisibility();
    379         }
    380     }
    381 
    382     @Override
    383     protected void onDetachedFromWindow() {
    384         super.onDetachedFromWindow();
    385         mTrustDrawable.stop();
    386     }
    387 
    388     private void updateLockIcon() {
    389         boolean visible = isShown() && KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
    390         if (visible) {
    391             mTrustDrawable.start();
    392         } else {
    393             mTrustDrawable.stop();
    394         }
    395         if (!visible) {
    396             return;
    397         }
    398         // TODO: Real icon for facelock.
    399         int iconRes = mUnlockMethodCache.isFaceUnlockRunning()
    400                 ? com.android.internal.R.drawable.ic_account_circle
    401                 : mUnlockMethodCache.isCurrentlyInsecure() ? R.drawable.ic_lock_open_24dp
    402                 : R.drawable.ic_lock_24dp;
    403         if (mLastUnlockIconRes != iconRes) {
    404             Drawable icon = mContext.getDrawable(iconRes);
    405             int iconHeight = getResources().getDimensionPixelSize(
    406                     R.dimen.keyguard_affordance_icon_height);
    407             int iconWidth = getResources().getDimensionPixelSize(
    408                     R.dimen.keyguard_affordance_icon_width);
    409             if (icon.getIntrinsicHeight() != iconHeight || icon.getIntrinsicWidth() != iconWidth) {
    410                 icon = new IntrinsicSizeDrawable(icon, iconWidth, iconHeight);
    411             }
    412             mLockIcon.setImageDrawable(icon);
    413         }
    414         boolean trustManaged = mUnlockMethodCache.isTrustManaged();
    415         mTrustDrawable.setTrustManaged(trustManaged);
    416         updateLockIconClickability();
    417     }
    418 
    419 
    420 
    421     public KeyguardAffordanceView getPhoneView() {
    422         return mPhoneImageView;
    423     }
    424 
    425     public KeyguardAffordanceView getCameraView() {
    426         return mCameraImageView;
    427     }
    428 
    429     public View getPhonePreview() {
    430         return mPhonePreview;
    431     }
    432 
    433     public View getCameraPreview() {
    434         return mCameraPreview;
    435     }
    436 
    437     public KeyguardAffordanceView getLockIcon() {
    438         return mLockIcon;
    439     }
    440 
    441     public View getIndicationView() {
    442         return mIndicationText;
    443     }
    444 
    445     @Override
    446     public boolean hasOverlappingRendering() {
    447         return false;
    448     }
    449 
    450     @Override
    451     public void onUnlockMethodStateChanged() {
    452         updateLockIcon();
    453         updateCameraVisibility();
    454     }
    455 
    456     private void inflatePreviews() {
    457         mPhonePreview = mPreviewInflater.inflatePreview(PHONE_INTENT);
    458         mCameraPreview = mPreviewInflater.inflatePreview(getCameraIntent());
    459         if (mPhonePreview != null) {
    460             mPreviewContainer.addView(mPhonePreview);
    461             mPhonePreview.setVisibility(View.INVISIBLE);
    462         }
    463         if (mCameraPreview != null) {
    464             mPreviewContainer.addView(mCameraPreview);
    465             mCameraPreview.setVisibility(View.INVISIBLE);
    466         }
    467     }
    468 
    469     public void startFinishDozeAnimation() {
    470         long delay = 0;
    471         if (mPhoneImageView.getVisibility() == View.VISIBLE) {
    472             startFinishDozeAnimationElement(mPhoneImageView, delay);
    473             delay += DOZE_ANIMATION_STAGGER_DELAY;
    474         }
    475         startFinishDozeAnimationElement(mLockIcon, delay);
    476         delay += DOZE_ANIMATION_STAGGER_DELAY;
    477         if (mCameraImageView.getVisibility() == View.VISIBLE) {
    478             startFinishDozeAnimationElement(mCameraImageView, delay);
    479         }
    480         mIndicationText.setAlpha(0f);
    481         mIndicationText.animate()
    482                 .alpha(1f)
    483                 .setInterpolator(mLinearOutSlowInInterpolator)
    484                 .setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
    485     }
    486 
    487     private void startFinishDozeAnimationElement(View element, long delay) {
    488         element.setAlpha(0f);
    489         element.setTranslationY(element.getHeight() / 2);
    490         element.animate()
    491                 .alpha(1f)
    492                 .translationY(0f)
    493                 .setInterpolator(mLinearOutSlowInInterpolator)
    494                 .setStartDelay(delay)
    495                 .setDuration(DOZE_ANIMATION_ELEMENT_DURATION);
    496     }
    497 
    498     private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() {
    499         public void onReceive(Context context, Intent intent) {
    500             post(new Runnable() {
    501                 @Override
    502                 public void run() {
    503                     updateCameraVisibility();
    504                 }
    505             });
    506         }
    507     };
    508 
    509     private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
    510             new KeyguardUpdateMonitorCallback() {
    511         @Override
    512         public void onUserSwitchComplete(int userId) {
    513             updateCameraVisibility();
    514         }
    515 
    516         @Override
    517         public void onScreenTurnedOn() {
    518             updateLockIcon();
    519         }
    520 
    521         @Override
    522         public void onScreenTurnedOff(int why) {
    523             updateLockIcon();
    524         }
    525 
    526         @Override
    527         public void onKeyguardVisibilityChanged(boolean showing) {
    528             updateLockIcon();
    529         }
    530     };
    531 
    532     public void setKeyguardIndicationController(
    533             KeyguardIndicationController keyguardIndicationController) {
    534         mIndicationController = keyguardIndicationController;
    535     }
    536 
    537 
    538     /**
    539      * A wrapper around another Drawable that overrides the intrinsic size.
    540      */
    541     private static class IntrinsicSizeDrawable extends InsetDrawable {
    542 
    543         private final int mIntrinsicWidth;
    544         private final int mIntrinsicHeight;
    545 
    546         public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) {
    547             super(drawable, 0);
    548             mIntrinsicWidth = intrinsicWidth;
    549             mIntrinsicHeight = intrinsicHeight;
    550         }
    551 
    552         @Override
    553         public int getIntrinsicWidth() {
    554             return mIntrinsicWidth;
    555         }
    556 
    557         @Override
    558         public int getIntrinsicHeight() {
    559             return mIntrinsicHeight;
    560         }
    561     }
    562 }
    563