• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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