Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ddeed15e authored by Andy Mast's avatar Andy Mast
Browse files

Fingerprint [1/2]: Numerous Fingerprint Improvements

1. Introduce getEnrolledFingerprints
2. Introduce Fingerprint object to track name/id/userid
3. Store fingerprints in global settings instead of per user.
   This is because vendor implementations are also global.
4. Give newly enrolled fingerprints a default name.
5. Allow fingerprint names to be changed
6. When getting fingerprint data, merge the native stored fingerprints data
   with settings provider fingerprint data.
   - This is because the vendor will store the actual fingerprint, but all the
     metadata is stored by the provider.
7. Provide basic dump commands in fingerprints

See also:
  libhardware
  vendor specific hal implementation

Change-Id: Iafd2d2f568df5ec1e81f9252a784420ad05bbffe

Conflicts:
	core/java/android/provider/Settings.java
parent aef5cdb5
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The CyanogenMod 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 android.hardware.fingerprint;

/** @hide */
parcelable Fingerprint;
+197 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 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 android.hardware.fingerprint;

import android.os.Parcel;
import android.os.Parcelable;
import android.util.JsonReader;
import android.util.JsonToken;
import android.util.JsonWriter;
import android.util.Log;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.LinkedList;
import java.util.List;

/**
 * @hide
 */
public class Fingerprint implements Parcelable {
    private static final String TAG = Fingerprint.class.getSimpleName();

    /**
     * Note: Do not change these member variables without also changing them in JNI.
     */
    private String mName;
    private int mFingerId;
    private int mUserId;

    /**
     * Note: Do not change this constructor without also changing it in JNI.
     */
    public Fingerprint() { };

    public Fingerprint(String name, Integer fingerId, Integer userId) {
        mName = name;
        mFingerId = fingerId;
        mUserId = userId;
    }

    private Fingerprint(Parcel source) {
        mName = source.readString();
        mFingerId = source.readInt();
        mUserId = source.readInt();
    }

    public String getName() {
        return mName;
    }

    public Integer getFingerId() {
        return mFingerId;
    }

