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