KeyguardBottomAreaView.java revision 0eb1e402c7e612887e38dc5516f11506b11fd835
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.os.AsyncTask;
29import android.os.Bundle;
30import android.os.RemoteException;
31import android.os.UserHandle;
32import android.provider.MediaStore;
33import android.telecomm.TelecommManager;
34import android.util.AttributeSet;
35import android.util.Log;
36import android.util.TypedValue;
37import android.view.View;
38import android.view.ViewGroup;
39import android.view.accessibility.AccessibilityNodeInfo;
40import android.widget.FrameLayout;
41import android.widget.TextView;
42
43import com.android.internal.widget.LockPatternUtils;
44import com.android.keyguard.KeyguardUpdateMonitor;
45import com.android.keyguard.KeyguardUpdateMonitorCallback;
46import com.android.systemui.R;
47import com.android.systemui.statusbar.CommandQueue;
48import com.android.systemui.statusbar.KeyguardAffordanceView;
49import com.android.systemui.statusbar.KeyguardIndicationController;
50import com.android.systemui.statusbar.policy.AccessibilityController;
51import com.android.systemui.statusbar.policy.FlashlightController;
52import com.android.systemui.statusbar.policy.PreviewInflater;
53
54import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
55import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
56
57/**
58 * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
59 * text.
60 */
61public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener,
62        UnlockMethodCache.OnUnlockMethodChangedListener,
63        AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener {
64
65    final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView";
66
67    private static final Intent SECURE_CAMERA_INTENT =
68            new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
69                    .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
70    private static final Intent INSECURE_CAMERA_INTENT =
71            new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
72    private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);
73
74    private KeyguardAffordanceView mCameraImageView;
75    private KeyguardAffordanceView mPhoneImageView;
76    private KeyguardAffordanceView mLockIcon;
77    private TextView mIndicationText;
78    private ViewGroup mPreviewContainer;
79
80    private View mPhonePreview;
81    private View mCameraPreview;
82
83    private ActivityStarter mActivityStarter;
84    private UnlockMethodCache mUnlockMethodCache;
85    private LockPatternUtils mLockPatternUtils;
86    private FlashlightController mFlashlightController;
87    private PreviewInflater mPreviewInflater;
88    private KeyguardIndicationController mIndicationController;
89    private AccessibilityController mAccessibilityController;
90    private PhoneStatusBar mPhoneStatusBar;
91
92    private final TrustDrawable mTrustDrawable;
93
94    public KeyguardBottomAreaView(Context context) {
95        this(context, null);
96    }
97
98    public KeyguardBottomAreaView(Context context, AttributeSet attrs) {
99        this(context, attrs, 0);
100    }
101
102    public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr) {
103        this(context, attrs, defStyleAttr, 0);
104    }
105
106    public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr,
107            int defStyleRes) {
108        super(context, attrs, defStyleAttr, defStyleRes);
109        mTrustDrawable = new TrustDrawable(mContext);
110    }
111
112    private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
113        @Override
114        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
115            super.onInitializeAccessibilityNodeInfo(host, info);
116            String label = null;
117            if (host == mLockIcon) {
118                label = getResources().getString(R.string.unlock_label);
119            } else if (host == mCameraImageView) {
120                label = getResources().getString(R.string.camera_label);
121            } else if (host == mPhoneImageView) {
122                label = getResources().getString(R.string.phone_label);
123            }
124            info.addAction(new AccessibilityAction(ACTION_CLICK, label));
125        }
126
127        @Override
128        public boolean performAccessibilityAction(View host, int action, Bundle args) {
129            if (action == ACTION_CLICK) {
130                if (host == mLockIcon) {
131                    mPhoneStatusBar.animateCollapsePanels(
132                            CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
133                    return true;
134                } else if (host == mCameraImageView) {
135                    launchCamera();
136                    return true;
137                } else if (host == mPhoneImageView) {
138                    launchPhone();
139                    return true;
140                }
141            }
142            return super.performAccessibilityAction(host, action, args);
143        }
144    };
145
146    @Override
147    protected void onFinishInflate() {
148        super.onFinishInflate();
149        mLockPatternUtils = new LockPatternUtils(mContext);
150        mPreviewContainer = (ViewGroup) findViewById(R.id.preview_container);
151        mCameraImageView = (KeyguardAffordanceView) findViewById(R.id.camera_button);
152        mPhoneImageView = (KeyguardAffordanceView) findViewById(R.id.phone_button);
153        mLockIcon = (KeyguardAffordanceView) findViewById(R.id.lock_icon);
154        mIndicationText = (TextView) findViewById(R.id.keyguard_indication_text);
155        watchForCameraPolicyChanges();
156        updateCameraVisibility();
157        updatePhoneVisibility();
158        mUnlockMethodCache = UnlockMethodCache.getInstance(getContext());
159        mUnlockMethodCache.addListener(this);
160        updateLockIcon();
161        setClipChildren(false);
162        setClipToPadding(false);
163        mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext));
164        inflatePreviews();
165        mLockIcon.setOnClickListener(this);
166        mLockIcon.setBackground(mTrustDrawable);
167        mLockIcon.setOnLongClickListener(this);
168        mCameraImageView.setOnClickListener(this);
169        mPhoneImageView.setOnClickListener(this);
170        initAccessibility();
171    }
172
173    private void initAccessibility() {
174        mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
175        mPhoneImageView.setAccessibilityDelegate(mAccessibilityDelegate);
176        mCameraImageView.setAccessibilityDelegate(mAccessibilityDelegate);
177    }
178
179    @Override
180    protected void onConfigurationChanged(Configuration newConfig) {
181        super.onConfigurationChanged(newConfig);
182        int indicationBottomMargin = getResources().getDimensionPixelSize(
183                R.dimen.keyguard_indication_margin_bottom);
184        MarginLayoutParams mlp = (MarginLayoutParams) mIndicationText.getLayoutParams();
185        if (mlp.bottomMargin != indicationBottomMargin) {
186            mlp.bottomMargin = indicationBottomMargin;
187            mIndicationText.setLayoutParams(mlp);
188        }
189
190        // Respect font size setting.
191        mIndicationText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
192                getResources().getDimensionPixelSize(
193                        com.android.internal.R.dimen.text_size_small_material));
194    }
195
196    public void setActivityStarter(ActivityStarter activityStarter) {
197        mActivityStarter = activityStarter;
198    }
199
200    public void setFlashlightController(FlashlightController flashlightController) {
201        mFlashlightController = flashlightController;
202    }
203
204    public void setAccessibilityController(AccessibilityController accessibilityController) {
205        mAccessibilityController = accessibilityController;
206        accessibilityController.addStateChangedCallback(this);
207    }
208
209    public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
210        mPhoneStatusBar = phoneStatusBar;
211    }
212
213    private Intent getCameraIntent() {
214        KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
215        boolean currentUserHasTrust = updateMonitor.getUserHasTrust(
216                mLockPatternUtils.getCurrentUser());
217        return mLockPatternUtils.isSecure() && !currentUserHasTrust
218                ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
219    }
220
221    private void updateCameraVisibility() {
222        ResolveInfo resolved = mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(),
223                PackageManager.MATCH_DEFAULT_ONLY,
224                mLockPatternUtils.getCurrentUser());
225        boolean visible = !isCameraDisabledByDpm() && resolved != null;
226        mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
227    }
228
229    private void updatePhoneVisibility() {
230        boolean visible = isPhoneVisible();
231        mPhoneImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
232    }
233
234    private boolean isPhoneVisible() {
235        PackageManager pm = mContext.getPackageManager();
236        return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
237                && pm.resolveActivity(PHONE_INTENT, 0) != null;
238    }
239
240    private boolean isCameraDisabledByDpm() {
241        final DevicePolicyManager dpm =
242                (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
243        if (dpm != null) {
244            try {
245                final int userId = ActivityManagerNative.getDefault().getCurrentUser().id;
246                final int disabledFlags = dpm.getKeyguardDisabledFeatures(null, userId);
247                final  boolean disabledBecauseKeyguardSecure =
248                        (disabledFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0
249                                && KeyguardTouchDelegate.getInstance(getContext()).isSecure();
250                return dpm.getCameraDisabled(null) || disabledBecauseKeyguardSecure;
251            } catch (RemoteException e) {
252                Log.e(TAG, "Can't get userId", e);
253            }
254        }
255        return false;
256    }
257
258    private void watchForCameraPolicyChanges() {
259        final IntentFilter filter = new IntentFilter();
260        filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
261        getContext().registerReceiverAsUser(mDevicePolicyReceiver,
262                UserHandle.ALL, filter, null, null);
263        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
264    }
265
266    @Override
267    public void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled) {
268        mCameraImageView.setClickable(touchExplorationEnabled);
269        mPhoneImageView.setClickable(touchExplorationEnabled);
270        mCameraImageView.setFocusable(accessibilityEnabled);
271        mPhoneImageView.setFocusable(accessibilityEnabled);
272        updateLockIconClickability();
273    }
274
275    private void updateLockIconClickability() {
276        if (mAccessibilityController == null) {
277            return;
278        }
279        mLockIcon.setClickable(mUnlockMethodCache.isTrustManaged()
280                || mAccessibilityController.isTouchExplorationEnabled());
281        mLockIcon.setLongClickable(mAccessibilityController.isTouchExplorationEnabled()
282                && mUnlockMethodCache.isTrustManaged());
283        mLockIcon.setFocusable(mAccessibilityController.isAccessibilityEnabled());
284    }
285
286    @Override
287    public void onClick(View v) {
288        if (v == mCameraImageView) {
289            launchCamera();
290        } else if (v == mPhoneImageView) {
291            launchPhone();
292        } if (v == mLockIcon) {
293            if (!mAccessibilityController.isAccessibilityEnabled()) {
294                handleTrustCircleClick();
295            } else {
296                mPhoneStatusBar.animateCollapsePanels(
297                        CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
298            }
299        }
300    }
301
302    @Override
303    public boolean onLongClick(View v) {
304        handleTrustCircleClick();
305        return true;
306    }
307
308    private void handleTrustCircleClick() {
309        mIndicationController.showTransientIndication(
310                R.string.keyguard_indication_trust_disabled);
311        mLockPatternUtils.requireCredentialEntry(mLockPatternUtils.getCurrentUser());
312    }
313
314    public void launchCamera() {
315        mFlashlightController.killFlashlight();
316        Intent intent = getCameraIntent();
317        if (intent == SECURE_CAMERA_INTENT &&
318                !mPreviewInflater.wouldLaunchResolverActivity(intent)) {
319            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
320        } else {
321            mActivityStarter.startActivity(intent, false /* dismissShade */);
322        }
323    }
324
325    public void launchPhone() {
326        final TelecommManager tm = TelecommManager.from(mContext);
327        if (tm.isInCall()) {
328            AsyncTask.execute(new Runnable() {
329                @Override
330                public void run() {
331                    tm.showInCallScreen(false /* showDialpad */);
332                }
333            });
334        } else {
335            mActivityStarter.startActivity(PHONE_INTENT, false /* dismissShade */);
336        }
337    }
338
339
340    @Override
341    protected void onVisibilityChanged(View changedView, int visibility) {
342        super.onVisibilityChanged(changedView, visibility);
343        if (isShown()) {
344            mTrustDrawable.start();
345        } else {
346            mTrustDrawable.stop();
347        }
348        if (changedView == this && visibility == VISIBLE) {
349            updateLockIcon();
350            updateCameraVisibility();
351        }
352    }
353
354    @Override
355    protected void onDetachedFromWindow() {
356        super.onDetachedFromWindow();
357        mTrustDrawable.stop();
358    }
359
360    private void updateLockIcon() {
361        boolean visible = isShown() && KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
362        if (visible) {
363            mTrustDrawable.start();
364        } else {
365            mTrustDrawable.stop();
366        }
367        if (!visible) {
368            return;
369        }
370        // TODO: Real icon for facelock.
371        int iconRes = mUnlockMethodCache.isFaceUnlockRunning() ? R.drawable.ic_account_circle
372                : mUnlockMethodCache.isMethodInsecure() ? R.drawable.ic_lock_open_24dp
373                : R.drawable.ic_lock_24dp;
374        mLockIcon.setImageResource(iconRes);
375        boolean trustManaged = mUnlockMethodCache.isTrustManaged();
376        mTrustDrawable.setTrustManaged(trustManaged);
377
378        updateLockIconClickability();
379        updateLockIconContentDescription(mUnlockMethodCache.isFaceUnlockRunning(),
380                mUnlockMethodCache.isMethodInsecure(), trustManaged);
381    }
382
383    private void updateLockIconContentDescription(boolean faceUnlockRunning, boolean insecure,
384            boolean trustManaged) {
385        mLockIcon.setContentDescription(getResources().getString(
386                faceUnlockRunning ? R.string.accessibility_unlock_button_face_unlock_running
387                : insecure && !trustManaged ? R.string.accessibility_unlock_button_not_secured
388                : insecure ? R.string.accessibility_unlock_button_not_secured_trust_managed
389                : !trustManaged ? R.string.accessibility_unlock_button_secured
390                : R.string.accessibility_unlock_button_secured_trust_managed));
391    }
392
393    public KeyguardAffordanceView getPhoneView() {
394        return mPhoneImageView;
395    }
396
397    public KeyguardAffordanceView getCameraView() {
398        return mCameraImageView;
399    }
400
401    public View getPhonePreview() {
402        return mPhonePreview;
403    }
404
405    public View getCameraPreview() {
406        return mCameraPreview;
407    }
408
409    public KeyguardAffordanceView getLockIcon() {
410        return mLockIcon;
411    }
412
413    public View getIndicationView() {
414        return mIndicationText;
415    }
416
417    @Override
418    public boolean hasOverlappingRendering() {
419        return false;
420    }
421
422    @Override
423    public void onMethodSecureChanged(boolean methodSecure) {
424        updateLockIcon();
425        updateCameraVisibility();
426    }
427
428    private void inflatePreviews() {
429        mPhonePreview = mPreviewInflater.inflatePreview(PHONE_INTENT);
430        mCameraPreview = mPreviewInflater.inflatePreview(getCameraIntent());
431        if (mPhonePreview != null) {
432            mPreviewContainer.addView(mPhonePreview);
433            mPhonePreview.setVisibility(View.INVISIBLE);
434        }
435        if (mCameraPreview != null) {
436            mPreviewContainer.addView(mCameraPreview);
437            mCameraPreview.setVisibility(View.INVISIBLE);
438        }
439    }
440
441    private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() {
442        public void onReceive(Context context, Intent intent) {
443            post(new Runnable() {
444                @Override
445                public void run() {
446                    updateCameraVisibility();
447                }
448            });
449        }
450    };
451
452    private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
453            new KeyguardUpdateMonitorCallback() {
454        @Override
455        public void onUserSwitchComplete(int userId) {
456            updateCameraVisibility();
457        }
458
459        @Override
460        public void onScreenTurnedOn() {
461            updateLockIcon();
462        }
463
464        @Override
465        public void onScreenTurnedOff(int why) {
466            updateLockIcon();
467        }
468    };
469
470    public void setKeyguardIndicationController(
471            KeyguardIndicationController keyguardIndicationController) {
472        mIndicationController = keyguardIndicationController;
473    }
474}
475