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