    public Integer getUserId() {
        return mUserId;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mName);
        dest.writeInt(mFingerId);
        dest.writeInt(mUserId);
    }

    public static final Parcelable.Creator<Fingerprint> CREATOR
            = new Parcelable.Creator<Fingerprint>() {
        public Fingerprint createFromParcel(Parcel source) {
            return new Fingerprint(source);
        }

        public Fingerprint[] newArray(int size) {
            return new Fingerprint[size];
        }
    };

    public static class Builder {
        private String mName;
        private Integer mId;
        private Integer mUserId;

        public Builder(Fingerprint fp) {
            this.mName = fp.getName();
            this.mId = fp.getFingerId();
            this.mUserId = fp.getUserId();
        }

        public Builder name(String name) {
            this.mName = name;
            return this;
        }

        public Builder id(int id) {
            this.mId = id;
            return this;
        }

        public Fingerprint build() {
            return new Fingerprint(mName, mId, mUserId);
        }
    }

    public static class JsonSerializer {
        private static final String NAME_ID = "fingerId";
        private static final String NAME_FINGERNAME = "fingerName";
        private static final String NAME_USERID = "userId";

        public static String toJson(List<Fingerprint> fingerprints) {
            String json = null;
            try (
                Writer writer = new StringWriter();
                JsonWriter jsonWriter = new JsonWriter(writer)
            ) {
                jsonWriter.beginArray();
                for(Fingerprint fingerprint : fingerprints) {
                    writeFingerprint(jsonWriter, fingerprint);
                }
                jsonWriter.endArray();
                json = writer.toString();
            } catch (IOException e) {
                Log.e(TAG, "Could not serialize fingerprint", e);
            }
            return json;
        };

        private static void writeFingerprint(JsonWriter writer, Fingerprint fingerprint)
                throws IOException {
            writer.beginObject();
            writer.name(NAME_ID).value(fingerprint.getFingerId());
            writer.name(NAME_FINGERNAME).value(fingerprint.getName());
            writer.name(NAME_USERID).value(fingerprint.getUserId());
            writer.endObject();
        }

        public static List<Fingerprint> fromJson(String json) {
            List<Fingerprint> fingerprints = new LinkedList<Fingerprint>();
            if (json == null) return fingerprints;

            try (
                StringReader reader = new StringReader(json);
                JsonReader jsonReader = new JsonReader(reader)
            ) {
                jsonReader.beginArray();
                while(jsonReader.hasNext()) {
                    fingerprints.add(readFingerprint(jsonReader));
                }
                jsonReader.endArray();
            } catch(Exception e) {
                Log.e(TAG, "Could not parse fingerprint from: " + json, e);
            }
            return fingerprints;
        }

        private static Fingerprint readFingerprint(JsonReader reader) throws IOException {
            String fingerName = null;
            int id = 0;
            int userId = 0;
            reader.beginObject();
            while(reader.hasNext()) {
                String name = reader.nextName();
                if (NAME_ID.equals(name) && reader.peek() != JsonToken.NULL) {
                    id = reader.nextInt();
                } else if (NAME_FINGERNAME.equals(name) && reader.peek() != JsonToken.NULL) {
                    fingerName = reader.nextString();
                } else if (NAME_USERID.equals(name)) {
                    userId = reader.nextInt();
                } else {
                    reader.skipValue();
                }
            }
            reader.endObject();

            return new Fingerprint(fingerName, id, userId);
        }
    }
}
+6 −6
Original line number Diff line number Diff line
@@ -4742,12 +4742,6 @@ public final class Settings {
        public static final String LOCK_SCREEN_APPWIDGET_IDS =
            "lock_screen_appwidget_ids";

        /**
         * List of enrolled fingerprint identifiers (comma-delimited).
         * @hide
         */
        public static final String USER_FINGERPRINT_IDS = "user_fingerprint_ids";

        /**
         * Id of the appwidget shown on the lock screen when appwidgets are disabled.
         * @hide
@@ -8010,6 +8004,12 @@ public final class Settings {
         */
        public static final String LTE_SERVICE_FORCED = "lte_service_forced";

        /**
         * List of enrolled fingerprint identifiers (comma-delimited).
         * @hide
         */
        public static final String USER_FINGERPRINTS = "user_fingerprints";

        /**
         * Settings to backup. This is here so that it's in the same place as the settings
         * keys and easy to update.
+38 −4
Original line number Diff line number Diff line
@@ -17,11 +17,9 @@
package android.service.fingerprint;

import android.app.ActivityManagerNative;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.hardware.fingerprint.Fingerprint;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -31,6 +29,9 @@ import android.provider.Settings;
import android.util.Log;
import android.util.Slog;

import java.util.Collections;
import java.util.List;

/**
 * A class that coordinates access to the fingerprint hardware.
 * @hide
@@ -141,7 +142,7 @@ public class FingerprintManager {
    public boolean enrolledAndEnabled() {
        ContentResolver res = mContext.getContentResolver();
        return Settings.Secure.getInt(res, "fingerprint_enabled", 0) != 0
                && FingerprintUtils.getFingerprintIdsForUser(res, getCurrentUserId()).length > 0;
                && FingerprintUtils.getFingerprintsForUser(res, getCurrentUserId()).size() > 0;
    }

    /**
@@ -203,6 +204,26 @@ public class FingerprintManager {
        }
    }

    /**
     * Rename the fingerprint
     */
    public void setFingerprintName(int fingerprintId, String newName) {
        if (mServiceReceiver == null) {
            sendError(FINGERPRINT_ERROR_NO_RECEIVER, 0, 0);
            return;
        }
        if (mService != null) {
            try {
                mService.setFingerprintName(mToken, fingerprintId, newName, getCurrentUserId());
            } catch (RemoteException e) {
                Log.v(TAG, "Remote exception renaming fingerprintId: " + fingerprintId, e);
            }
        } else {
            Log.w(TAG, "setFingerprintName(): Service not connected!");
            sendError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0, 0);
        }
    }

    /**
     * Starts listening for fingerprint events.  When a finger is scanned or recognized, the
     * client will be notified via the callback.
@@ -264,6 +285,19 @@ public class FingerprintManager {
        }
    }

    public List<Fingerprint> getEnrolledFingerprints() {
        if (mService != null) {
            try {
                return mService.getEnrolledFingerprints(mToken, getCurrentUserId());
            } catch (RemoteException e) {
                Log.v(TAG, "Remote exception in getEnrolledFingerprints(): ", e);
            }
        } else {
            Log.w(TAG, "getEnrolledFingerprints(): Service not connected!");
        }
        return Collections.emptyList();
    }

    private void sendError(int msg, int arg1, int arg2) {
        mHandler.obtainMessage(msg, arg1, arg2);
    }
+75 −37
Original line number Diff line number Diff line
@@ -17,11 +17,16 @@
package android.service.fingerprint;

import android.content.ContentResolver;
import android.content.Context;
import android.hardware.fingerprint.Fingerprint;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

/**
 * Utility class for dealing with fingerprints and fingerprint settings.
@@ -29,42 +34,48 @@ import java.util.Arrays;
 */
public
class FingerprintUtils {
    private static final boolean DEBUG = true;
    private static final boolean DEBUG = false;
    private static final String TAG = "FingerprintUtils";

