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 61 private final Runnable mWriteStateRunnable = this::doWriteStateInternal; 62 63 /** 64 * @return The tag for the biometrics. There may be multiple instances of a biometric within. 65 */ getBiometricsTag()66 protected abstract String getBiometricsTag(); 67 68 /** 69 * @return The resource for the name template, this is used to generate the default name. 70 */ getNameTemplateResource()71 protected abstract int getNameTemplateResource(); 72 73 /** 74 * @return A copy of the list. 75 */ getCopy(ArrayList<T> array)76 protected abstract ArrayList<T> getCopy(ArrayList<T> array); 77 doWriteState(@onNull TypedXmlSerializer serializer)78 protected abstract void doWriteState(@NonNull TypedXmlSerializer serializer) throws Exception; 79 80 /** 81 * @Writes the cached data to persistent storage. 82 */ doWriteStateInternal()83 private void doWriteStateInternal() { 84 AtomicFile destination = new AtomicFile(mFile); 85 86 FileOutputStream out = null; 87 88 try { 89 out = destination.startWrite(); 90 TypedXmlSerializer serializer = Xml.resolveSerializer(out); 91 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 92 serializer.startDocument(null, true); 93 94 // Store the authenticatorId 95 serializer.startTag(null, TAG_INVALIDATION); 96 serializer.attributeBoolean(null, ATTR_INVALIDATION, mInvalidationInProgress); 97 serializer.endTag(null, TAG_INVALIDATION); 98 99 // Do any additional serialization that subclasses may require 100 doWriteState(serializer); 101 102 serializer.endDocument(); 103 destination.finishWrite(out); 104 } catch (Throwable t) { 105 Slog.wtf(TAG, "Failed to write settings, restoring backup", t); 106 destination.failWrite(out); 107 throw new IllegalStateException("Failed to write to file: " + mFile.toString(), t); 108 } finally { 109 IoUtils.closeQuietly(out); 110 } 111 } 112 113 /** 114 * @return 115 */ parseBiometricsLocked(TypedXmlPullParser parser)116 protected abstract void parseBiometricsLocked(TypedXmlPullParser parser) 117 throws IOException, XmlPullParserException; 118 119 BiometricUserState(Context context, int userId, @NonNull String fileName)120 public BiometricUserState(Context context, int userId, @NonNull String fileName) { 121 mFile = getFileForUser(userId, fileName); 122 mContext = context; 123 synchronized (this) { 124 readStateSyncLocked(); 125 } 126 } 127 setInvalidationInProgress(boolean invalidationInProgress)128 public void setInvalidationInProgress(boolean invalidationInProgress) { 129 synchronized (this) { 130 mInvalidationInProgress = invalidationInProgress; 131 scheduleWriteStateLocked(); 132 } 133 } 134 isInvalidationInProgress()135 public boolean isInvalidationInProgress() { 136 synchronized (this) { 137 return mInvalidationInProgress; 138 } 139 } 140 addBiometric(T identifier)141 public void addBiometric(T identifier) { 142 synchronized (this) { 143 mBiometrics.add(identifier); 144 scheduleWriteStateLocked(); 145 } 146 } 147 removeBiometric(int biometricId)148 public void removeBiometric(int biometricId) { 149 synchronized (this) { 150 for (int i = 0; i < mBiometrics.size(); i++) { 151 if (mBiometrics.get(i).getBiometricId() == biometricId) { 152 mBiometrics.remove(i); 153 scheduleWriteStateLocked(); 154 break; 155 } 156 } 157 } 158 } 159 renameBiometric(int biometricId, CharSequence name)160 public void renameBiometric(int biometricId, CharSequence name) { 161 synchronized (this) { 162 for (int i = 0; i < mBiometrics.size(); i++) { 163 if (mBiometrics.get(i).getBiometricId() == biometricId) { 164 BiometricAuthenticator.Identifier identifier = mBiometrics.get(i); 165 identifier.setName(name); 166 scheduleWriteStateLocked(); 167 break; 168 } 169 } 170 } 171 } 172 getBiometrics()173 public List<T> getBiometrics() { 174 synchronized (this) { 175 return getCopy(mBiometrics); 176 } 177 } 178 179 /** 180 * Finds a unique name for the given fingerprint 181 * @return unique name 182 */ getUniqueName()183 public String getUniqueName() { 184 int guess = 1; 185 while (true) { 186 // Not the most efficient algorithm in the world, but there shouldn't be more than 10 187 String name = mContext.getString(getNameTemplateResource(), guess); 188 if (isUnique(name)) { 189 return name; 190 } 191 guess++; 192 } 193 } 194 isUnique(String name)195 private boolean isUnique(String name) { 196 for (T identifier : mBiometrics) { 197 if (identifier.getName().equals(name)) { 198 return false; 199 } 200 } 201 return true; 202 } 203 getFileForUser(int userId, @NonNull String fileName)204 private File getFileForUser(int userId, @NonNull String fileName) { 205 return new File(Environment.getUserSystemDirectory(userId), fileName); 206 } 207 scheduleWriteStateLocked()208 private void scheduleWriteStateLocked() { 209 AsyncTask.execute(mWriteStateRunnable); 210 } 211 212 @GuardedBy("this") readStateSyncLocked()213 private void readStateSyncLocked() { 214 FileInputStream in; 215 if (!mFile.exists()) { 216 return; 217 } 218 try { 219 in = new FileInputStream(mFile); 220 } catch (FileNotFoundException fnfe) { 221 Slog.i(TAG, "No fingerprint state"); 222 return; 223 } 224 try { 225 TypedXmlPullParser parser = Xml.resolvePullParser(in); 226 parseStateLocked(parser); 227 228 } catch (XmlPullParserException | IOException e) { 229 throw new IllegalStateException("Failed parsing settings file: " 230 + mFile , e); 231 } finally { 232 IoUtils.closeQuietly(in); 233 } 234 } 235 236 @GuardedBy("this") parseStateLocked(TypedXmlPullParser parser)237 private void parseStateLocked(TypedXmlPullParser parser) 238 throws IOException, XmlPullParserException { 239 final int outerDepth = parser.getDepth(); 240 int type; 241 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 242 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 243 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 244 continue; 245 } 246 247 String tagName = parser.getName(); 248 if (tagName.equals(getBiometricsTag())) { 249 parseBiometricsLocked(parser); 250 } else if (tagName.equals(TAG_INVALIDATION)) { 251 mInvalidationInProgress = parser.getAttributeBoolean(null, ATTR_INVALIDATION); 252 } 253 } 254 } 255 256 } 257