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