KeyguardSelectorView.java revision 42c3e02c52fb487676251b297461f6ff7beff3ef
1/*
2 * Copyright (C) 2012 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 */
16package com.android.keyguard;
17
18import android.animation.ObjectAnimator;
19import android.app.PendingIntent;
20import android.app.SearchManager;
21import android.app.admin.DevicePolicyManager;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.graphics.drawable.Drawable;
26import android.os.Build;
27import android.os.Bundle;
28import android.os.PowerManager;
29import android.os.UserHandle;
30import android.provider.Settings;
31import android.speech.hotword.HotwordRecognitionListener;
32import android.speech.hotword.HotwordRecognizer;
33import android.telephony.TelephonyManager;
34import android.util.AttributeSet;
35import android.util.Log;
36import android.util.Slog;
37import android.view.View;
38import android.widget.LinearLayout;
39
40import com.android.internal.telephony.IccCardConstants.State;
41import com.android.internal.widget.LockPatternUtils;
42import com.android.internal.widget.multiwaveview.GlowPadView;
43import com.android.internal.widget.multiwaveview.GlowPadView.OnTriggerListener;
44import com.android.keyguard.KeyguardHostView.OnDismissAction;
45
46public class KeyguardSelectorView extends LinearLayout implements KeyguardSecurityView {
47    private static final boolean DEBUG = KeyguardHostView.DEBUG;
48    private static final String TAG = "SecuritySelectorView";
49    private static final String ASSIST_ICON_METADATA_NAME =
50        "com.android.systemui.action_assist_icon";
51    // Flag to enable/disable hotword detection on lock screen.
52    private static final boolean FLAG_HOTWORD = true;
53
54    // TODO: Fix this to be non-static.
55    private static HotwordRecognizer sHotwordClient;
56
57    private KeyguardSecurityCallback mCallback;
58    private GlowPadView mGlowPadView;
59    private ObjectAnimator mAnim;
60    private View mFadeView;
61    private boolean mIsBouncing;
62    private boolean mCameraDisabled;
63    private boolean mSearchDisabled;
64    private LockPatternUtils mLockPatternUtils;
65    private SecurityMessageDisplay mSecurityMessageDisplay;
66    private Drawable mBouncerFrame;
67
68    OnTriggerListener mOnTriggerListener = new OnTriggerListener() {
69
70        public void onTrigger(View v, int target) {
71            final int resId = mGlowPadView.getResourceIdForTarget(target);
72            maybeStopHotwordDetector();
73
74            switch (resId) {
75                case R.drawable.ic_action_assist_generic:
76                    Intent assistIntent =
77                            ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
78                            .getAssistIntent(mContext, true, UserHandle.USER_CURRENT);
79                    if (assistIntent != null) {
80                        mActivityLauncher.launchActivity(assistIntent, false, true, null, null);
81                    } else {
82                        Log.w(TAG, "Failed to get intent for assist activity");
83                    }
84                    mCallback.userActivity(0);
85                    break;
86
87                case R.drawable.ic_lockscreen_camera:
88                    mActivityLauncher.launchCamera(null, null);
89                    mCallback.userActivity(0);
90                    break;
91
92                case R.drawable.ic_lockscreen_unlock_phantom:
93                case R.drawable.ic_lockscreen_unlock:
94                    mCallback.userActivity(0);
95                    mCallback.dismiss(false);
96                break;
97            }
98        }
99
100        public void onReleased(View v, int handle) {
101            if (!mIsBouncing) {
102                doTransition(mFadeView, 1.0f);
103            }
104        }
105
106        public void onGrabbed(View v, int handle) {
107            mCallback.userActivity(0);
108            doTransition(mFadeView, 0.0f);
109        }
110
111        public void onGrabbedStateChange(View v, int handle) {
112
113        }
114
115        public void onFinishFinalAnimation() {
116
117        }
118
119    };
120
121    KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
122
123        @Override
124        public void onDevicePolicyManagerStateChanged() {
125            updateTargets();
126        }
127
128        @Override
129        public void onSimStateChanged(State simState) {
130            updateTargets();
131        }
132
133        @Override
134        public void onPhoneStateChanged(int phoneState) {
135            if (FLAG_HOTWORD) {
136                // We need to stop hotword detection when a call state is not idle anymore.
137                if (phoneState != TelephonyManager.CALL_STATE_IDLE) {
138                    if (DEBUG) Log.d(TAG, "Stopping due to call state not being idle");
139                    maybeStopHotwordDetector();
140                }
141            }
142        }
143
144        @Override
145        public void onUserSwitching(int userId) {
146            maybeStopHotwordDetector();
147        }
148    };
149
150    private final KeyguardActivityLauncher mActivityLauncher = new KeyguardActivityLauncher() {
151
152        @Override
153        KeyguardSecurityCallback getCallback() {
154            return mCallback;
155        }
156
157        @Override
158        LockPatternUtils getLockPatternUtils() {
159            return mLockPatternUtils;
160        }
161
162        @Override
163        Context getContext() {
164            return mContext;
165        }};
166
167    public KeyguardSelectorView(Context context) {
168        this(context, null);
169    }
170
171    public KeyguardSelectorView(Context context, AttributeSet attrs) {
172        super(context, attrs);
173        mLockPatternUtils = new LockPatternUtils(getContext());
174    }
175
176    @Override
177    protected void onFinishInflate() {
178        super.onFinishInflate();
179        mGlowPadView = (GlowPadView) findViewById(R.id.glow_pad_view);
180        mGlowPadView.setOnTriggerListener(mOnTriggerListener);
181        updateTargets();
182
183        mSecurityMessageDisplay = new KeyguardMessageArea.Helper(this);
184        View bouncerFrameView = findViewById(R.id.keyguard_selector_view_frame);
185        mBouncerFrame = bouncerFrameView.getBackground();
186        if (FLAG_HOTWORD && sHotwordClient == null) {
187            sHotwordClient = HotwordRecognizer.createHotwordRecognizer(getContext());
188        }
189    }
190
191    public void setCarrierArea(View carrierArea) {
192        mFadeView = carrierArea;
193    }
194
195    public boolean isTargetPresent(int resId) {
196        return mGlowPadView.getTargetPosition(resId) != -1;
197    }
198
199    @Override
200    public void showUsabilityHint() {
201        mGlowPadView.ping();
202    }
203
204    private void updateTargets() {
205        int currentUserHandle = mLockPatternUtils.getCurrentUser();
206        DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
207        int disabledFeatures = dpm.getKeyguardDisabledFeatures(null, currentUserHandle);
208        boolean secureCameraDisabled = mLockPatternUtils.isSecure()
209                && (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0;
210        boolean cameraDisabledByAdmin = dpm.getCameraDisabled(null, currentUserHandle)
211                || secureCameraDisabled;
212        final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(getContext());
213        boolean disabledBySimState = monitor.isSimLocked();
214        boolean cameraTargetPresent =
215            isTargetPresent(R.drawable.ic_lockscreen_camera);
216        boolean searchTargetPresent =
217            isTargetPresent(R.drawable.ic_action_assist_generic);
218
219        if (cameraDisabledByAdmin) {
220            Log.v(TAG, "Camera disabled by Device Policy");
221        } else if (disabledBySimState) {
222            Log.v(TAG, "Camera disabled by Sim State");
223        }
224        boolean currentUserSetup = 0 != Settings.Secure.getIntForUser(
225                mContext.getContentResolver(),
226                Settings.Secure.USER_SETUP_COMPLETE,
227                0 /*default */,
228                currentUserHandle);
229        boolean searchActionAvailable =
230                ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
231                .getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null;
232        mCameraDisabled = cameraDisabledByAdmin || disabledBySimState || !cameraTargetPresent
233                || !currentUserSetup;
234        mSearchDisabled = disabledBySimState || !searchActionAvailable || !searchTargetPresent
235                || !currentUserSetup;
236        updateResources();
237    }
238
239    public void updateResources() {
240        // Update the search icon with drawable from the search .apk
241        if (!mSearchDisabled) {
242            Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
243                    .getAssistIntent(mContext, false, UserHandle.USER_CURRENT);
244            if (intent != null) {
245                // XXX Hack. We need to substitute the icon here but haven't formalized
246                // the public API. The "_google" metadata will be going away, so
247                // DON'T USE IT!
248                ComponentName component = intent.getComponent();
249                boolean replaced = mGlowPadView.replaceTargetDrawablesIfPresent(component,
250                        ASSIST_ICON_METADATA_NAME + "_google", R.drawable.ic_action_assist_generic);
251
252                if (!replaced && !mGlowPadView.replaceTargetDrawablesIfPresent(component,
253                            ASSIST_ICON_METADATA_NAME, R.drawable.ic_action_assist_generic)) {
254                        Slog.w(TAG, "Couldn't grab icon from package " + component);
255                }
256            }
257        }
258
259        mGlowPadView.setEnableTarget(R.drawable.ic_lockscreen_camera, !mCameraDisabled);
260        mGlowPadView.setEnableTarget(R.drawable.ic_action_assist_generic, !mSearchDisabled);
261    }
262
263    void doTransition(View view, float to) {
264        if (mAnim != null) {
265            mAnim.cancel();
266        }
267        mAnim = ObjectAnimator.ofFloat(view, "alpha", to);
268        mAnim.start();
269    }
270
271    public void setKeyguardCallback(KeyguardSecurityCallback callback) {
272        mCallback = callback;
273    }
274
275    public void setLockPatternUtils(LockPatternUtils utils) {
276        mLockPatternUtils = utils;
277    }
278
279    @Override
280    public void reset() {
281        mGlowPadView.reset(false);
282    }
283
284    @Override
285    public boolean needsInput() {
286        return false;
287    }
288
289    @Override
290    public void onPause() {
291        KeyguardUpdateMonitor.getInstance(getContext()).removeCallback(mUpdateCallback);
292        maybeStopHotwordDetector();
293    }
294
295    @Override
296    public void onResume(int reason) {
297        KeyguardUpdateMonitor.getInstance(getContext()).registerCallback(mUpdateCallback);
298        // TODO: Figure out if there's a better way to do it.
299        // onResume gets called multiple times, however we are interested in
300        // the reason to figure out when to start/stop hotword detection.
301        if (reason == SCREEN_ON) {
302            if (!KeyguardUpdateMonitor.getInstance(getContext()).isSwitchingUser()) {
303                maybeStartHotwordDetector();
304            }
305        }
306    }
307
308    @Override
309    public KeyguardSecurityCallback getCallback() {
310        return mCallback;
311    }
312
313    @Override
314    public void showBouncer(int duration) {
315        mIsBouncing = true;
316        KeyguardSecurityViewHelper.
317                showBouncer(mSecurityMessageDisplay, mFadeView, mBouncerFrame, duration);
318    }
319
320    @Override
321    public void hideBouncer(int duration) {
322        mIsBouncing = false;
323        KeyguardSecurityViewHelper.
324                hideBouncer(mSecurityMessageDisplay, mFadeView, mBouncerFrame, duration);
325    }
326
327    /**
328     * Start the hotword detector if:
329     * <li> FLAG_HOTWORD is true and
330     * <li> Hotword detection is not already running and
331     * <li> TelephonyManager is in CALL_STATE_IDLE
332     *
333     * If this method is called when the screen is off,
334     * it attempts to stop hotword detection if it's running.
335     */
336    private void maybeStartHotwordDetector() {
337        if (FLAG_HOTWORD && sHotwordClient != null) {
338            if (DEBUG) Log.d(TAG, "maybeStartHotwordDetector()");
339            // Don't start it if the screen is off or not showing
340            PowerManager powerManager = (PowerManager) getContext().getSystemService(
341                    Context.POWER_SERVICE);
342            if (!powerManager.isScreenOn()) {
343                if (DEBUG) Log.d(TAG, "screen was off, not starting");
344                return;
345            }
346
347            KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(getContext());
348            if (monitor.getPhoneState() != TelephonyManager.CALL_STATE_IDLE) {
349                if (DEBUG) Log.d(TAG, "Call underway, not starting");
350                return;
351            }
352
353            try {
354                sHotwordClient.startRecognition(mHotwordCallback);
355            } catch(Exception ex) {
356                // Don't allow hotword errors to make the keyguard unusable
357                Log.e(TAG, "Failed to start hotword recognition", ex);
358                sHotwordClient = null;
359            }
360        }
361    }
362
363    /**
364     * Stop hotword detector if HOTWORDING_ENABLED is true.
365     */
366    private void maybeStopHotwordDetector() {
367        if (FLAG_HOTWORD && sHotwordClient != null) {
368            if (DEBUG) Log.d(TAG, "maybeStopHotwordDetector()");
369            try {
370                sHotwordClient.stopRecognition();
371            } catch(Exception ex) {
372                // Don't allow hotword errors to make the keyguard unusable
373                Log.e(TAG, "Failed to start hotword recognition", ex);
374            } finally {
375                sHotwordClient = null;
376            }
377        }
378    }
379
380    private final HotwordRecognitionListener mHotwordCallback = new HotwordRecognitionListener() {
381        private static final String TAG = "HotwordRecognitionListener";
382
383        public void onHotwordRecognitionStarted() {
384            if (DEBUG) Log.d(TAG, "onHotwordRecognitionStarted()");
385        }
386
387        public void onHotwordRecognitionStopped() {
388            if (DEBUG) Log.d(TAG, "onHotwordRecognitionStopped()");
389        }
390
391        public void onHotwordEvent(int eventType, Bundle eventBundle) {
392            if (DEBUG) Log.d(TAG, "onHotwordEvent: " + eventType);
393            if (eventType == HotwordRecognizer.EVENT_TYPE_STATE_CHANGED) {
394                if (eventBundle != null && eventBundle.containsKey(HotwordRecognizer.PROMPT_TEXT)) {
395                    mSecurityMessageDisplay.setMessage(
396                            eventBundle.getString(HotwordRecognizer.PROMPT_TEXT), true);
397                }
398            }
399        }
400
401        public void onHotwordRecognized(PendingIntent intent) {
402            if (DEBUG) Log.d(TAG, "onHotwordRecognized");
403            maybeStopHotwordDetector();
404            if (intent != null) {
405                try {
406                    intent.send();
407                } catch (PendingIntent.CanceledException e) {
408                    Log.w(TAG, "Failed to launch PendingIntent. Encountered CanceledException");
409                }
410            }
411            mCallback.userActivity(0);
412            mCallback.dismiss(false);
413        }
414
415        public void onHotwordError(int errorCode) {
416            if (DEBUG) Log.d(TAG, "onHotwordError: " + errorCode);
417            // TODO: Inspect the error code and handle the errors appropriately
418            // instead of blindly failing.
419            maybeStopHotwordDetector();
420        }
421    };
422}
423