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