KeyguardBottomAreaView.java revision 4eedc1d97e5aa3bfaa60af9894cd991f9e210fc2
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.widget.FrameLayout;
43import android.widget.TextView;
44
45import com.android.internal.widget.LockPatternUtils;
46import com.android.keyguard.KeyguardUpdateMonitor;
47import com.android.keyguard.KeyguardUpdateMonitorCallback;
48import com.android.systemui.R;
49import com.android.systemui.statusbar.CommandQueue;
50import com.android.systemui.statusbar.KeyguardAffordanceView;
51import com.android.systemui.statusbar.KeyguardIndicationController;
52import com.android.systemui.statusbar.policy.AccessibilityController;
53import com.android.systemui.statusbar.policy.FlashlightController;
54import com.android.systemui.statusbar.policy.PreviewInflater;
55
56import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
57import 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 */
63public 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_RECENTS_PANEL, 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        @Override
482        public void onKeyguardVisibilityChanged(boolean showing) {
483            updateLockIcon();
484        }
485    };
486
487    public void setKeyguardIndicationController(
488            KeyguardIndicationController keyguardIndicationController) {
489        mIndicationController = keyguardIndicationController;
490    }
491
492
493    /**
494     * A wrapper around another Drawable that overrides the intrinsic size.
495     */
496    private static class IntrinsicSizeDrawable extends InsetDrawable {
497
498        private final int mIntrinsicWidth;
499        private final int mIntrinsicHeight;
500
501        public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) {
502            super(drawable, 0);
503            mIntrinsicWidth = intrinsicWidth;
504            mIntrinsicHeight = intrinsicHeight;
505        }
506
507        @Override
508        public int getIntrinsicWidth() {
509            return mIntrinsicWidth;
510        }
511
512        @Override
513        public int getIntrinsicHeight() {
514            return mIntrinsicHeight;
515        }
516    }
517}
518