KeyguardBottomAreaView.java revision 64d4dca63f65e4c7d4a829c85ff6670bdd34e2eb
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                    if (PhoneStatusBar.DEBUG_EMPTY_KEYGUARD) {
132                        Log.i(TAG, "Collapsing panel from lock icon accessibility click");
133                    }
134                    mPhoneStatusBar.animateCollapsePanels(
135                            CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
136                    return true;
137                } else if (host == mCameraImageView) {
138                    launchCamera();
139                    return true;
140                } else if (host == mPhoneImageView) {
141                    launchPhone();
142                    return true;
143                }
144            }
145            return super.performAccessibilityAction(host, action, args);
146        }
147    };
148
149    @Override
150    protected void onFinishInflate() {
151        super.onFinishInflate();
152        mLockPatternUtils = new LockPatternUtils(mContext);
153        mPreviewContainer = (ViewGroup) findViewById(R.id.preview_container);
154        mCameraImageView = (KeyguardAffordanceView) findViewById(R.id.camera_button);
155        mPhoneImageView = (KeyguardAffordanceView) findViewById(R.id.phone_button);
156        mLockIcon = (KeyguardAffordanceView) findViewById(R.id.lock_icon);
157        mIndicationText = (TextView) findViewById(R.id.keyguard_indication_text);
158        watchForCameraPolicyChanges();
159        updateCameraVisibility();
160        updatePhoneVisibility();
161        mUnlockMethodCache = UnlockMethodCache.getInstance(getContext());
162        mUnlockMethodCache.addListener(this);
163        updateLockIcon();
164        setClipChildren(false);
165        setClipToPadding(false);
166        mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext));
167        inflatePreviews();
168        mLockIcon.setOnClickListener(this);
169        mLockIcon.setBackground(mTrustDrawable);
170        mLockIcon.setOnLongClickListener(this);
171        mCameraImageView.setOnClickListener(this);
172        mPhoneImageView.setOnClickListener(this);
173        initAccessibility();
174    }
175
176    private void initAccessibility() {
177        mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
178        mPhoneImageView.setAccessibilityDelegate(mAccessibilityDelegate);
179        mCameraImageView.setAccessibilityDelegate(mAccessibilityDelegate);
180    }
181
182    @Override
183    protected void onConfigurationChanged(Configuration newConfig) {
184        super.onConfigurationChanged(newConfig);
185        int indicationBottomMargin = getResources().getDimensionPixelSize(
186                R.dimen.keyguard_indication_margin_bottom);
187        MarginLayoutParams mlp = (MarginLayoutParams) mIndicationText.getLayoutParams();
188        if (mlp.bottomMargin != indicationBottomMargin) {
189            mlp.bottomMargin = indicationBottomMargin;
190            mIndicationText.setLayoutParams(mlp);
191        }
192
193        // Respect font size setting.
194        mIndicationText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
195                getResources().getDimensionPixelSize(
196                        com.android.internal.R.dimen.text_size_small_material));
197    }
198
199    public void setActivityStarter(ActivityStarter activityStarter) {
200        mActivityStarter = activityStarter;
201    }
202
203    public void setFlashlightController(FlashlightController flashlightController) {
204        mFlashlightController = flashlightController;
205    }
206
207    public void setAccessibilityController(AccessibilityController accessibilityController) {
208        mAccessibilityController = accessibilityController;
209        accessibilityController.addStateChangedCallback(this);
210    }
211
212    public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
213        mPhoneStatusBar = phoneStatusBar;
214    }
215
216    private Intent getCameraIntent() {
217        KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
218        boolean currentUserHasTrust = updateMonitor.getUserHasTrust(
219                mLockPatternUtils.getCurrentUser());
220        return mLockPatternUtils.isSecure() && !currentUserHasTrust
221                ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
222    }
223
224    private void updateCameraVisibility() {
225        ResolveInfo resolved = mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(),
226                PackageManager.MATCH_DEFAULT_ONLY,
227                mLockPatternUtils.getCurrentUser());
228        boolean visible = !isCameraDisabledByDpm() && resolved != null
229                && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance);
230        mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
231    }
232
233    private void updatePhoneVisibility() {
234        boolean visible = isPhoneVisible();
235        mPhoneImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
236    }
237
238    private boolean isPhoneVisible() {
239        PackageManager pm = mContext.getPackageManager();
240        return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
241                && pm.resolveActivity(PHONE_INTENT, 0) != null;
242    }
243
244    private boolean isCameraDisabledByDpm() {
245        final DevicePolicyManager dpm =
246                (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
247        if (dpm != null) {
248            try {
249                final int userId = ActivityManagerNative.getDefault().getCurrentUser().id;
250                final int disabledFlags = dpm.getKeyguardDisabledFeatures(null, userId);
251                final  boolean disabledBecauseKeyguardSecure =
252                        (disabledFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0
253                                && KeyguardTouchDelegate.getInstance(getContext()).isSecure();
254                return dpm.getCameraDisabled(null) || disabledBecauseKeyguardSecure;
255            } catch (RemoteException e) {
256                Log.e(TAG, "Can't get userId", e);
257            }
258        }
259        return false;
260    }
261
262    private void watchForCameraPolicyChanges() {
263        final IntentFilter filter = new IntentFilter();
264        filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
265        getContext().registerReceiverAsUser(mDevicePolicyReceiver,
266                UserHandle.ALL, filter, null, null);
267        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
268    }
269
270    @Override
271    public void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled) {
272        mCameraImageView.setClickable(touchExplorationEnabled);
273        mPhoneImageView.setClickable(touchExplorationEnabled);
274        mCameraImageView.setFocusable(accessibilityEnabled);
275        mPhoneImageView.setFocusable(accessibilityEnabled);
276        updateLockIconClickability();
277    }
278
279    private void updateLockIconClickability() {
280        if (mAccessibilityController == null) {
281            return;
282        }
283        mLockIcon.setClickable(mUnlockMethodCache.isTrustManaged()
284                || mAccessibilityController.isTouchExplorationEnabled());
285        mLockIcon.setLongClickable(mAccessibilityController.isTouchExplorationEnabled()
286                && mUnlockMethodCache.isTrustManaged());
287        mLockIcon.setFocusable(mAccessibilityController.isAccessibilityEnabled());
288    }
289
290    @Override
291    public void onClick(View v) {
292        if (v == mCameraImageView) {
293            launchCamera();
294        } else if (v == mPhoneImageView) {
295            launchPhone();
296        } if (v == mLockIcon) {
297            if (!mAccessibilityController.isAccessibilityEnabled()) {
298                handleTrustCircleClick();
299            } else {
300                mPhoneStatusBar.animateCollapsePanels(
301                        CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
302            }
303        }
304    }
305
306    @Override
307    public boolean onLongClick(View v) {
308        handleTrustCircleClick();
309        return true;
310    }
311
312    private void handleTrustCircleClick() {
313        mIndicationController.showTransientIndication(
314                R.string.keyguard_indication_trust_disabled);
315        mLockPatternUtils.requireCredentialEntry(mLockPatternUtils.getCurrentUser());
316    }
317
318    public void launchCamera() {
319        mFlashlightController.killFlashlight();
320        Intent intent = getCameraIntent();
321        boolean wouldLaunchResolverActivity = PreviewInflater.wouldLaunchResolverActivity(
322                mContext, intent, mLockPatternUtils.getCurrentUser());
323        if (intent == SECURE_CAMERA_INTENT && !wouldLaunchResolverActivity) {
324            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
325        } else {
326
327            // We need to delay starting the activity because ResolverActivity finishes itself if
328            // launched behind lockscreen.
329            mActivityStarter.startActivity(intent, false /* dismissShade */);
330        }
331    }
332
333    public void launchPhone() {
334        final TelecommManager tm = TelecommManager.from(mContext);
335        if (tm.isInCall()) {
336            AsyncTask.execute(new Runnable() {
337                @Override
338                public void run() {
339                    tm.showInCallScreen(false /* showDialpad */);
340                }
341            });
342        } else {
343            mActivityStarter.startActivity(PHONE_INTENT, false /* dismissShade */);
344        }
345    }
346
347
348    @Override
349    protected void onVisibilityChanged(View changedView, int visibility) {
350        super.onVisibilityChanged(changedView, visibility);
351        if (isShown()) {
352            mTrustDrawable.start();
353        } else {
354            mTrustDrawable.stop();
355        }
356        if (changedView == this && visibility == VISIBLE) {
357            updateLockIcon();
358            updateCameraVisibility();
359        }
360    }
361
362    @Override
363    protected void onDetachedFromWindow() {
364        super.onDetachedFromWindow();
365        mTrustDrawable.stop();
366    }
367
368    private void updateLockIcon() {
369        boolean visible = isShown() && KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
370        if (visible) {
371            mTrustDrawable.start();
372        } else {
373            mTrustDrawable.stop();
374        }
375        if (!visible) {
376            return;
377        }
378        // TODO: Real icon for facelock.
379        int iconRes = mUnlockMethodCache.isFaceUnlockRunning()
380                ? com.android.internal.R.drawable.ic_account_circle
381                : mUnlockMethodCache.isMethodInsecure() ? R.drawable.ic_lock_open_24dp
382                : R.drawable.ic_lock_24dp;
383        mLockIcon.setImageResource(iconRes);
384        boolean trustManaged = mUnlockMethodCache.isTrustManaged();
385        mTrustDrawable.setTrustManaged(trustManaged);
386        updateLockIconClickability();
387    }
388
389
390
391    public KeyguardAffordanceView getPhoneView() {
392        return mPhoneImageView;
393    }
394
395    public KeyguardAffordanceView getCameraView() {
396        return mCameraImageView;
397    }
398
399    public View getPhonePreview() {
400        return mPhonePreview;
401    }
402
403    public View getCameraPreview() {
404        return mCameraPreview;
405    }
406
407    public KeyguardAffordanceView getLockIcon() {
408        return mLockIcon;
409    }
410
411    public View getIndicationView() {
412        return mIndicationText;
413    }
414
415    @Override
416    public boolean hasOverlappingRendering() {
417        return false;
418    }
419
420    @Override
421    public void onMethodSecureChanged(boolean methodSecure) {
422        updateLockIcon();
423        updateCameraVisibility();
424    }
425
426    private void inflatePreviews() {
427        mPhonePreview = mPreviewInflater.inflatePreview(PHONE_INTENT);
428        mCameraPreview = mPreviewInflater.inflatePreview(getCameraIntent());
429        if (mPhonePreview != null) {
430            mPreviewContainer.addView(mPhonePreview);
431            mPhonePreview.setVisibility(View.INVISIBLE);
432        }
433        if (mCameraPreview != null) {
434            mPreviewContainer.addView(mCameraPreview);
435            mCameraPreview.setVisibility(View.INVISIBLE);
436        }
437    }
438
439    private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() {
440        public void onReceive(Context context, Intent intent) {
441            post(new Runnable() {
442                @Override
443                public void run() {
444                    updateCameraVisibility();
445                }
446            });
447        }
448    };
449
450    private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
451            new KeyguardUpdateMonitorCallback() {
452        @Override
453        public void onUserSwitchComplete(int userId) {
454            updateCameraVisibility();
455        }
456
457        @Override
458        public void onScreenTurnedOn() {
459            updateLockIcon();
460        }
461
462        @Override
463        public void onScreenTurnedOff(int why) {
464            updateLockIcon();
465        }
466    };
467
468    public void setKeyguardIndicationController(
469            KeyguardIndicationController keyguardIndicationController) {
470        mIndicationController = keyguardIndicationController;
471    }
472}
473