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 static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
20import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
21
22import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON;
23import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK;
24import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON;
25import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_UNLOCK;
26
27import android.app.ActivityManager;
28import android.app.ActivityOptions;
29import android.app.admin.DevicePolicyManager;
30import android.content.BroadcastReceiver;
31import android.content.ComponentName;
32import android.content.Context;
33import android.content.Intent;
34import android.content.IntentFilter;
35import android.content.ServiceConnection;
36import android.content.pm.ActivityInfo;
37import android.content.pm.PackageManager;
38import android.content.pm.ResolveInfo;
39import android.content.res.Configuration;
40import android.graphics.drawable.Drawable;
41import android.os.AsyncTask;
42import android.os.Bundle;
43import android.os.IBinder;
44import android.os.Message;
45import android.os.Messenger;
46import android.os.RemoteException;
47import android.os.UserHandle;
48import android.provider.MediaStore;
49import android.service.media.CameraPrewarmService;
50import android.telecom.TelecomManager;
51import android.text.TextUtils;
52import android.util.AttributeSet;
53import android.util.Log;
54import android.util.TypedValue;
55import android.view.View;
56import android.view.ViewGroup;
57import android.view.WindowManager;
58import android.view.accessibility.AccessibilityNodeInfo;
59import android.widget.FrameLayout;
60import android.widget.TextView;
61
62import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
63import com.android.internal.widget.LockPatternUtils;
64import com.android.keyguard.KeyguardUpdateMonitor;
65import com.android.keyguard.KeyguardUpdateMonitorCallback;
66import com.android.systemui.EventLogTags;
67import com.android.systemui.Dependency;
68import com.android.systemui.Interpolators;
69import com.android.systemui.R;
70import com.android.systemui.assist.AssistManager;
71import com.android.systemui.plugins.IntentButtonProvider;
72import com.android.systemui.plugins.IntentButtonProvider.IntentButton;
73import com.android.systemui.plugins.IntentButtonProvider.IntentButton.IconState;
74import com.android.systemui.plugins.PluginListener;
75import com.android.systemui.plugins.PluginManager;
76import com.android.systemui.plugins.ActivityStarter;
77import com.android.systemui.statusbar.CommandQueue;
78import com.android.systemui.statusbar.KeyguardAffordanceView;
79import com.android.systemui.statusbar.KeyguardIndicationController;
80import com.android.systemui.statusbar.policy.AccessibilityController;
81import com.android.systemui.statusbar.policy.ExtensionController;
82import com.android.systemui.statusbar.policy.ExtensionController.Extension;
83import com.android.systemui.statusbar.policy.FlashlightController;
84import com.android.systemui.statusbar.policy.PreviewInflater;
85import com.android.systemui.tuner.LockscreenFragment;
86import com.android.systemui.tuner.LockscreenFragment.LockButtonFactory;
87import com.android.systemui.tuner.TunerService;
88import com.android.systemui.tuner.TunerService.Tunable;
89
90/**
91 * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
92 * text.
93 */
94public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener,
95        UnlockMethodCache.OnUnlockMethodChangedListener,
96        AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener {
97
98    final static String TAG = "StatusBar/KeyguardBottomAreaView";
99
100    public static final String CAMERA_LAUNCH_SOURCE_AFFORDANCE = "lockscreen_affordance";
101    public static final String CAMERA_LAUNCH_SOURCE_WIGGLE = "wiggle_gesture";
102    public static final String CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = "power_double_tap";
103
104    public static final String EXTRA_CAMERA_LAUNCH_SOURCE
105            = "com.android.systemui.camera_launch_source";
106
107    private static final String LEFT_BUTTON_PLUGIN
108            = "com.android.systemui.action.PLUGIN_LOCKSCREEN_LEFT_BUTTON";
109    private static final String RIGHT_BUTTON_PLUGIN
110            = "com.android.systemui.action.PLUGIN_LOCKSCREEN_RIGHT_BUTTON";
111
112    private static final Intent SECURE_CAMERA_INTENT =
113            new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
114                    .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
115    public static final Intent INSECURE_CAMERA_INTENT =
116            new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
117    private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);
118    private static final int DOZE_ANIMATION_STAGGER_DELAY = 48;
119    private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250;
120
121    private KeyguardAffordanceView mRightAffordanceView;
122    private KeyguardAffordanceView mLeftAffordanceView;
123    private LockIcon mLockIcon;
124    private ViewGroup mIndicationArea;
125    private TextView mEnterpriseDisclosure;
126    private TextView mIndicationText;
127    private ViewGroup mPreviewContainer;
128
129    private View mLeftPreview;
130    private View mCameraPreview;
131
132    private ActivityStarter mActivityStarter;
133    private UnlockMethodCache mUnlockMethodCache;
134    private LockPatternUtils mLockPatternUtils;
135    private FlashlightController mFlashlightController;
136    private PreviewInflater mPreviewInflater;
137    private KeyguardIndicationController mIndicationController;
138    private AccessibilityController mAccessibilityController;
139    private StatusBar mStatusBar;
140    private KeyguardAffordanceHelper mAffordanceHelper;
141
142    private boolean mUserSetupComplete;
143    private boolean mPrewarmBound;
144    private Messenger mPrewarmMessenger;
145    private final ServiceConnection mPrewarmConnection = new ServiceConnection() {
146
147        @Override
148        public void onServiceConnected(ComponentName name, IBinder service) {
149            mPrewarmMessenger = new Messenger(service);
150        }
151
152        @Override
153        public void onServiceDisconnected(ComponentName name) {
154            mPrewarmMessenger = null;
155        }
156    };
157
158    private boolean mLeftIsVoiceAssist;
159    private AssistManager mAssistManager;
160    private Drawable mLeftAssistIcon;
161
162    private IntentButton mRightButton = new DefaultRightButton();
163    private Extension<IntentButton> mRightExtension;
164    private String mRightButtonStr;
165    private IntentButton mLeftButton = new DefaultLeftButton();
166    private Extension<IntentButton> mLeftExtension;
167    private String mLeftButtonStr;
168    private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
169    private boolean mDozing;
170
171    public KeyguardBottomAreaView(Context context) {
172        this(context, null);
173    }
174
175    public KeyguardBottomAreaView(Context context, AttributeSet attrs) {
176        this(context, attrs, 0);
177    }
178
179    public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr) {
180        this(context, attrs, defStyleAttr, 0);
181    }
182
183    public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr,
184            int defStyleRes) {
185        super(context, attrs, defStyleAttr, defStyleRes);
186    }
187
188    private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
189        @Override
190        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
191            super.onInitializeAccessibilityNodeInfo(host, info);
192            String label = null;
193            if (host == mLockIcon) {
194                label = getResources().getString(R.string.unlock_label);
195            } else if (host == mRightAffordanceView) {
196                label = getResources().getString(R.string.camera_label);
197            } else if (host == mLeftAffordanceView) {
198                if (mLeftIsVoiceAssist) {
199                    label = getResources().getString(R.string.voice_assist_label);
200                } else {
201                    label = getResources().getString(R.string.phone_label);
202                }
203            }
204            info.addAction(new AccessibilityAction(ACTION_CLICK, label));
205        }
206
207        @Override
208        public boolean performAccessibilityAction(View host, int action, Bundle args) {
209            if (action == ACTION_CLICK) {
210                if (host == mLockIcon) {
211                    mStatusBar.animateCollapsePanels(
212                            CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
213                    return true;
214                } else if (host == mRightAffordanceView) {
215                    launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
216                    return true;
217                } else if (host == mLeftAffordanceView) {
218                    launchLeftAffordance();
219                    return true;
220                }
221            }
222            return super.performAccessibilityAction(host, action, args);
223        }
224    };
225
226    @Override
227    protected void onFinishInflate() {
228        super.onFinishInflate();
229        mLockPatternUtils = new LockPatternUtils(mContext);
230        mPreviewContainer = findViewById(R.id.preview_container);
231        mRightAffordanceView = findViewById(R.id.camera_button);
232        mLeftAffordanceView = findViewById(R.id.left_button);
233        mLockIcon = findViewById(R.id.lock_icon);
234        mIndicationArea = findViewById(R.id.keyguard_indication_area);
235        mEnterpriseDisclosure = findViewById(
236                R.id.keyguard_indication_enterprise_disclosure);
237        mIndicationText = findViewById(R.id.keyguard_indication_text);
238        watchForCameraPolicyChanges();
239        updateCameraVisibility();
240        mUnlockMethodCache = UnlockMethodCache.getInstance(getContext());
241        mUnlockMethodCache.addListener(this);
242        mLockIcon.update();
243        setClipChildren(false);
244        setClipToPadding(false);
245        mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext));
246        inflateCameraPreview();
247        mLockIcon.setOnClickListener(this);
248        mLockIcon.setOnLongClickListener(this);
249        mRightAffordanceView.setOnClickListener(this);
250        mLeftAffordanceView.setOnClickListener(this);
251        initAccessibility();
252        mActivityStarter = Dependency.get(ActivityStarter.class);
253        mFlashlightController = Dependency.get(FlashlightController.class);
254        mAccessibilityController = Dependency.get(AccessibilityController.class);
255        mAssistManager = Dependency.get(AssistManager.class);
256        updateLeftAffordance();
257    }
258
259    @Override
260    protected void onAttachedToWindow() {
261        super.onAttachedToWindow();
262        mAccessibilityController.addStateChangedCallback(this);
263        mRightExtension = Dependency.get(ExtensionController.class).newExtension(IntentButton.class)
264                .withPlugin(IntentButtonProvider.class, RIGHT_BUTTON_PLUGIN,
265                        p -> p.getIntentButton())
266                .withTunerFactory(new LockButtonFactory(mContext, LOCKSCREEN_RIGHT_BUTTON))
267                .withDefault(() -> new DefaultRightButton())
268                .withCallback(button -> setRightButton(button))
269                .build();
270        mLeftExtension = Dependency.get(ExtensionController.class).newExtension(IntentButton.class)
271                .withPlugin(IntentButtonProvider.class, LEFT_BUTTON_PLUGIN,
272                        p -> p.getIntentButton())
273                .withTunerFactory(new LockButtonFactory(mContext, LOCKSCREEN_LEFT_BUTTON))
274                .withDefault(() -> new DefaultLeftButton())
275                .withCallback(button -> setLeftButton(button))
276                .build();
277    }
278
279    @Override
280    protected void onDetachedFromWindow() {
281        super.onDetachedFromWindow();
282        mAccessibilityController.removeStateChangedCallback(this);
283        mRightExtension.destroy();
284        mLeftExtension.destroy();
285    }
286
287    private void initAccessibility() {
288        mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
289        mLeftAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
290        mRightAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
291    }
292
293    @Override
294    protected void onConfigurationChanged(Configuration newConfig) {
295        super.onConfigurationChanged(newConfig);
296        int indicationBottomMargin = getResources().getDimensionPixelSize(
297                R.dimen.keyguard_indication_margin_bottom);
298        MarginLayoutParams mlp = (MarginLayoutParams) mIndicationArea.getLayoutParams();
299        if (mlp.bottomMargin != indicationBottomMargin) {
300            mlp.bottomMargin = indicationBottomMargin;
301            mIndicationArea.setLayoutParams(mlp);
302        }
303
304        // Respect font size setting.
305        mEnterpriseDisclosure.setTextSize(TypedValue.COMPLEX_UNIT_PX,
306                getResources().getDimensionPixelSize(
307                        com.android.internal.R.dimen.text_size_small_material));
308        mIndicationText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
309                getResources().getDimensionPixelSize(
310                        com.android.internal.R.dimen.text_size_small_material));
311
312        ViewGroup.LayoutParams lp = mRightAffordanceView.getLayoutParams();
313        lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
314        lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
315        mRightAffordanceView.setLayoutParams(lp);
316        updateRightAffordanceIcon();
317
318        lp = mLockIcon.getLayoutParams();
319        lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
320        lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
321        mLockIcon.setLayoutParams(lp);
322        mLockIcon.update(true /* force */);
323
324        lp = mLeftAffordanceView.getLayoutParams();
325        lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
326        lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
327        mLeftAffordanceView.setLayoutParams(lp);
328        updateLeftAffordanceIcon();
329    }
330
331    private void updateRightAffordanceIcon() {
332        IconState state = mRightButton.getIcon();
333        mRightAffordanceView.setVisibility(!mDozing && state.isVisible ? View.VISIBLE : View.GONE);
334        mRightAffordanceView.setImageDrawable(state.drawable, state.tint);
335        mRightAffordanceView.setContentDescription(state.contentDescription);
336    }
337
338    public void setStatusBar(StatusBar statusBar) {
339        mStatusBar = statusBar;
340        updateCameraVisibility(); // in case onFinishInflate() was called too early
341    }
342
343    public void setAffordanceHelper(KeyguardAffordanceHelper affordanceHelper) {
344        mAffordanceHelper = affordanceHelper;
345    }
346
347    public void setUserSetupComplete(boolean userSetupComplete) {
348        mUserSetupComplete = userSetupComplete;
349        updateCameraVisibility();
350        updateLeftAffordanceIcon();
351    }
352
353    private Intent getCameraIntent() {
354        return mRightButton.getIntent();
355    }
356
357    /**
358     * Resolves the intent to launch the camera application.
359     */
360    public ResolveInfo resolveCameraIntent() {
361        return mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(),
362                PackageManager.MATCH_DEFAULT_ONLY,
363                KeyguardUpdateMonitor.getCurrentUser());
364    }
365
366    private void updateCameraVisibility() {
367        if (mRightAffordanceView == null) {
368            // Things are not set up yet; reply hazy, ask again later
369            return;
370        }
371        mRightAffordanceView.setVisibility(!mDozing && mRightButton.getIcon().isVisible
372                ? View.VISIBLE : View.GONE);
373    }
374
375    /**
376     * Set an alternate icon for the left assist affordance (replace the mic icon)
377     */
378    public void setLeftAssistIcon(Drawable drawable) {
379        mLeftAssistIcon = drawable;
380        updateLeftAffordanceIcon();
381    }
382
383    private void updateLeftAffordanceIcon() {
384        IconState state = mLeftButton.getIcon();
385        mLeftAffordanceView.setVisibility(!mDozing && state.isVisible ? View.VISIBLE : View.GONE);
386        mLeftAffordanceView.setImageDrawable(state.drawable, state.tint);
387        mLeftAffordanceView.setContentDescription(state.contentDescription);
388    }
389
390    public boolean isLeftVoiceAssist() {
391        return mLeftIsVoiceAssist;
392    }
393
394    private boolean isPhoneVisible() {
395        PackageManager pm = mContext.getPackageManager();
396        return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
397                && pm.resolveActivity(PHONE_INTENT, 0) != null;
398    }
399
400    private boolean isCameraDisabledByDpm() {
401        final DevicePolicyManager dpm =
402                (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
403        if (dpm != null && mStatusBar != null) {
404            try {
405                final int userId = ActivityManager.getService().getCurrentUser().id;
406                final int disabledFlags = dpm.getKeyguardDisabledFeatures(null, userId);
407                final  boolean disabledBecauseKeyguardSecure =
408                        (disabledFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0
409                                && mStatusBar.isKeyguardSecure();
410                return dpm.getCameraDisabled(null) || disabledBecauseKeyguardSecure;
411            } catch (RemoteException e) {
412                Log.e(TAG, "Can't get userId", e);
413            }
414        }
415        return false;
416    }
417
418    private void watchForCameraPolicyChanges() {
419        final IntentFilter filter = new IntentFilter();
420        filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
421        getContext().registerReceiverAsUser(mDevicePolicyReceiver,
422                UserHandle.ALL, filter, null, null);
423        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
424    }
425
426    @Override
427    public void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled) {
428        mRightAffordanceView.setClickable(touchExplorationEnabled);
429        mLeftAffordanceView.setClickable(touchExplorationEnabled);
430        mRightAffordanceView.setFocusable(accessibilityEnabled);
431        mLeftAffordanceView.setFocusable(accessibilityEnabled);
432        mLockIcon.update();
433    }
434
435    @Override
436    public void onClick(View v) {
437        if (v == mRightAffordanceView) {
438            launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
439        } else if (v == mLeftAffordanceView) {
440            launchLeftAffordance();
441        } if (v == mLockIcon) {
442            if (!mAccessibilityController.isAccessibilityEnabled()) {
443                handleTrustCircleClick();
444            } else {
445                mStatusBar.animateCollapsePanels(
446                        CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
447            }
448        }
449    }
450
451    @Override
452    public boolean onLongClick(View v) {
453        handleTrustCircleClick();
454        return true;
455    }
456
457    private void handleTrustCircleClick() {
458        mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_LOCK, 0 /* lengthDp - N/A */,
459                0 /* velocityDp - N/A */);
460        mIndicationController.showTransientIndication(
461                R.string.keyguard_indication_trust_disabled);
462        mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
463    }
464
465    public void bindCameraPrewarmService() {
466        Intent intent = getCameraIntent();
467        ActivityInfo targetInfo = PreviewInflater.getTargetActivityInfo(mContext, intent,
468                KeyguardUpdateMonitor.getCurrentUser(), true /* onlyDirectBootAware */);
469        if (targetInfo != null && targetInfo.metaData != null) {
470            String clazz = targetInfo.metaData.getString(
471                    MediaStore.META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE);
472            if (clazz != null) {
473                Intent serviceIntent = new Intent();
474                serviceIntent.setClassName(targetInfo.packageName, clazz);
475                serviceIntent.setAction(CameraPrewarmService.ACTION_PREWARM);
476                try {
477                    if (getContext().bindServiceAsUser(serviceIntent, mPrewarmConnection,
478                            Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
479                            new UserHandle(UserHandle.USER_CURRENT))) {
480                        mPrewarmBound = true;
481                    }
482                } catch (SecurityException e) {
483                    Log.w(TAG, "Unable to bind to prewarm service package=" + targetInfo.packageName
484                            + " class=" + clazz, e);
485                }
486            }
487        }
488    }
489
490    public void unbindCameraPrewarmService(boolean launched) {
491        if (mPrewarmBound) {
492            if (mPrewarmMessenger != null && launched) {
493                try {
494                    mPrewarmMessenger.send(Message.obtain(null /* handler */,
495                            CameraPrewarmService.MSG_CAMERA_FIRED));
496                } catch (RemoteException e) {
497                    Log.w(TAG, "Error sending camera fired message", e);
498                }
499            }
500            mContext.unbindService(mPrewarmConnection);
501            mPrewarmBound = false;
502        }
503    }
504
505    public void launchCamera(String source) {
506        final Intent intent = getCameraIntent();
507        intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source);
508        boolean wouldLaunchResolverActivity = PreviewInflater.wouldLaunchResolverActivity(
509                mContext, intent, KeyguardUpdateMonitor.getCurrentUser());
510        if (intent == SECURE_CAMERA_INTENT && !wouldLaunchResolverActivity) {
511            AsyncTask.execute(new Runnable() {
512                @Override
513                public void run() {
514                    int result = ActivityManager.START_CANCELED;
515
516                    // Normally an activity will set it's requested rotation
517                    // animation on its window. However when launching an activity
518                    // causes the orientation to change this is too late. In these cases
519                    // the default animation is used. This doesn't look good for
520                    // the camera (as it rotates the camera contents out of sync
521                    // with physical reality). So, we ask the WindowManager to
522                    // force the crossfade animation if an orientation change
523                    // happens to occur during the launch.
524                    ActivityOptions o = ActivityOptions.makeBasic();
525                    o.setRotationAnimationHint(
526                            WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
527                    try {
528                        result = ActivityManager.getService().startActivityAsUser(
529                                null, getContext().getBasePackageName(),
530                                intent,
531                                intent.resolveTypeIfNeeded(getContext().getContentResolver()),
532                                null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, o.toBundle(),
533                                UserHandle.CURRENT.getIdentifier());
534                    } catch (RemoteException e) {
535                        Log.w(TAG, "Unable to start camera activity", e);
536                    }
537                    final boolean launched = isSuccessfulLaunch(result);
538                    post(new Runnable() {
539                        @Override
540                        public void run() {
541                            unbindCameraPrewarmService(launched);
542                        }
543                    });
544                }
545            });
546        } else {
547
548            // We need to delay starting the activity because ResolverActivity finishes itself if
549            // launched behind lockscreen.
550            mActivityStarter.startActivity(intent, false /* dismissShade */,
551                    new ActivityStarter.Callback() {
552                        @Override
553                        public void onActivityStarted(int resultCode) {
554                            unbindCameraPrewarmService(isSuccessfulLaunch(resultCode));
555                        }
556                    });
557        }
558    }
559
560    private static boolean isSuccessfulLaunch(int result) {
561        return result == ActivityManager.START_SUCCESS
562                || result == ActivityManager.START_DELIVERED_TO_TOP
563                || result == ActivityManager.START_TASK_TO_FRONT;
564    }
565
566    public void launchLeftAffordance() {
567        if (mLeftIsVoiceAssist) {
568            launchVoiceAssist();
569        } else {
570            launchPhone();
571        }
572    }
573
574    private void launchVoiceAssist() {
575        Runnable runnable = new Runnable() {
576            @Override
577            public void run() {
578                mAssistManager.launchVoiceAssistFromKeyguard();
579            }
580        };
581        if (mStatusBar.isKeyguardCurrentlySecure()) {
582            AsyncTask.execute(runnable);
583        } else {
584            boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr)
585                    && Dependency.get(TunerService.class).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0;
586            mStatusBar.executeRunnableDismissingKeyguard(runnable, null /* cancelAction */,
587                    dismissShade, false /* afterKeyguardGone */, true /* deferred */);
588        }
589    }
590
591    private boolean canLaunchVoiceAssist() {
592        return mAssistManager.canVoiceAssistBeLaunchedFromKeyguard();
593    }
594
595    private void launchPhone() {
596        final TelecomManager tm = TelecomManager.from(mContext);
597        if (tm.isInCall()) {
598            AsyncTask.execute(new Runnable() {
599                @Override
600                public void run() {
601                    tm.showInCallScreen(false /* showDialpad */);
602                }
603            });
604        } else {
605            boolean dismissShade = !TextUtils.isEmpty(mLeftButtonStr)
606                    && Dependency.get(TunerService.class).getValue(LOCKSCREEN_LEFT_UNLOCK, 1) != 0;
607            mActivityStarter.startActivity(mLeftButton.getIntent(), dismissShade);
608        }
609    }
610
611
612    @Override
613    protected void onVisibilityChanged(View changedView, int visibility) {
614        super.onVisibilityChanged(changedView, visibility);
615        if (changedView == this && visibility == VISIBLE) {
616            mLockIcon.update();
617            updateCameraVisibility();
618        }
619    }
620
621    public KeyguardAffordanceView getLeftView() {
622        return mLeftAffordanceView;
623    }
624
625    public KeyguardAffordanceView getRightView() {
626        return mRightAffordanceView;
627    }
628
629    public View getLeftPreview() {
630        return mLeftPreview;
631    }
632
633    public View getRightPreview() {
634        return mCameraPreview;
635    }
636
637    public LockIcon getLockIcon() {
638        return mLockIcon;
639    }
640
641    public View getIndicationArea() {
642        return mIndicationArea;
643    }
644
645    @Override
646    public boolean hasOverlappingRendering() {
647        return false;
648    }
649
650    @Override
651    public void onUnlockMethodStateChanged() {
652        mLockIcon.update();
653        updateCameraVisibility();
654    }
655
656    private void inflateCameraPreview() {
657        View previewBefore = mCameraPreview;
658        boolean visibleBefore = false;
659        if (previewBefore != null) {
660            mPreviewContainer.removeView(previewBefore);
661            visibleBefore = previewBefore.getVisibility() == View.VISIBLE;
662        }
663        mCameraPreview = mPreviewInflater.inflatePreview(getCameraIntent());
664        if (mCameraPreview != null) {
665            mPreviewContainer.addView(mCameraPreview);
666            mCameraPreview.setVisibility(visibleBefore ? View.VISIBLE : View.INVISIBLE);
667        }
668        if (mAffordanceHelper != null) {
669            mAffordanceHelper.updatePreviews();
670        }
671    }
672
673    private void updateLeftPreview() {
674        View previewBefore = mLeftPreview;
675        if (previewBefore != null) {
676            mPreviewContainer.removeView(previewBefore);
677        }
678        if (mLeftIsVoiceAssist) {
679            mLeftPreview = mPreviewInflater.inflatePreviewFromService(
680                    mAssistManager.getVoiceInteractorComponentName());
681        } else {
682            mLeftPreview = mPreviewInflater.inflatePreview(mLeftButton.getIntent());
683        }
684        if (mLeftPreview != null) {
685            mPreviewContainer.addView(mLeftPreview);
686            mLeftPreview.setVisibility(View.INVISIBLE);
687        }
688        if (mAffordanceHelper != null) {
689            mAffordanceHelper.updatePreviews();
690        }
691    }
692
693    public void startFinishDozeAnimation() {
694        long delay = 0;
695        if (mLeftAffordanceView.getVisibility() == View.VISIBLE) {
696            startFinishDozeAnimationElement(mLeftAffordanceView, delay);
697            delay += DOZE_ANIMATION_STAGGER_DELAY;
698        }
699        startFinishDozeAnimationElement(mLockIcon, delay);
700        delay += DOZE_ANIMATION_STAGGER_DELAY;
701        if (mRightAffordanceView.getVisibility() == View.VISIBLE) {
702            startFinishDozeAnimationElement(mRightAffordanceView, delay);
703        }
704        mIndicationArea.setAlpha(0f);
705        mIndicationArea.animate()
706                .alpha(1f)
707                .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
708                .setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
709    }
710
711    private void startFinishDozeAnimationElement(View element, long delay) {
712        element.setAlpha(0f);
713        element.setTranslationY(element.getHeight() / 2);
714        element.animate()
715                .alpha(1f)
716                .translationY(0f)
717                .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
718                .setStartDelay(delay)
719                .setDuration(DOZE_ANIMATION_ELEMENT_DURATION);
720    }
721
722    private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() {
723        @Override
724        public void onReceive(Context context, Intent intent) {
725            post(new Runnable() {
726                @Override
727                public void run() {
728                    updateCameraVisibility();
729                }
730            });
731        }
732    };
733
734    private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
735            new KeyguardUpdateMonitorCallback() {
736                @Override
737                public void onUserSwitchComplete(int userId) {
738                    updateCameraVisibility();
739                }
740
741                @Override
742                public void onStartedWakingUp() {
743                    mLockIcon.setDeviceInteractive(true);
744                }
745
746                @Override
747                public void onFinishedGoingToSleep(int why) {
748                    mLockIcon.setDeviceInteractive(false);
749                }
750
751                @Override
752                public void onScreenTurnedOn() {
753                    mLockIcon.setScreenOn(true);
754                }
755
756                @Override
757                public void onScreenTurnedOff() {
758                    mLockIcon.setScreenOn(false);
759                }
760
761                @Override
762                public void onKeyguardVisibilityChanged(boolean showing) {
763                    mLockIcon.update();
764                }
765
766                @Override
767                public void onFingerprintRunningStateChanged(boolean running) {
768                    mLockIcon.update();
769                }
770
771                @Override
772                public void onStrongAuthStateChanged(int userId) {
773                    mLockIcon.update();
774        }
775
776                @Override
777                public void onUserUnlocked() {
778                    inflateCameraPreview();
779                    updateCameraVisibility();
780                    updateLeftAffordance();
781                }
782            };
783
784    public void setKeyguardIndicationController(
785            KeyguardIndicationController keyguardIndicationController) {
786        mIndicationController = keyguardIndicationController;
787    }
788
789    public void updateLeftAffordance() {
790        updateLeftAffordanceIcon();
791        updateLeftPreview();
792    }
793
794    public void onKeyguardShowingChanged() {
795        updateLeftAffordance();
796        inflateCameraPreview();
797    }
798
799    private void setRightButton(IntentButton button) {
800        mRightButton = button;
801        updateRightAffordanceIcon();
802        updateCameraVisibility();
803        inflateCameraPreview();
804    }
805
806    private void setLeftButton(IntentButton button) {
807        mLeftButton = button;
808        if (!(mLeftButton instanceof DefaultLeftButton)) {
809            mLeftIsVoiceAssist = false;
810        }
811        updateLeftAffordance();
812    }
813
814    public void setDozing(boolean dozing, boolean animate) {
815        mDozing = dozing;
816
817        updateCameraVisibility();
818        updateLeftAffordanceIcon();
819
820        if (dozing) {
821            mLockIcon.setVisibility(INVISIBLE);
822        } else {
823            mLockIcon.setVisibility(VISIBLE);
824            if (animate) {
825                startFinishDozeAnimation();
826            }
827        }
828    }
829
830    private class DefaultLeftButton implements IntentButton {
831
832        private IconState mIconState = new IconState();
833
834        @Override
835        public IconState getIcon() {
836            mLeftIsVoiceAssist = canLaunchVoiceAssist();
837            if (mLeftIsVoiceAssist) {
838                mIconState.isVisible = mUserSetupComplete;
839                if (mLeftAssistIcon == null) {
840                    mIconState.drawable = mContext.getDrawable(R.drawable.ic_mic_26dp);
841                } else {
842                    mIconState.drawable = mLeftAssistIcon;
843                }
844                mIconState.contentDescription = mContext.getString(
845                        R.string.accessibility_voice_assist_button);
846            } else {
847                mIconState.isVisible = mUserSetupComplete && isPhoneVisible();
848                mIconState.drawable = mContext.getDrawable(R.drawable.ic_phone_24dp);
849                mIconState.contentDescription = mContext.getString(
850                        R.string.accessibility_phone_button);
851            }
852            return mIconState;
853        }
854
855        @Override
856        public Intent getIntent() {
857            return PHONE_INTENT;
858        }
859    }
860
861    private class DefaultRightButton implements IntentButton {
862
863        private IconState mIconState = new IconState();
864
865        @Override
866        public IconState getIcon() {
867            ResolveInfo resolved = resolveCameraIntent();
868            mIconState.isVisible = !isCameraDisabledByDpm() && resolved != null
869                    && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance)
870                    && mUserSetupComplete;
871            mIconState.drawable = mContext.getDrawable(R.drawable.ic_camera_alt_24dp);
872            mIconState.contentDescription =
873                    mContext.getString(R.string.accessibility_camera_button);
874            return mIconState;
875        }
876
877        @Override
878        public Intent getIntent() {
879            KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
880            boolean canSkipBouncer = updateMonitor.getUserCanSkipBouncer(
881                    KeyguardUpdateMonitor.getCurrentUser());
882            boolean secure = mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser());
883            return (secure && !canSkipBouncer) ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
884        }
885    }
886}
887