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