1 /*
2  * Copyright (C) 2015 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.fingerprint;
18 
19 import android.content.Context;
20 import android.hardware.fingerprint.Fingerprint;
21 import android.os.AsyncTask;
22 import android.os.Environment;
23 import android.util.AtomicFile;
24 import android.util.Slog;
25 import android.util.Xml;
26 
27 import com.android.internal.annotations.GuardedBy;
28 
29 import libcore.io.IoUtils;
30 import org.xmlpull.v1.XmlPullParser;
31 import org.xmlpull.v1.XmlPullParserException;
32 import org.xmlpull.v1.XmlSerializer;
33 
34 import java.io.File;
35 import java.io.FileInputStream;
36 import java.io.FileNotFoundException;
37 import java.io.FileOutputStream;
38 import java.io.IOException;
39 import java.util.ArrayList;
40 import java.util.List;
41 
42 /**
43  * Class managing the set of fingerprint per user across device reboots.
44  */
45 class FingerprintsUserState {
46 
47     private static final String TAG = "FingerprintState";
48     private static final String FINGERPRINT_FILE = "settings_fingerprint.xml";
49 
50     private static final String TAG_FINGERPRINTS = "fingerprints";
51     private static final String TAG_FINGERPRINT = "fingerprint";
52     private static final String ATTR_NAME = "name";
53     private static final String ATTR_GROUP_ID = "groupId";
54     private static final String ATTR_FINGER_ID = "fingerId";
55     private static final String ATTR_DEVICE_ID = "deviceId";
56 
57     private final File mFile;
58 
59     @GuardedBy("this")
60     private final ArrayList<Fingerprint> mFingerprints = new ArrayList<Fingerprint>();
61     private final Context mCtx;
62 
FingerprintsUserState(Context ctx, int userId)63     public FingerprintsUserState(Context ctx, int userId) {
64         mFile = getFileForUser(userId);
65         mCtx = ctx;
66         synchronized (this) {
67             readStateSyncLocked();
68         }
69     }
70 
addFingerprint(int fingerId, int groupId)71     public void addFingerprint(int fingerId, int groupId) {
72         synchronized (this) {
73             mFingerprints.add(new Fingerprint(getUniqueName(), groupId, fingerId, 0));
74             scheduleWriteStateLocked();
75         }
76     }
77 
removeFingerprint(int fingerId)78     public void removeFingerprint(int fingerId) {
79         synchronized (this) {
80             for (int i = 0; i < mFingerprints.size(); i++) {
81                 if (mFingerprints.get(i).getFingerId() == fingerId) {
82                     mFingerprints.remove(i);
83                     scheduleWriteStateLocked();
84                     break;
85                 }
86             }
87         }
88     }
89 
renameFingerprint(int fingerId, CharSequence name)90     public void renameFingerprint(int fingerId, CharSequence name) {
91         synchronized (this) {
92             for (int i = 0; i < mFingerprints.size(); i++) {
93                 if (mFingerprints.get(i).getFingerId() == fingerId) {
94                     Fingerprint old = mFingerprints.get(i);
95                     mFingerprints.set(i, new Fingerprint(name, old.getGroupId(), old.getFingerId(),
96                             old.getDeviceId()));
97                     scheduleWriteStateLocked();
98                     break;
99                 }
100             }
101         }
102     }
103 
getFingerprints()104     public List<Fingerprint> getFingerprints() {
105         synchronized (this) {
106             return getCopy(mFingerprints);
107         }
108     }
109 
110     /**
111      * Finds a unique name for the given fingerprint
112      * @return unique name
113      */
getUniqueName()114     private String getUniqueName() {
115         int guess = 1;
116         while (true) {
117             // Not the most efficient algorithm in the world, but there shouldn't be more than 10
118             String name = mCtx.getString(com.android.internal.R.string.fingerprint_name_template,
119                     guess);
120             if (isUnique(name)) {
121                 return name;
122             }
123             guess++;
124         }
125     }
126 
isUnique(String name)127     private boolean isUnique(String name) {
128         for (Fingerprint fp : mFingerprints) {
129             if (fp.getName().equals(name)) {
130                 return false;
131             }
132         }
133         return true;
134     }
135 
getFileForUser(int userId)136     private static File getFileForUser(int userId) {
137         return new File(Environment.getUserSystemDirectory(userId), FINGERPRINT_FILE);
138     }
139 
140     private final Runnable mWriteStateRunnable = new Runnable() {
141         @Override
142         public void run() {
143             doWriteState();
144         }
145     };
146 
scheduleWriteStateLocked()147     private void scheduleWriteStateLocked() {
148         AsyncTask.execute(mWriteStateRunnable);
149     }
150 
getCopy(ArrayList<Fingerprint> array)151     private ArrayList<Fingerprint> getCopy(ArrayList<Fingerprint> array) {
152         ArrayList<Fingerprint> result = new ArrayList<Fingerprint>(array.size());
153         for (int i = 0; i < array.size(); i++) {
154             Fingerprint fp = array.get(i);
155             result.add(new Fingerprint(fp.getName(), fp.getGroupId(), fp.getFingerId(),
156                     fp.getDeviceId()));
157         }
158         return result;
159     }
160 
doWriteState()161     private void doWriteState() {
162         AtomicFile destination = new AtomicFile(mFile);
163 
164         ArrayList<Fingerprint> fingerprints;
165 
166         synchronized (this) {
167             fingerprints = getCopy(mFingerprints);
168         }
169 
170         FileOutputStream out = null;
171         try {
172             out = destination.startWrite();
173 
174             XmlSerializer serializer = Xml.newSerializer();
175             serializer.setOutput(out, "utf-8");
176             serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
177             serializer.startDocument(null, true);
178             serializer.startTag(null, TAG_FINGERPRINTS);
179 
180             final int count = fingerprints.size();
181             for (int i = 0; i < count; i++) {
182                 Fingerprint fp = fingerprints.get(i);
183                 serializer.startTag(null, TAG_FINGERPRINT);
184                 serializer.attribute(null, ATTR_FINGER_ID, Integer.toString(fp.getFingerId()));
185                 serializer.attribute(null, ATTR_NAME, fp.getName().toString());
186                 serializer.attribute(null, ATTR_GROUP_ID, Integer.toString(fp.getGroupId()));
187                 serializer.attribute(null, ATTR_DEVICE_ID, Long.toString(fp.getDeviceId()));
188                 serializer.endTag(null, TAG_FINGERPRINT);
189             }
190 
191             serializer.endTag(null, TAG_FINGERPRINTS);
192             serializer.endDocument();
193             destination.finishWrite(out);
194 
195             // Any error while writing is fatal.
196         } catch (Throwable t) {
197             Slog.wtf(TAG, "Failed to write settings, restoring backup", t);
198             destination.failWrite(out);
199             throw new IllegalStateException("Failed to write fingerprints", t);
200         } finally {
201             IoUtils.closeQuietly(out);
202         }
203     }
204 
readStateSyncLocked()205     private void readStateSyncLocked() {
206         FileInputStream in;
207         if (!mFile.exists()) {
208             return;
209         }
210         try {
211             in = new FileInputStream(mFile);
212         } catch (FileNotFoundException fnfe) {
213             Slog.i(TAG, "No fingerprint state");
214             return;
215         }
216         try {
217             XmlPullParser parser = Xml.newPullParser();
218             parser.setInput(in, null);
219             parseStateLocked(parser);
220 
221         } catch (XmlPullParserException | IOException e) {
222             throw new IllegalStateException("Failed parsing settings file: "
223                     + mFile , e);
224         } finally {
225             IoUtils.closeQuietly(in);
226         }
227     }
228 
parseStateLocked(XmlPullParser parser)229     private void parseStateLocked(XmlPullParser parser)
230             throws IOException, XmlPullParserException {
231         final int outerDepth = parser.getDepth();
232         int type;
233         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
234                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
235             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
236                 continue;
237             }
238 
239             String tagName = parser.getName();
240             if (tagName.equals(TAG_FINGERPRINTS)) {
241                 parseFingerprintsLocked(parser);
242             }
243         }
244     }
245 
parseFingerprintsLocked(XmlPullParser parser)246     private void parseFingerprintsLocked(XmlPullParser parser)
247             throws IOException, XmlPullParserException {
248 
249         final int outerDepth = parser.getDepth();
250         int type;
251         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
252                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
253             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
254                 continue;
255             }
256 
257             String tagName = parser.getName();
258             if (tagName.equals(TAG_FINGERPRINT)) {
259                 String name = parser.getAttributeValue(null, ATTR_NAME);
260                 String groupId = parser.getAttributeValue(null, ATTR_GROUP_ID);
261                 String fingerId = parser.getAttributeValue(null, ATTR_FINGER_ID);
262                 String deviceId = parser.getAttributeValue(null, ATTR_DEVICE_ID);
263                 mFingerprints.add(new Fingerprint(name, Integer.parseInt(groupId),
264                         Integer.parseInt(fingerId), Integer.parseInt(deviceId)));
265             }
266         }
267     }
268 
269 }
270