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