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.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