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