blob: 979ce3a26815da3b7f69c4a9ccf519725f688406 [file] [log] [blame]
Kevin Chyna56dff72018-06-19 18:41:12 -07001/*
2 * Copyright (C) 2018 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
Kevin Chyn836f2cf2018-08-27 11:06:39 -070017package com.android.server.biometrics;
Kevin Chyna56dff72018-06-19 18:41:12 -070018
19import android.content.Context;
20import android.hardware.biometrics.BiometricAuthenticator;
21import android.os.AsyncTask;
22import android.os.Environment;
23import android.util.Slog;
24import android.util.Xml;
25
26import com.android.internal.annotations.GuardedBy;
27
28import libcore.io.IoUtils;
29
30import org.xmlpull.v1.XmlPullParser;
31import org.xmlpull.v1.XmlPullParserException;
32
33import java.io.File;
34import java.io.FileInputStream;
35import java.io.FileNotFoundException;
36import java.io.IOException;
37import java.util.ArrayList;
38import java.util.List;
39
40/**
41 * Abstract base class for managing biometrics per user across device reboots.
42 * @hide
43 */
44public abstract class BiometricUserState<T extends BiometricAuthenticator.Identifier> {
45 private static final String TAG = "UserState";
46
47 @GuardedBy("this")
48 protected final ArrayList<T> mBiometrics = new ArrayList<>();
49 protected final Context mContext;
50 protected final File mFile;
51
52 private final Runnable mWriteStateRunnable = new Runnable() {
53 @Override
54 public void run() {
55 doWriteState();
56 }
57 };
58
59 /**
60 * @return The tag for the biometrics. There may be multiple instances of a biometric within.
61 */
62 protected abstract String getBiometricsTag();
63
64 /**
65 * @return The file where the biometric metadata should be stored.
66 */
67 protected abstract String getBiometricFile();
68
69 /**
70 * @return The resource for the name template, this is used to generate the default name.
71 */
72 protected abstract int getNameTemplateResource();
73
74 /**
75 * @return A copy of the list.
76 */
77 protected abstract ArrayList<T> getCopy(ArrayList<T> array);
78
79 /**
80 * @return Writes the cached data to persistent storage.
81 */
82 protected abstract void doWriteState();
83
84 /**
85 * @return
86 */
87 protected abstract void parseBiometricsLocked(XmlPullParser parser)
88 throws IOException, XmlPullParserException;
89
90
91 public BiometricUserState(Context context, int userId) {
92 mFile = getFileForUser(userId);
93 mContext = context;
94 synchronized (this) {
95 readStateSyncLocked();
96 }
97 }
98
99 public void addBiometric(T identifier) {
100 synchronized (this) {
101 mBiometrics.add(identifier);
102 scheduleWriteStateLocked();
103 }
104 }
105
106 public void removeBiometric(int biometricId) {
107 synchronized (this) {
108 for (int i = 0; i < mBiometrics.size(); i++) {
109 if (mBiometrics.get(i).getBiometricId() == biometricId) {
110 mBiometrics.remove(i);
111 scheduleWriteStateLocked();
112 break;
113 }
114 }
115 }
116 }
117
118 public void renameBiometric(int biometricId, CharSequence name) {
119 synchronized (this) {
120 for (int i = 0; i < mBiometrics.size(); i++) {
121 if (mBiometrics.get(i).getBiometricId() == biometricId) {
122 BiometricAuthenticator.Identifier identifier = mBiometrics.get(i);
123 identifier.setName(name);
124 scheduleWriteStateLocked();
125 break;
126 }
127 }
128 }
129 }
130
131 public List<T> getBiometrics() {
132 synchronized (this) {
133 return getCopy(mBiometrics);
134 }
135 }
136
137 /**
138 * Finds a unique name for the given fingerprint
139 * @return unique name
140 */
141 public String getUniqueName() {
142 int guess = 1;
143 while (true) {
144 // Not the most efficient algorithm in the world, but there shouldn't be more than 10
145 String name = mContext.getString(getNameTemplateResource(), guess);
146 if (isUnique(name)) {
147 return name;
148 }
149 guess++;
150 }
151 }
152
153 private boolean isUnique(String name) {
154 for (T identifier : mBiometrics) {
155 if (identifier.getName().equals(name)) {
156 return false;
157 }
158 }
159 return true;
160 }
161
162 private File getFileForUser(int userId) {
163 return new File(Environment.getUserSystemDirectory(userId), getBiometricFile());
164 }
165
166 private void scheduleWriteStateLocked() {
167 AsyncTask.execute(mWriteStateRunnable);
168 }
169
170 @GuardedBy("this")
171 private void readStateSyncLocked() {
172 FileInputStream in;
173 if (!mFile.exists()) {
174 return;
175 }
176 try {
177 in = new FileInputStream(mFile);
178 } catch (FileNotFoundException fnfe) {
179 Slog.i(TAG, "No fingerprint state");
180 return;
181 }
182 try {
183 XmlPullParser parser = Xml.newPullParser();
184 parser.setInput(in, null);
185 parseStateLocked(parser);
186
187 } catch (XmlPullParserException | IOException e) {
188 throw new IllegalStateException("Failed parsing settings file: "
189 + mFile , e);
190 } finally {
191 IoUtils.closeQuietly(in);
192 }
193 }
194
195 @GuardedBy("this")
196 private void parseStateLocked(XmlPullParser parser)
197 throws IOException, XmlPullParserException {
198 final int outerDepth = parser.getDepth();
199 int type;
200 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
201 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
202 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
203 continue;
204 }
205
206 String tagName = parser.getName();
207 if (tagName.equals(getBiometricsTag())) {
208 parseBiometricsLocked(parser);
209 }
210 }
211 }
212
213}