FaceUnlock.java revision 000464ac012471d301c6e48a8228291519915e17
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 */
16
17package com.android.internal.policy.impl.keyguard;
18
19import com.android.internal.policy.IFaceLockCallback;
20import com.android.internal.policy.IFaceLockInterface;
21import com.android.internal.widget.LockPatternUtils;
22
23import android.app.admin.DevicePolicyManager;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
27import android.content.ServiceConnection;
28import android.os.Handler;
29import android.os.IBinder;
30import android.os.Looper;
31import android.os.Message;
32import android.os.RemoteException;
33import android.util.Log;
34import android.view.View;
35
36public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
37
38    private static final boolean DEBUG = false;
39    private static final String TAG = "FULLockscreen";
40
41    private final Context mContext;
42    private final LockPatternUtils mLockPatternUtils;
43
44    // TODO: is mServiceRunning needed or can we just use mIsRunning or check if mService is null?
45    private boolean mServiceRunning = false;
46    // TODO: now that the code has been restructure to do almost all operations from a handler, this
47    // lock may no longer be necessary.
48    private final Object mServiceRunningLock = new Object();
49    private IFaceLockInterface mService;
50    private boolean mBoundToService = false;
51    private View mFaceUnlockView;
52
53    private Handler mHandler;
54    private final int MSG_SHOW_FACE_UNLOCK_VIEW = 0;
55    private final int MSG_HIDE_FACE_UNLOCK_VIEW = 1;
56    private final int MSG_SERVICE_CONNECTED = 2;
57    private final int MSG_SERVICE_DISCONNECTED = 3;
58    private final int MSG_UNLOCK = 4;
59    private final int MSG_CANCEL = 5;
60    private final int MSG_REPORT_FAILED_ATTEMPT = 6;
61    private final int MSG_EXPOSE_FALLBACK = 7;
62    private final int MSG_POKE_WAKELOCK = 8;
63
64    // TODO: This was added for the purpose of adhering to what the biometric interface expects
65    // the isRunning() function to return.  However, it is probably not necessary to have both
66    // mRunning and mServiceRunning.  I'd just rather wait to change that logic.
67    private volatile boolean mIsRunning = false;
68
69    // Long enough to stay visible while the service starts
70    // Short enough to not have to wait long for backup if service fails to start or crashes
71    // The service can take a couple of seconds to start on the first try after boot
72    private final int SERVICE_STARTUP_VIEW_TIMEOUT = 3000;
73
74    // So the user has a consistent amount of time when brought to the backup method from Face
75    // Unlock
76    private final int BACKUP_LOCK_TIMEOUT = 5000;
77
78    KeyguardSecurityCallback mKeyguardScreenCallback;
79
80    /**
81     * Stores some of the structures that Face Unlock will need to access and creates the handler
82     * will be used to execute messages on the UI thread.
83     */
84    public FaceUnlock(Context context) {
85        mContext = context;
86        mLockPatternUtils = new LockPatternUtils(context);
87        mHandler = new Handler(this);
88    }
89
90    public void setKeyguardCallback(KeyguardSecurityCallback keyguardScreenCallback) {
91        mKeyguardScreenCallback = keyguardScreenCallback;
92    }
93
94    /**
95     * Stores and displays the view that Face Unlock is allowed to draw within.
96     * TODO: since the layout object will eventually be shared by multiple biometric unlock
97     * methods, we will have to add our other views (background, cancel button) here.
98     */
99    public void initializeView(View biometricUnlockView) {
100        Log.d(TAG, "initializeView()");
101        mFaceUnlockView = biometricUnlockView;
102    }
103
104    /**
105     * Indicates whether Face Unlock is currently running.
106     */
107    public boolean isRunning() {
108        return mIsRunning;
109    }
110
111    /**
112     * Sets the Face Unlock view to visible, hiding it after the specified amount of time.  If
113     * timeoutMillis is 0, no hide is performed.  Called on the UI thread.
114     */
115    public void show(long timeoutMillis) {
116        if (DEBUG) Log.d(TAG, "show()");
117        if (mHandler.getLooper() != Looper.myLooper()) {
118            Log.e(TAG, "show() called off of the UI thread");
119        }
120        removeDisplayMessages();
121        if (timeoutMillis > 0) {
122            mHandler.sendEmptyMessageDelayed(MSG_HIDE_FACE_UNLOCK_VIEW, timeoutMillis);
123        }
124    }
125
126    /**
127     * Hides the Face Unlock view.
128     */
129    public void hide() {
130        if (DEBUG) Log.d(TAG, "hide()");
131        // Remove messages to prevent a delayed show message from undo-ing the hide
132        removeDisplayMessages();
133        mHandler.sendEmptyMessage(MSG_HIDE_FACE_UNLOCK_VIEW);
134    }
135
136    /**
137     * Binds to the Face Unlock service.  Face Unlock will be started when the bind completes.  The
138     * Face Unlock view is displayed to hide the backup lock while the service is starting up.
139     * Called on the UI thread.
140     */
141    public boolean start() {
142        if (DEBUG) Log.d(TAG, "start()");
143        if (mHandler.getLooper() != Looper.myLooper()) {
144            Log.e(TAG, "start() called off of the UI thread");
145        }
146
147        if (mIsRunning) {
148            Log.w(TAG, "start() called when already running");
149        }
150
151        // Show Face Unlock view, but only for a little bit so lockpattern will become visible if
152        // Face Unlock fails to start or crashes
153        // This must show before bind to guarantee that Face Unlock has a place to display
154        show(SERVICE_STARTUP_VIEW_TIMEOUT);
155        if (!mBoundToService) {
156            Log.d(TAG, "Binding to Face Unlock service");
157            mContext.bindService(new Intent(IFaceLockInterface.class.getName()),
158                    mConnection,
159                    Context.BIND_AUTO_CREATE,
160                    mLockPatternUtils.getCurrentUser());
161            mBoundToService = true;
162        } else {
163            Log.w(TAG, "Attempt to bind to Face Unlock when already bound");
164        }
165
166        mIsRunning = true;
167        return true;
168    }
169
170    /**
171     * Stops Face Unlock and unbinds from the service.  Called on the UI thread.
172     */
173    public boolean stop() {
174        if (DEBUG) Log.d(TAG, "stop()");
175        if (mHandler.getLooper() != Looper.myLooper()) {
176            Log.e(TAG, "stop() called off of the UI thread");
177        }
178
179        boolean mWasRunning = mIsRunning;
180        stopUi();
181
182        if (mBoundToService) {
183            if (mService != null) {
184                try {
185                    mService.unregisterCallback(mFaceUnlockCallback);
186                } catch (RemoteException e) {
187                    // Not much we can do
188                }
189            }
190            Log.d(TAG, "Unbinding from Face Unlock service");
191            mContext.unbindService(mConnection);
192            mBoundToService = false;
193        } else {
194            // This is usually not an error when this happens.  Sometimes we will tell it to
195            // unbind multiple times because it's called from both onWindowFocusChanged and
196            // onDetachedFromWindow.
197            if (DEBUG) Log.d(TAG, "Attempt to unbind from Face Unlock when not bound");
198        }
199        mIsRunning = false;
200        return mWasRunning;
201    }
202
203    /**
204     * Frees up resources used by Face Unlock and stops it if it is still running.
205     */
206    public void cleanUp() {
207        if (DEBUG) Log.d(TAG, "cleanUp()");
208        if (mService != null) {
209            try {
210                mService.unregisterCallback(mFaceUnlockCallback);
211            } catch (RemoteException e) {
212                // Not much we can do
213            }
214            stopUi();
215            mService = null;
216        }
217    }
218
219    /**
220     * Returns the Device Policy Manager quality for Face Unlock, which is BIOMETRIC_WEAK.
221     */
222    public int getQuality() {
223        return DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
224    }
225
226    /**
227     * Handles messages such that everything happens on the UI thread in a deterministic order.
228     * Calls from the Face Unlock service come from binder threads.  Calls from lockscreen typically
229     * come from the UI thread.  This makes sure there are no race conditions between those calls.
230     */
231    public boolean handleMessage(Message msg) {
232        switch (msg.what) {
233            case MSG_SHOW_FACE_UNLOCK_VIEW:
234                handleShowFaceUnlockView();
235                break;
236            case MSG_HIDE_FACE_UNLOCK_VIEW:
237                handleHideFaceUnlockView();
238                break;
239            case MSG_SERVICE_CONNECTED:
240                handleServiceConnected();
241                break;
242            case MSG_SERVICE_DISCONNECTED:
243                handleServiceDisconnected();
244                break;
245            case MSG_UNLOCK:
246                handleUnlock();
247                break;
248            case MSG_CANCEL:
249                handleCancel();
250                break;
251            case MSG_REPORT_FAILED_ATTEMPT:
252                handleReportFailedAttempt();
253                break;
254            case MSG_EXPOSE_FALLBACK:
255                handleExposeFallback();
256                break;
257            case MSG_POKE_WAKELOCK:
258                handlePokeWakelock(msg.arg1);
259                break;
260            default:
261                Log.e(TAG, "Unhandled message");
262                return false;
263        }
264        return true;
265    }
266
267    /**
268     * Sets the Face Unlock view to visible, thus covering the backup lock.
269     */
270    void handleShowFaceUnlockView() {
271        if (DEBUG) Log.d(TAG, "handleShowFaceUnlockView()");
272        // Not required
273    }
274
275    /**
276     * Hide face unlock and show backup
277     */
278    void handleHideFaceUnlockView() {
279        if (DEBUG) Log.d(TAG, "handleHideFaceUnlockView()");
280        mKeyguardScreenCallback.showBackupSecurity();
281    }
282
283    /**
284     * Tells the service to start its UI via an AIDL interface.  Called when the
285     * onServiceConnected() callback is received.
286     */
287    void handleServiceConnected() {
288        Log.d(TAG, "handleServiceConnected()");
289
290        // It is possible that an unbind has occurred in the time between the bind and when this
291        // function is reached.  If an unbind has already occurred, proceeding on to call startUi()
292        // can result in a fatal error.  Note that the onServiceConnected() callback is
293        // asynchronous, so this possibility would still exist if we executed this directly in
294        // onServiceConnected() rather than using a handler.
295        if (!mBoundToService) {
296            Log.d(TAG, "Dropping startUi() in handleServiceConnected() because no longer bound");
297            return;
298        }
299
300        try {
301            mService.registerCallback(mFaceUnlockCallback);
302        } catch (RemoteException e) {
303            Log.e(TAG, "Caught exception connecting to Face Unlock: " + e.toString());
304            mService = null;
305            mBoundToService = false;
306            mIsRunning = false;
307            return;
308        }
309
310        if (mFaceUnlockView != null) {
311            IBinder windowToken = mFaceUnlockView.getWindowToken();
312            if (windowToken != null) {
313                // When switching between portrait and landscape view while Face Unlock is running,
314                // the screen will eventually go dark unless we poke the wakelock when Face Unlock
315                // is restarted.
316                mKeyguardScreenCallback.userActivity(0);
317
318                int[] position;
319                position = new int[2];
320                mFaceUnlockView.getLocationInWindow(position);
321                startUi(windowToken, position[0], position[1], mFaceUnlockView.getWidth(),
322                        mFaceUnlockView.getHeight());
323            } else {
324                Log.e(TAG, "windowToken is null in handleServiceConnected()");
325            }
326        }
327    }
328
329    /**
330     * Called when the onServiceDisconnected() callback is received.  This should not happen during
331     * normal operation.  It indicates an error has occurred.
332     */
333    void handleServiceDisconnected() {
334        Log.e(TAG, "handleServiceDisconnected()");
335        // TODO: this lock may no longer be needed now that everything is being called from a
336        // handler
337        synchronized (mServiceRunningLock) {
338            mService = null;
339            mServiceRunning = false;
340        }
341        mBoundToService = false;
342        mIsRunning = false;
343    }
344
345    /**
346     * Stops the Face Unlock service and tells the device to grant access to the user.  Shows the
347     * Face Unlock view to keep the backup lock covered while the device unlocks.
348     */
349    void handleUnlock() {
350        if (DEBUG) Log.d(TAG, "handleUnlock()");
351        removeDisplayMessages();
352        stop();
353        mKeyguardScreenCallback.reportSuccessfulUnlockAttempt();
354        mKeyguardScreenCallback.dismiss(true);
355    }
356
357    /**
358     * Stops the Face Unlock service and exposes the backup lock.
359     */
360    void handleCancel() {
361        if (DEBUG) Log.d(TAG, "handleCancel()");
362        mKeyguardScreenCallback.dismiss(false);
363        stop();
364        mKeyguardScreenCallback.userActivity(BACKUP_LOCK_TIMEOUT);
365    }
366
367    /**
368     * Increments the number of failed Face Unlock attempts.
369     */
370    void handleReportFailedAttempt() {
371        if (DEBUG) Log.d(TAG, "handleReportFailedAttempt()");
372        mKeyguardScreenCallback.reportFailedUnlockAttempt();
373    }
374
375    /**
376     * Hides the Face Unlock view to expose the backup lock.  Called when the Face Unlock service UI
377     * is started, indicating there is no need to continue displaying the underlying view because
378     * the service UI is now covering the backup lock.
379     */
380    void handleExposeFallback() {
381        if (DEBUG) Log.d(TAG, "handleExposeFallback()");
382        // No longer required because face unlock doesn't cover backup unlock.
383    }
384
385    /**
386     * Pokes the wakelock to keep the screen alive and active for a specific amount of time.
387     */
388    void handlePokeWakelock(int millis) {
389        mKeyguardScreenCallback.userActivity(millis);
390    }
391
392    /**
393     * Removes show and hide messages from the message queue.  Called to prevent delayed show/hide
394     * messages from undoing a new message.
395     */
396    private void removeDisplayMessages() {
397        mHandler.removeMessages(MSG_SHOW_FACE_UNLOCK_VIEW);
398        mHandler.removeMessages(MSG_HIDE_FACE_UNLOCK_VIEW);
399    }
400
401    /**
402     * Implements service connection methods.
403     */
404    private ServiceConnection mConnection = new ServiceConnection() {
405        /**
406         * Called when the Face Unlock service connects after calling bind().
407         */
408        public void onServiceConnected(ComponentName className, IBinder iservice) {
409            Log.d(TAG, "Connected to Face Unlock service");
410            mService = IFaceLockInterface.Stub.asInterface(iservice);
411            mHandler.sendEmptyMessage(MSG_SERVICE_CONNECTED);
412        }
413
414        /**
415         * Called if the Face Unlock service unexpectedly disconnects.  This indicates an error.
416         */
417        public void onServiceDisconnected(ComponentName className) {
418            Log.e(TAG, "Unexpected disconnect from Face Unlock service");
419            mHandler.sendEmptyMessage(MSG_SERVICE_DISCONNECTED);
420        }
421    };
422
423    /**
424     * Tells the Face Unlock service to start displaying its UI and start processing.
425     */
426    private void startUi(IBinder windowToken, int x, int y, int w, int h) {
427        if (DEBUG) Log.d(TAG, "startUi()");
428        synchronized (mServiceRunningLock) {
429            if (!mServiceRunning) {
430                Log.d(TAG, "Starting Face Unlock");
431                try {
432                    mService.startUi(windowToken, x, y, w, h,
433                            mLockPatternUtils.isBiometricWeakLivelinessEnabled());
434                } catch (RemoteException e) {
435                    Log.e(TAG, "Caught exception starting Face Unlock: " + e.toString());
436                    return;
437                }
438                mServiceRunning = true;
439            } else {
440                Log.w(TAG, "startUi() attempted while running");
441            }
442        }
443    }
444
445    /**
446     * Tells the Face Unlock service to stop displaying its UI and stop processing.
447     */
448    private void stopUi() {
449        if (DEBUG) Log.d(TAG, "stopUi()");
450        // Note that attempting to stop Face Unlock when it's not running is not an issue.
451        // Face Unlock can return, which stops it and then we try to stop it when the
452        // screen is turned off.  That's why we check.
453        synchronized (mServiceRunningLock) {
454            if (mServiceRunning) {
455                Log.d(TAG, "Stopping Face Unlock");
456                try {
457                    mService.stopUi();
458                } catch (RemoteException e) {
459                    Log.e(TAG, "Caught exception stopping Face Unlock: " + e.toString());
460                }
461                mServiceRunning = false;
462            } else {
463                // This is usually not an error when this happens.  Sometimes we will tell it to
464                // stop multiple times because it's called from both onWindowFocusChanged and
465                // onDetachedFromWindow.
466                if (DEBUG) Log.d(TAG, "stopUi() attempted while not running");
467            }
468        }
469    }
470
471    /**
472     * Implements the AIDL biometric unlock service callback interface.
473     */
474    private final IFaceLockCallback mFaceUnlockCallback = new IFaceLockCallback.Stub() {
475        /**
476         * Called when Face Unlock wants to grant access to the user.
477         */
478        public void unlock() {
479            if (DEBUG) Log.d(TAG, "unlock()");
480            mHandler.sendEmptyMessage(MSG_UNLOCK);
481        }
482
483        /**
484         * Called when Face Unlock wants to go to the backup.
485         */
486        public void cancel() {
487            if (DEBUG) Log.d(TAG, "cancel()");
488            mHandler.sendEmptyMessage(MSG_CANCEL);
489        }
490
491        /**
492         * Called when Face Unlock wants to increment the number of failed attempts.
493         */
494        public void reportFailedAttempt() {
495            if (DEBUG) Log.d(TAG, "reportFailedAttempt()");
496            mHandler.sendEmptyMessage(MSG_REPORT_FAILED_ATTEMPT);
497        }
498
499        /**
500         * Called when the Face Unlock service starts displaying the UI, indicating that the backup
501         * unlock can be exposed because the Face Unlock service is now covering the backup with its
502         * UI.
503         **/
504        public void exposeFallback() {
505            if (DEBUG) Log.d(TAG, "exposeFallback()");
506            mHandler.sendEmptyMessage(MSG_EXPOSE_FALLBACK);
507        }
508
509        /**
510         * Called when Face Unlock wants to keep the screen alive and active for a specific amount
511         * of time.
512         */
513        public void pokeWakelock(int millis) {
514            if (DEBUG) Log.d(TAG, "pokeWakelock() for " + millis + "ms");
515            Message message = mHandler.obtainMessage(MSG_POKE_WAKELOCK, millis, -1);
516            mHandler.sendMessage(message);
517        }
518
519    };
520}
521