• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 
17 package com.android.server.biometrics.sensors;
18 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.hardware.biometrics.BiometricAuthenticator;
22 import android.os.AsyncTask;
23 import android.os.Environment;
24 import android.util.AtomicFile;
25 import android.util.Slog;
26 import android.util.Xml;
27 
28 import com.android.internal.annotations.GuardedBy;
29 import com.android.modules.utils.TypedXmlPullParser;
30 import com.android.modules.utils.TypedXmlSerializer;
31 
32 import libcore.io.IoUtils;
33 
34 import org.xmlpull.v1.XmlPullParser;
35 import org.xmlpull.v1.XmlPullParserException;
36 
37 import java.io.File;
38 import java.io.FileInputStream;
39 import java.io.FileNotFoundException;
40 import java.io.FileOutputStream;
41 import java.io.IOException;
42 import java.util.ArrayList;
43 import java.util.List;
44 
45 /**
46  * Abstract base class for managing biometrics per user across device reboots.
47  * @hide
48  */
49 public abstract class BiometricUserState<T extends BiometricAuthenticator.Identifier> {
50     private static final String TAG = "UserState";
51 
52     private static final String TAG_INVALIDATION = "authenticatorIdInvalidation_tag";
53     private static final String ATTR_INVALIDATION = "authenticatorIdInvalidation_attr";
54 
55     @GuardedBy("this")
56     protected final ArrayList<T> mBiometrics = new ArrayList<>();
57     protected boolean mInvalidationInProgress;
58     protected final Context mContext;
59     protected final File mFile;
60     private boolean mIsInvalidBiometricState = false;
61 
62     private final Runnable mWriteStateRunnable = this::doWriteStateInternal;
63 
64     /**
65      * @return The tag for the biometrics. There may be multiple instances of a biometric within.
66      */
getBiometricsTag()67     protected abstract String getBiometricsTag();
68 
69     /**
70      * @return The resource for the name template, this is used to generate the default name.
71      */
getNameTemplateResource()72     protected abstract int getNameTemplateResource();
73 
74     /**
75      * @return A copy of the list.
76      */
getCopy(ArrayList<T> array)77     protected abstract ArrayList<T> getCopy(ArrayList<T> array);
78 
doWriteState(@onNull TypedXmlSerializer serializer)79     protected abstract void doWriteState(@NonNull TypedXmlSerializer serializer) throws Exception;
80 
81     /**
82      * @Writes the cached data to persistent storage.
83      */
doWriteStateInternal()84     private void doWriteStateInternal() {
85         AtomicFile destination = new AtomicFile(mFile);
86 
87         FileOutputStream out = null;
88 
89         try {
90             out = destination.startWrite();
91             TypedXmlSerializer serializer = Xml.resolveSerializer(out);
92             serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
93             serializer.startDocument(null, true);
94 
95             // Store the authenticatorId
96             serializer.startTag(null, TAG_INVALIDATION);
97             serializer.attributeBoolean(null, ATTR_INVALIDATION, mInvalidationInProgress);
98             serializer.endTag(null, TAG_INVALIDATION);
99 
100             // Do any additional serialization that subclasses may require
101             doWriteState(serializer);
102 
103             serializer.endDocument();
104             destination.finishWrite(out);
105         } catch (Throwable t) {
106             Slog.e(TAG, "Failed to write settings, restoring backup", t);
107             destination.failWrite(out);
108             throw new IllegalStateException("Failed to write to file: " + mFile.toString(), t);
109         } finally {
110             IoUtils.closeQuietly(out);
111         }
112     }
113 
114     /**
115      * @return
116      */
parseBiometricsLocked(TypedXmlPullParser parser)117     protected abstract void parseBiometricsLocked(TypedXmlPullParser parser)
118             throws IOException, XmlPullParserException;
119 
120 
BiometricUserState(Context context, int userId, @NonNull String fileName)121     public BiometricUserState(Context context, int userId, @NonNull String fileName) {
122         mFile = getFileForUser(userId, fileName);
123         mContext = context;
124         synchronized (this) {
125             readStateSyncLocked();
126         }
127     }
128 
setInvalidationInProgress(boolean invalidationInProgress)129     public void setInvalidationInProgress(boolean invalidationInProgress) {
130         synchronized (this) {
131             mInvalidationInProgress = invalidationInProgress;
132             scheduleWriteStateLocked();
133         }
134     }
135 
isInvalidationInProgress()136     public boolean isInvalidationInProgress() {
137         synchronized (this) {
138             return mInvalidationInProgress;
139         }
140     }
141 
addBiometric(T identifier)142     public void addBiometric(T identifier) {
143         synchronized (this) {
144             mBiometrics.add(identifier);
145             scheduleWriteStateLocked();
146         }
147     }
148 
removeBiometric(int biometricId)149     public void removeBiometric(int biometricId) {
150         synchronized (this) {
151             for (int i = 0; i < mBiometrics.size(); i++) {
152                 if (mBiometrics.get(i).getBiometricId() == biometricId) {
153                     mBiometrics.remove(i);
154                     scheduleWriteStateLocked();
155                     break;
156                 }
157             }
158         }
159     }
160 
renameBiometric(int biometricId, CharSequence name)161     public void renameBiometric(int biometricId, CharSequence name) {
162         synchronized (this) {
163             for (int i = 0; i < mBiometrics.size(); i++) {
164                 if (mBiometrics.get(i).getBiometricId() == biometricId) {
165                     BiometricAuthenticator.Identifier identifier = mBiometrics.get(i);
166                     identifier.setName(name);
167                     scheduleWriteStateLocked();
168                     break;
169                 }
170             }
171         }
172     }
173 
getBiometrics()174     public List<T> getBiometrics() {
175         synchronized (this) {
176             return getCopy(mBiometrics);
177         }
178     }
179 
180     /**
181      * Finds a unique name for the given fingerprint
182      * @return unique name
183      */
getUniqueName()184     public String getUniqueName() {
185         int guess = 1;
186         while (true) {
187             // Not the most efficient algorithm in the world, but there shouldn't be more than 10
188             String name = mContext.getString(getNameTemplateResource(), guess);
189             if (isUnique(name)) {
190                 return name;
191             }
192             guess++;
193         }
194     }
195 
196     /**
197      * Return true if the biometric file is correctly read. Otherwise return false.
198      */
isInvalidBiometricState()199     public boolean isInvalidBiometricState() {
200         return mIsInvalidBiometricState;
201     }
202 
203     /**
204      * Delete the file of the biometric state.
205      */
deleteBiometricFile()206     public void deleteBiometricFile() {
207         synchronized (this) {
208             if (!mFile.exists()) {
209                 return;
210             }
211             if (mFile.delete()) {
212                 Slog.i(TAG, mFile + " is deleted successfully");
213             } else {
214                 Slog.i(TAG, "Failed to delete " + mFile);
215             }
216         }
217     }
218 
isUnique(String name)219     private boolean isUnique(String name) {
220         for (T identifier : mBiometrics) {
221             if (identifier.getName().equals(name)) {
222                 return false;
223             }
224         }
225         return true;
226     }
227 
getFileForUser(int userId, @NonNull String fileName)228     private File getFileForUser(int userId, @NonNull String fileName) {
229         return new File(Environment.getUserSystemDirectory(userId), fileName);
230     }
231 
scheduleWriteStateLocked()232     private void scheduleWriteStateLocked() {
233         AsyncTask.execute(mWriteStateRunnable);
234     }
235 
236     @GuardedBy("this")
readStateSyncLocked()237     private void readStateSyncLocked() {
238         FileInputStream in;
239         if (!mFile.exists()) {
240             return;
241         }
242         try {
243             in = new FileInputStream(mFile);
244         } catch (FileNotFoundException fnfe) {
245             Slog.i(TAG, "No fingerprint state", fnfe);
246             mIsInvalidBiometricState = true;
247             return;
248         }
249         try {
250             TypedXmlPullParser parser = Xml.resolvePullParser(in);
251             parseStateLocked(parser);
252 
253         } catch (XmlPullParserException | IOException e) {
254             throw new IllegalStateException("Failed parsing settings file: "
255                     + mFile , e);
256         } finally {
257             IoUtils.closeQuietly(in);
258         }
259     }
260 
261     @GuardedBy("this")
parseStateLocked(TypedXmlPullParser parser)262     private void parseStateLocked(TypedXmlPullParser parser)
263             throws IOException, XmlPullParserException {
264         final int outerDepth = parser.getDepth();
265         int type;
266         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
267                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
268             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
269                 continue;
270             }
271 
272             String tagName = parser.getName();
273             if (tagName.equals(getBiometricsTag())) {
274                 parseBiometricsLocked(parser);
275             } else if (tagName.equals(TAG_INVALIDATION)) {
276                 mInvalidationInProgress = parser.getAttributeBoolean(null, ATTR_INVALIDATION);
277             }
278         }
279     }
280 
281 }
282