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 
17 package com.android.systemui.statusbar.phone;
18 
19 import android.app.ActivityManagerNative;
20 import android.app.admin.DevicePolicyManager;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ResolveInfo;
27 import android.content.res.Configuration;
28 import android.graphics.drawable.Drawable;
29 import android.graphics.drawable.InsetDrawable;
30 import android.os.AsyncTask;
31 import android.os.Bundle;
32 import android.os.RemoteException;
33 import android.os.UserHandle;
34 import android.provider.MediaStore;
35 import android.telecom.TelecomManager;
36 import android.util.AttributeSet;
37 import android.util.Log;
38 import android.util.TypedValue;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.view.accessibility.AccessibilityNodeInfo;
42 import android.view.animation.AnimationUtils;
43 import android.view.animation.Interpolator;
44 import android.widget.FrameLayout;
45 import android.widget.TextView;
46 
47 import com.android.internal.widget.LockPatternUtils;
48 import com.android.keyguard.KeyguardUpdateMonitor;
49 import com.android.keyguard.KeyguardUpdateMonitorCallback;
50 import com.android.systemui.EventLogConstants;
51 import com.android.systemui.EventLogTags;
52 import com.android.systemui.R;
53 import com.android.systemui.statusbar.CommandQueue;
54 import com.android.systemui.statusbar.KeyguardAffordanceView;
55 import com.android.systemui.statusbar.KeyguardIndicationController;
56 import com.android.systemui.statusbar.policy.AccessibilityController;
57 import com.android.systemui.statusbar.policy.FlashlightController;
58 import com.android.systemui.statusbar.policy.PreviewInflater;
59 
60 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
61 import 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  */
67 public 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 
KeyguardBottomAreaView(Context context)104     public KeyguardBottomAreaView(Context context) {
105         this(context, null);
106     }
107 
KeyguardBottomAreaView(Context context, AttributeSet attrs)108     public KeyguardBottomAreaView(Context context, AttributeSet attrs) {
109         this(context, attrs, 0);
110     }
111 
KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr)112     public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr) {
113         this(context, attrs, defStyleAttr, 0);
114     }
115 
KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)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
onFinishInflate()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 
initAccessibility()185     private void initAccessibility() {
186         mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
187         mPhoneImageView.setAccessibilityDelegate(mAccessibilityDelegate);
188         mCameraImageView.setAccessibilityDelegate(mAccessibilityDelegate);
189     }
190 
191     @Override
onConfigurationChanged(Configuration newConfig)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 
setActivityStarter(ActivityStarter activityStarter)208     public void setActivityStarter(ActivityStarter activityStarter) {
209         mActivityStarter = activityStarter;
210     }
211 
setFlashlightController(FlashlightController flashlightController)212     public void setFlashlightController(FlashlightController flashlightController) {
213         mFlashlightController = flashlightController;
214     }
215 
setAccessibilityController(AccessibilityController accessibilityController)216     public void setAccessibilityController(AccessibilityController accessibilityController) {
217         mAccessibilityController = accessibilityController;
218         accessibilityController.addStateChangedCallback(this);
219     }
220 
setPhoneStatusBar(PhoneStatusBar phoneStatusBar)221     public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
222         mPhoneStatusBar = phoneStatusBar;
223     }
224 
getCameraIntent()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 
updateCameraVisibility()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 
updatePhoneVisibility()242     private void updatePhoneVisibility() {
243         boolean visible = isPhoneVisible();
244         mPhoneImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
245     }
246 
isPhoneVisible()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 
isCameraDisabledByDpm()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 
watchForCameraPolicyChanges()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
onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled)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 
updateLockIconClickability()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
onClick(View v)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
onLongClick(View v)319     public boolean onLongClick(View v) {
320         handleTrustCircleClick();
321         return true;
322     }
323 
handleTrustCircleClick()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 
launchCamera()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 
launchPhone()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
onVisibilityChanged(View changedView, int visibility)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
onDetachedFromWindow()378     protected void onDetachedFromWindow() {
379         super.onDetachedFromWindow();
380         mTrustDrawable.stop();
381     }
382 
updateLockIcon()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 
getPhoneView()416     public KeyguardAffordanceView getPhoneView() {
417         return mPhoneImageView;
418     }
419 
getCameraView()420     public KeyguardAffordanceView getCameraView() {
421         return mCameraImageView;
422     }
423 
getPhonePreview()424     public View getPhonePreview() {
425         return mPhonePreview;
426     }
427 
getCameraPreview()428     public View getCameraPreview() {
429         return mCameraPreview;
430     }
431 
getLockIcon()432     public KeyguardAffordanceView getLockIcon() {
433         return mLockIcon;
434     }
435 
getIndicationView()436     public View getIndicationView() {
437         return mIndicationText;
438     }
439 
440     @Override
hasOverlappingRendering()441     public boolean hasOverlappingRendering() {
442         return false;
443     }
444 
445     @Override
onUnlockMethodStateChanged()446     public void onUnlockMethodStateChanged() {
447         updateLockIcon();
448         updateCameraVisibility();
449     }
450 
inflatePreviews()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 
startFinishDozeAnimation()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 
startFinishDozeAnimationElement(View element, long delay)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 
setKeyguardIndicationController( KeyguardIndicationController keyguardIndicationController)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 
IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight)541         public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) {
542             super(drawable, 0);
543             mIntrinsicWidth = intrinsicWidth;
544             mIntrinsicHeight = intrinsicHeight;
545         }
546 
547         @Override
getIntrinsicWidth()548         public int getIntrinsicWidth() {
549             return mIntrinsicWidth;
550         }
551 
552         @Override
getIntrinsicHeight()553         public int getIntrinsicHeight() {
554             return mIntrinsicHeight;
555         }
556     }
557 }
558