/** * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.fingerprint; import android.app.Service; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.PowerManager; import android.os.RemoteException; import android.provider.Settings; import android.service.fingerprint.FingerprintManager; import android.util.ArrayMap; import android.util.Slog; import com.android.server.SystemService; import android.service.fingerprint.FingerprintUtils; import android.service.fingerprint.IFingerprintService; import android.service.fingerprint.IFingerprintServiceReceiver; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map.Entry; import java.util.Set; /** * A service to manage multiple clients that want to access the fingerprint HAL API. * The service is responsible for maintaining a list of clients and dispatching all * fingerprint -related events. * * @hide */ public class FingerprintService extends SystemService { private final String TAG = "FingerprintService"; private static final boolean DEBUG = true; private ArrayMap mClients = new ArrayMap(); private static final int MSG_NOTIFY = 10; Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { case MSG_NOTIFY: handleNotify(msg.arg1, msg.arg2, (Integer) msg.obj); break; default: Slog.w(TAG, "Unknown message:" + msg.what); } } }; private Context mContext; private static final int STATE_IDLE = 0; private static final int STATE_LISTENING = 1; private static final int STATE_ENROLLING = 2; private static final int STATE_REMOVING = 3; private static final long MS_PER_SEC = 1000; public static final String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT"; public static final String ENROLL_FINGERPRINT = "android.permission.ENROLL_FINGERPRINT"; private static final class ClientData { public IFingerprintServiceReceiver receiver; int state; int userId; public TokenWatcher tokenWatcher; IBinder getToken() { return tokenWatcher.getToken(); } } private class TokenWatcher implements IBinder.DeathRecipient { WeakReference token; TokenWatcher(IBinder token) { this.token = new WeakReference(token); } IBinder getToken() { return token.get(); } public void binderDied() { mClients.remove(token); this.token = null; } protected void finalize() throws Throwable { try { if (token != null) { if (DEBUG) Slog.w(TAG, "removing leaked reference: " + token); mClients.remove(token); } } finally { super.finalize(); } } } public FingerprintService(Context context) { super(context); mContext = context; nativeInit(this); } // TODO: Move these into separate process // JNI methods to communicate from FingerprintManagerService to HAL native int nativeEnroll(int timeout); native int nativeEnrollCancel(); native int nativeRemove(int fingerprintId); native int nativeOpenHal(); native int nativeCloseHal(); native void nativeInit(FingerprintService service); // JNI methods for communicating from HAL to clients void notify(int msg, int arg1, int arg2) { mHandler.obtainMessage(MSG_NOTIFY, msg, arg1, arg2).sendToTarget(); } void handleNotify(int msg, int arg1, int arg2) { Slog.v(TAG, "handleNotify(msg=" + msg + ", arg1=" + arg1 + ", arg2=" + arg2 + ")"); for (int i = 0; i < mClients.size(); i++) { ClientData clientData = mClients.valueAt(i); if (clientData == null || clientData.receiver == null) { if (DEBUG) Slog.v(TAG, "clientData at " + i + " is invalid!!"); continue; } switch (msg) { case FingerprintManager.FINGERPRINT_ERROR: { final int error = arg1; try { clientData.receiver.onError(error); } catch (RemoteException e) { Slog.e(TAG, "can't send message to client. Did it die?", e); mClients.remove(mClients.keyAt(i)); } } break; case FingerprintManager.FINGERPRINT_ACQUIRED: { final int acquireInfo = arg1; try { clientData.receiver.onAcquired(acquireInfo); } catch (RemoteException e) { Slog.e(TAG, "can't send message to client. Did it die?", e); mClients.remove(mClients.keyAt(i)); } break; } case FingerprintManager.FINGERPRINT_PROCESSED: { final int fingerId = arg1; try { clientData.receiver.onProcessed(fingerId); } catch (RemoteException e) { Slog.e(TAG, "can't send message to client. Did it die?", e); mClients.remove(mClients.keyAt(i)); } break; } case FingerprintManager.FINGERPRINT_TEMPLATE_ENROLLING: { final int fingerId = arg1; final int remaining = arg2; if (clientData.state == STATE_ENROLLING) { // Only send enroll updates to clients that are actually enrolling try { clientData.receiver.onEnrollResult(fingerId, remaining); } catch (RemoteException e) { Slog.e(TAG, "can't send message to client. Did it die?", e); mClients.remove(mClients.keyAt(i)); } // Update the database with new finger id. // TODO: move to client code (Settings) if (remaining == 0) { FingerprintUtils.addFingerprintIdForUser(fingerId, mContext.getContentResolver(), clientData.userId); clientData.state = STATE_IDLE; // Nothing left to do } } else { if (DEBUG) Slog.w(TAG, "Client not enrolling"); break; } break; } case FingerprintManager.FINGERPRINT_TEMPLATE_REMOVED: { int fingerId = arg1; if (fingerId == 0) throw new IllegalStateException("Got illegal id from HAL"); FingerprintUtils.removeFingerprintIdForUser(fingerId, mContext.getContentResolver(), clientData.userId); if (clientData.receiver != null) { try { clientData.receiver.onRemoved(fingerId); } catch (RemoteException e) { Slog.e(TAG, "can't send message to client. Did it die?", e); mClients.remove(mClients.keyAt(i)); } } clientData.state = STATE_LISTENING; } break; } } } void startEnroll(IBinder token, long timeout, int userId) { ClientData clientData = mClients.get(token); if (clientData != null) { if (clientData.userId != userId) throw new IllegalStateException("Bad user"); clientData.state = STATE_ENROLLING; nativeEnroll((int) (timeout / MS_PER_SEC)); } else { Slog.w(TAG, "enroll(): No listener registered"); } } void startEnrollCancel(IBinder token, int userId) { ClientData clientData = mClients.get(token); if (clientData != null) { if (clientData.userId != userId) throw new IllegalStateException("Bad user"); clientData.state = STATE_LISTENING; nativeEnrollCancel(); } else { Slog.w(TAG, "enrollCancel(): No listener registered"); } } // Remove all fingerprints for the given user. void startRemove(IBinder token, int fingerId, int userId) { ClientData clientData = mClients.get(token); if (clientData != null) { if (clientData.userId != userId) throw new IllegalStateException("Bad user"); clientData.state = STATE_REMOVING; // The fingerprint id will be removed when we get confirmation from the HAL int result = nativeRemove(fingerId); if (result != 0) { Slog.w(TAG, "Error removing fingerprint with id = " + fingerId); } } else { Slog.w(TAG, "remove(" + token + "): No listener registered"); } } void addListener(IBinder token, IFingerprintServiceReceiver receiver, int userId) { if (DEBUG) Slog.v(TAG, "startListening(" + receiver + ")"); if (mClients.get(token) == null) { ClientData clientData = new ClientData(); clientData.state = STATE_LISTENING; clientData.receiver = receiver; clientData.userId = userId; clientData.tokenWatcher = new TokenWatcher(token); try { token.linkToDeath(clientData.tokenWatcher, 0); mClients.put(token, clientData); } catch (RemoteException e) { Slog.w(TAG, "caught remote exception in linkToDeath: ", e); } } else { if (DEBUG) Slog.v(TAG, "listener already registered for " + token); } } void removeListener(IBinder token, int userId) { if (DEBUG) Slog.v(TAG, "stopListening(" + token + ")"); ClientData clientData = mClients.get(token); if (clientData != null) { token.unlinkToDeath(clientData.tokenWatcher, 0); mClients.remove(token); } else { if (DEBUG) Slog.v(TAG, "listener not registered: " + token); } mClients.remove(token); } void checkPermission(String permisison) { // TODO } private final class FingerprintServiceWrapper extends IFingerprintService.Stub { @Override // Binder call public void enroll(IBinder token, long timeout, int userId) { checkPermission(ENROLL_FINGERPRINT); startEnroll(token, timeout, userId); } @Override // Binder call public void enrollCancel(IBinder token,int userId) { checkPermission(ENROLL_FINGERPRINT); startEnrollCancel(token, userId); } @Override // Binder call public void remove(IBinder token, int fingerprintId, int userId) { checkPermission(ENROLL_FINGERPRINT); // TODO: Maybe have another permission startRemove(token, fingerprintId, userId); } @Override // Binder call public void startListening(IBinder token, IFingerprintServiceReceiver receiver, int userId) { checkPermission(USE_FINGERPRINT); addListener(token, receiver, userId); } @Override // Binder call public void stopListening(IBinder token, int userId) { checkPermission(USE_FINGERPRINT); removeListener(token, userId); } } @Override public void onStart() { publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper()); nativeOpenHal(); } }