1 /* 2 * Copyright (C) 2023 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.healthconnect.permission; 18 19 import android.util.ArrayMap; 20 import android.util.AtomicFile; 21 import android.util.Log; 22 import android.util.Xml; 23 24 import libcore.io.IoUtils; 25 26 import org.xmlpull.v1.XmlPullParser; 27 import org.xmlpull.v1.XmlPullParserException; 28 import org.xmlpull.v1.XmlSerializer; 29 30 import java.io.File; 31 import java.io.FileInputStream; 32 import java.io.FileNotFoundException; 33 import java.io.FileOutputStream; 34 import java.io.IOException; 35 import java.nio.charset.StandardCharsets; 36 import java.time.Instant; 37 import java.util.Map; 38 39 /** 40 * Helper class for serialisation / parsing grant time xml file. 41 * 42 * @hide 43 */ 44 public final class GrantTimeXmlHelper { 45 private static final String TAG = "GrantTimeSerializer"; 46 private static final String TAG_FIRST_GRANT_TIMES = "first-grant-times"; 47 private static final String TAG_PACKAGE = "package"; 48 private static final String TAG_SHARED_USER = "shared-user"; 49 50 private static final String ATTRIBUTE_NAME = "name"; 51 private static final String ATTRIBUTE_FIRST_GRANT_TIME = "first-grant-time"; 52 private static final String ATTRIBUTE_VERSION = "version"; 53 54 /** 55 * Serializes the grant times into the passed file. 56 * 57 * @param userGrantTimeState the grant times to be serialized. 58 * @param file the file into which the serialized data should be written. 59 */ serializeGrantTimes(File file, UserGrantTimeState userGrantTimeState)60 public static void serializeGrantTimes(File file, UserGrantTimeState userGrantTimeState) { 61 AtomicFile atomicFile = new AtomicFile(file); 62 FileOutputStream outputStream = null; 63 try { 64 outputStream = atomicFile.startWrite(); 65 66 XmlSerializer serializer = Xml.newSerializer(); 67 serializer.setOutput(outputStream, StandardCharsets.UTF_8.name()); 68 serializer.startDocument(/* encoding= */ null, /* standalone= */ true); 69 GrantTimeXmlHelper.writeGrantTimes(serializer, userGrantTimeState); 70 71 serializer.endDocument(); 72 atomicFile.finishWrite(outputStream); 73 } catch (Exception e) { 74 Log.wtf(TAG, "Failed to write, restoring backup: " + file, e); 75 atomicFile.failWrite(outputStream); 76 } finally { 77 IoUtils.closeQuietly(outputStream); 78 } 79 } 80 81 /** 82 * Parses the passed grant time file to return the grant times. 83 * 84 * @param file the file from which the data should be parsed. 85 * @return the grant times. 86 */ 87 @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression parseGrantTime(File file)88 public static UserGrantTimeState parseGrantTime(File file) { 89 try (FileInputStream inputStream = new AtomicFile(file).openRead()) { 90 XmlPullParser parser = Xml.newPullParser(); 91 parser.setInput(inputStream, /* inputEncoding= */ null); 92 return parseXml(parser); 93 } catch (FileNotFoundException e) { 94 Log.w(TAG, file.getPath() + " not found"); 95 return null; 96 } catch (XmlPullParserException | IOException e) { 97 throw new IllegalStateException("Failed to read " + file, e); 98 } 99 } 100 parseXml(XmlPullParser parser)101 private static UserGrantTimeState parseXml(XmlPullParser parser) 102 throws IOException, XmlPullParserException { 103 int targetDepth = parser.getDepth() + 1; 104 int type = parser.next(); 105 106 // Scan the xml until find the grant time tag at the target depth. 107 while (type != XmlPullParser.END_DOCUMENT 108 && (parser.getDepth() >= targetDepth || type != XmlPullParser.END_TAG)) { 109 if (parser.getDepth() > targetDepth || type != XmlPullParser.START_TAG) { 110 type = parser.next(); 111 continue; 112 } 113 114 if (parser.getName().equals(TAG_FIRST_GRANT_TIMES)) { 115 return parseFirstGrantTimes(parser); 116 } 117 118 type = parser.next(); 119 } 120 throw new IllegalStateException( 121 "Missing <" + TAG_FIRST_GRANT_TIMES + "> in provided file."); 122 } 123 writeGrantTimes( XmlSerializer serializer, UserGrantTimeState userGrantTimeState)124 private static void writeGrantTimes( 125 XmlSerializer serializer, UserGrantTimeState userGrantTimeState) throws IOException { 126 serializer.startTag(/* namespace= */ null, TAG_FIRST_GRANT_TIMES); 127 serializer.attribute( 128 /* namespace= */ null, 129 ATTRIBUTE_VERSION, 130 Integer.toString(userGrantTimeState.getVersion())); 131 132 for (Map.Entry<String, Instant> entry : 133 userGrantTimeState.getPackageGrantTimes().entrySet()) { 134 String packageName = entry.getKey(); 135 Instant grantTime = entry.getValue(); 136 137 serializer.startTag(/* namespace= */ null, TAG_PACKAGE); 138 serializer.attribute(/* namespace= */ null, ATTRIBUTE_NAME, packageName); 139 serializer.attribute( 140 /* namespace= */ null, ATTRIBUTE_FIRST_GRANT_TIME, grantTime.toString()); 141 serializer.endTag(/* namespace= */ null, TAG_PACKAGE); 142 } 143 144 for (Map.Entry<String, Instant> entry : 145 userGrantTimeState.getSharedUserGrantTimes().entrySet()) { 146 String sharedUserName = entry.getKey(); 147 Instant grantTime = entry.getValue(); 148 149 serializer.startTag(/* namespace= */ null, TAG_SHARED_USER); 150 serializer.attribute(/* namespace= */ null, ATTRIBUTE_NAME, sharedUserName); 151 serializer.attribute( 152 /* namespace= */ null, ATTRIBUTE_FIRST_GRANT_TIME, grantTime.toString()); 153 serializer.endTag(/* namespace= */ null, TAG_SHARED_USER); 154 } 155 156 serializer.endTag(/* namespace= */ null, TAG_FIRST_GRANT_TIMES); 157 } 158 parseFirstGrantTimes(XmlPullParser parser)159 private static UserGrantTimeState parseFirstGrantTimes(XmlPullParser parser) 160 throws IOException, XmlPullParserException { 161 String versionValue = parser.getAttributeValue(/* namespace= */ null, ATTRIBUTE_VERSION); 162 int version = 163 versionValue != null 164 ? Integer.parseInt(versionValue) 165 : UserGrantTimeState.NO_VERSION; 166 Map<String, Instant> packagePermissions = new ArrayMap<>(); 167 Map<String, Instant> sharedUserPermissions = new ArrayMap<>(); 168 169 int targetDepth = parser.getDepth() + 1; 170 int type = parser.next(); 171 // Scan the xml until find the needed tags at the target depth. 172 while (type != XmlPullParser.END_DOCUMENT 173 && (parser.getDepth() >= targetDepth || type != XmlPullParser.END_TAG)) { 174 if (parser.getDepth() > targetDepth || type != XmlPullParser.START_TAG) { 175 type = parser.next(); 176 continue; 177 } 178 switch (parser.getName()) { 179 case TAG_PACKAGE: 180 { 181 String packageName = 182 parser.getAttributeValue(/* namespace= */ null, ATTRIBUTE_NAME); 183 Instant firstGrantTime = 184 Instant.parse( 185 parser.getAttributeValue( 186 /* namespace= */ null, ATTRIBUTE_FIRST_GRANT_TIME)); 187 packagePermissions.put(packageName, firstGrantTime); 188 break; 189 } 190 case TAG_SHARED_USER: 191 { 192 String sharedUserName = 193 parser.getAttributeValue(/* namespace= */ null, ATTRIBUTE_NAME); 194 Instant firstGrantTime = 195 Instant.parse( 196 parser.getAttributeValue( 197 /* namespace= */ null, ATTRIBUTE_FIRST_GRANT_TIME)); 198 sharedUserPermissions.put(sharedUserName, firstGrantTime); 199 break; 200 } 201 default: 202 { 203 Log.w(TAG, "Tag " + parser.getName() + " is not parsed"); 204 } 205 } 206 type = parser.next(); 207 } 208 209 return new UserGrantTimeState(packagePermissions, sharedUserPermissions, version); 210 } 211 } 212