    public static int[] getFingerprintIdsForUser(ContentResolver res, int userId) {
        String fingerIdsRaw = Settings.Secure.getStringForUser(res,
                Settings.Secure.USER_FINGERPRINT_IDS, userId);

        int result[] = {};
        if (!TextUtils.isEmpty(fingerIdsRaw)) {
            String[] fingerStringIds = fingerIdsRaw.replace("[","").replace("]","").split(", ");
            result = new int[fingerStringIds.length];
            for (int i = 0; i < result.length; i++) {
                try {
                    result[i] = Integer.decode(fingerStringIds[i]);
                } catch (NumberFormatException e) {
                    if (DEBUG) Log.d(TAG, "Error when parsing finger id " + fingerStringIds[i]);
                }
            }
    public static List<Fingerprint> getFingerprintsForUser(ContentResolver res, int userId) {
        String fingerprintJson = Settings.Global.getString(res,
                Settings.Global.USER_FINGERPRINTS);
        List<Fingerprint> fingerprints = Fingerprint.JsonSerializer.fromJson(fingerprintJson);

        // Filter out fingerprints that do not match userId
        Iterator<Fingerprint> iter = fingerprints.iterator();
        while(iter.hasNext()) {
            Fingerprint fingerprint = iter.next();
            if (fingerprint.getUserId() != userId) {
                iter.remove();
            }
        return result;
        }

    public static void addFingerprintIdForUser(int fingerId, ContentResolver res, int userId) {
        int[] fingerIds = getFingerprintIdsForUser(res, userId);
        return fingerprints;
    }

    public static void addFingerprintIdForUser(int fingerId, Context context,
                                               int userId) {
        // FingerId 0 has special meaning.
        if (fingerId == 0) return;

        // Get existing fingerprints from secure settings
        List<Fingerprint> fingerprints =
                getFingerprintsForUser(context.getContentResolver(), userId);

        // Don't allow dups
        for (int i = 0; i < fingerIds.length; i++) {
            if (fingerIds[i] == fingerId) return;
        for (Fingerprint fingerprint : fingerprints) {
            if (fingerprint.getFingerId() == fingerId) {
                return;
            }
        }
        int[] newList = Arrays.copyOf(fingerIds, fingerIds.length + 1);
        newList[fingerIds.length] = fingerId;
        Settings.Secure.putStringForUser(res, Settings.Secure.USER_FINGERPRINT_IDS,
                Arrays.toString(newList), userId);

        // Add the new fingerprint and write back to secure settings
        String defaultName =
                context.getString(com.android.internal.R.string.fingerprint_default_name, fingerId);
        Fingerprint fingerprint = new Fingerprint(defaultName, fingerId, userId);
        fingerprints.add(fingerprint);
        saveFingerprints(fingerprints, context.getContentResolver(), userId);
    }

    public static boolean removeFingerprintIdForUser(int fingerId, ContentResolver res, int userId)
@@ -74,21 +85,48 @@ class FingerprintUtils {
        // something bad has happened.
        if (fingerId == 0) throw new IllegalStateException("Bad fingerId");

        int[] fingerIds = getFingerprintIdsForUser(res, userId);
        int[] resultIds = Arrays.copyOf(fingerIds, fingerIds.length);
        int resultCount = 0;
        for (int i = 0; i < fingerIds.length; i++) {
            if (fingerId != fingerIds[i]) {
                resultIds[resultCount++] = fingerIds[i];
        List<Fingerprint> fingerprints = getFingerprintsForUser(res, userId);

        Iterator<Fingerprint> iter = fingerprints.iterator();
        while(iter.hasNext()) {
            if (iter.next().getFingerId() == fingerId) {
                iter.remove();
            }
        }
        if (resultCount > 0) {
            Settings.Secure.putStringForUser(res, Settings.Secure.USER_FINGERPRINT_IDS,
                    Arrays.toString(Arrays.copyOf(resultIds, resultCount)), userId);

        saveFingerprints(fingerprints, res, userId);
        return true;
    }
        return false;



    public static void setFingerprintName(int fingerId, String name,
                                          ContentResolver res, int userId) {
        // FingerId 0 has special meaning. The HAL layer is supposed to remove each finger one
        // at a time and invoke notify() for each fingerId.  If we get called with 0 here, it means
        // something bad has happened.
        if (fingerId == 0) throw new IllegalStateException("Bad fingerId");

        // Find & Replace old fingerprint with newly named fingerprint in the list
        List<Fingerprint> fingerprints = getFingerprintsForUser(res, userId);
        ListIterator<Fingerprint> iter = fingerprints.listIterator();
        while(iter.hasNext()) {
            Fingerprint fingerprint = iter.next();
            if (fingerprint.getFingerId() == fingerId) {
                Fingerprint.Builder builder = new Fingerprint.Builder(fingerprint);
                builder.name(name);
                iter.set(builder.build());
            }
        }

};
        saveFingerprints(fingerprints, res, userId);
    }


    public static void saveFingerprints(List<Fingerprint> fingerprints,
                                         ContentResolver res, int userId) {
        String json = Fingerprint.JsonSerializer.toJson(fingerprints);
        Settings.Global.putString(res, Settings.Global.USER_FINGERPRINTS, json);
    }
}
Loading