• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.wm;
18 
19 import static android.app.UiModeManager.MODE_NIGHT_AUTO;
20 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
21 
22 import android.annotation.NonNull;
23 import android.os.Environment;
24 import android.util.AtomicFile;
25 import android.util.Slog;
26 import android.util.SparseArray;
27 import android.util.TypedXmlPullParser;
28 import android.util.TypedXmlSerializer;
29 import android.util.Xml;
30 
31 import com.android.internal.annotations.GuardedBy;
32 import com.android.internal.util.XmlUtils;
33 
34 import org.xmlpull.v1.XmlPullParser;
35 import org.xmlpull.v1.XmlPullParserException;
36 
37 import java.io.ByteArrayOutputStream;
38 import java.io.File;
39 import java.io.FileInputStream;
40 import java.io.FileNotFoundException;
41 import java.io.FileOutputStream;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.util.HashMap;
45 
46 /**
47  * Persist configuration for each package, only persist the change if some on attributes are
48  * different from the global configuration. This class only applies to packages with Activities.
49  */
50 public class PackageConfigPersister {
51     private static final String TAG = PackageConfigPersister.class.getSimpleName();
52     private static final boolean DEBUG = false;
53 
54     private static final String TAG_CONFIG = "config";
55     private static final String ATTR_PACKAGE_NAME = "package_name";
56     private static final String ATTR_NIGHT_MODE = "night_mode";
57 
58     private static final String PACKAGE_DIRNAME = "package_configs";
59     private static final String SUFFIX_FILE_NAME = "_config.xml";
60 
61     private final PersisterQueue mPersisterQueue;
62     private final Object mLock = new Object();
63 
64     @GuardedBy("mLock")
65     private final SparseArray<HashMap<String, PackageConfigRecord>> mPendingWrite =
66             new SparseArray<>();
67     @GuardedBy("mLock")
68     private final SparseArray<HashMap<String, PackageConfigRecord>> mModified =
69             new SparseArray<>();
70 
getUserConfigsDir(int userId)71     private static File getUserConfigsDir(int userId) {
72         return new File(Environment.getDataSystemCeDirectory(userId), PACKAGE_DIRNAME);
73     }
74 
PackageConfigPersister(PersisterQueue queue)75     PackageConfigPersister(PersisterQueue queue) {
76         mPersisterQueue = queue;
77     }
78 
79     @GuardedBy("mLock")
loadUserPackages(int userId)80     void loadUserPackages(int userId) {
81         synchronized (mLock) {
82             final File userConfigsDir = getUserConfigsDir(userId);
83             final File[] configFiles = userConfigsDir.listFiles();
84             if (configFiles == null) {
85                 Slog.v(TAG, "loadPackages: empty list files from " + userConfigsDir);
86                 return;
87             }
88 
89             for (int fileIndex = 0; fileIndex < configFiles.length; ++fileIndex) {
90                 final File configFile = configFiles[fileIndex];
91                 if (DEBUG) {
92                     Slog.d(TAG, "loadPackages: userId=" + userId
93                             + ", configFile=" + configFile.getName());
94                 }
95                 if (!configFile.getName().endsWith(SUFFIX_FILE_NAME)) {
96                     continue;
97                 }
98 
99                 try (InputStream is = new FileInputStream(configFile)) {
100                     final TypedXmlPullParser in = Xml.resolvePullParser(is);
101                     int event;
102                     String packageName = null;
103                     int nightMode = MODE_NIGHT_AUTO;
104                     while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
105                             && event != XmlPullParser.END_TAG) {
106                         final String name = in.getName();
107                         if (event == XmlPullParser.START_TAG) {
108                             if (DEBUG) {
109                                 Slog.d(TAG, "loadPackages: START_TAG name=" + name);
110                             }
111                             if (TAG_CONFIG.equals(name)) {
112                                 for (int attIdx = in.getAttributeCount() - 1; attIdx >= 0;
113                                         --attIdx) {
114                                     final String attrName = in.getAttributeName(attIdx);
115                                     final String attrValue = in.getAttributeValue(attIdx);
116                                     switch (attrName) {
117                                         case ATTR_PACKAGE_NAME:
118                                             packageName = attrValue;
119                                             break;
120                                         case ATTR_NIGHT_MODE:
121                                             nightMode = Integer.parseInt(attrValue);
122                                             break;
123                                     }
124                                 }
125                             }
126                         }
127                         XmlUtils.skipCurrentTag(in);
128                     }
129                     if (packageName != null) {
130                         final PackageConfigRecord initRecord =
131                                 findRecordOrCreate(mModified, packageName, userId);
132                         initRecord.mNightMode = nightMode;
133                         if (DEBUG) {
134                             Slog.d(TAG, "loadPackages: load one package " + initRecord);
135                         }
136                     }
137                 } catch (FileNotFoundException e) {
138                     e.printStackTrace();
139                 } catch (IOException e) {
140                     e.printStackTrace();
141                 } catch (XmlPullParserException e) {
142                     e.printStackTrace();
143                 }
144             }
145         }
146     }
147 
148     @GuardedBy("mLock")
updateConfigIfNeeded(@onNull ConfigurationContainer container, int userId, String packageName)149     void updateConfigIfNeeded(@NonNull ConfigurationContainer container, int userId,
150             String packageName) {
151         synchronized (mLock) {
152             final PackageConfigRecord modifiedRecord = findRecord(mModified, packageName, userId);
153             if (DEBUG) {
154                 Slog.d(TAG,
155                         "updateConfigIfNeeded record " + container + " find? " + modifiedRecord);
156             }
157             if (modifiedRecord != null) {
158                 container.setOverrideNightMode(modifiedRecord.mNightMode);
159             }
160         }
161     }
162 
163     @GuardedBy("mLock")
updateFromImpl(String packageName, int userId, ActivityTaskManagerService.PackageConfigurationUpdaterImpl impl)164     void updateFromImpl(String packageName, int userId,
165             ActivityTaskManagerService.PackageConfigurationUpdaterImpl impl) {
166         synchronized (mLock) {
167             PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId);
168             record.mNightMode = impl.getNightMode();
169 
170             if (record.isResetNightMode()) {
171                 removePackage(record.mName, record.mUserId);
172             } else {
173                 final PackageConfigRecord pendingRecord =
174                         findRecord(mPendingWrite, record.mName, record.mUserId);
175                 final PackageConfigRecord writeRecord;
176                 if (pendingRecord == null) {
177                     writeRecord = findRecordOrCreate(mPendingWrite, record.mName,
178                             record.mUserId);
179                 } else {
180                     writeRecord = pendingRecord;
181                 }
182                 if (writeRecord.mNightMode == record.mNightMode) {
183                     return;
184                 }
185                 writeRecord.mNightMode = record.mNightMode;
186                 if (DEBUG) {
187                     Slog.d(TAG, "PackageConfigUpdater save config " + writeRecord);
188                 }
189                 mPersisterQueue.addItem(new WriteProcessItem(writeRecord), false /* flush */);
190             }
191         }
192     }
193 
194     @GuardedBy("mLock")
removeUser(int userId)195     void removeUser(int userId) {
196         synchronized (mLock) {
197             final HashMap<String, PackageConfigRecord> modifyRecords = mModified.get(userId);
198             final HashMap<String, PackageConfigRecord> writeRecords = mPendingWrite.get(userId);
199             if ((modifyRecords == null || modifyRecords.size() == 0)
200                     && (writeRecords == null || writeRecords.size() == 0)) {
201                 return;
202             }
203             final HashMap<String, PackageConfigRecord> tempList = new HashMap<>(modifyRecords);
204             tempList.forEach((name, record) -> {
205                 removePackage(record.mName, record.mUserId);
206             });
207         }
208     }
209 
210     @GuardedBy("mLock")
onPackageUninstall(String packageName)211     void onPackageUninstall(String packageName) {
212         synchronized (mLock) {
213             for (int i = mModified.size() - 1; i > 0; i--) {
214                 final int userId = mModified.keyAt(i);
215                 removePackage(packageName, userId);
216             }
217         }
218     }
219 
removePackage(String packageName, int userId)220     private void removePackage(String packageName, int userId) {
221         if (DEBUG) {
222             Slog.d(TAG, "removePackage packageName :" + packageName + " userId " + userId);
223         }
224         final PackageConfigRecord record = findRecord(mPendingWrite, packageName, userId);
225         if (record != null) {
226             removeRecord(mPendingWrite, record);
227             mPersisterQueue.removeItems(item ->
228                             item.mRecord.mName == record.mName
229                                     && item.mRecord.mUserId == record.mUserId,
230                     WriteProcessItem.class);
231         }
232 
233         final PackageConfigRecord modifyRecord = findRecord(mModified, packageName, userId);
234         if (modifyRecord != null) {
235             removeRecord(mModified, modifyRecord);
236             mPersisterQueue.addItem(new DeletePackageItem(userId, packageName),
237                     false /* flush */);
238         }
239     }
240 
241     // store a changed data so we don't need to get the process
242     static class PackageConfigRecord {
243         final String mName;
244         final int mUserId;
245         int mNightMode;
246 
PackageConfigRecord(String name, int userId)247         PackageConfigRecord(String name, int userId) {
248             mName = name;
249             mUserId = userId;
250         }
251 
isResetNightMode()252         boolean isResetNightMode() {
253             return mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM;
254         }
255 
256         @Override
toString()257         public String toString() {
258             return "PackageConfigRecord package name: " + mName + " userId " + mUserId
259                     + " nightMode " + mNightMode;
260         }
261     }
262 
findRecordOrCreate( SparseArray<HashMap<String, PackageConfigRecord>> list, String name, int userId)263     private PackageConfigRecord findRecordOrCreate(
264             SparseArray<HashMap<String, PackageConfigRecord>> list, String name, int userId) {
265         HashMap<String, PackageConfigRecord> records = list.get(userId);
266         if (records == null) {
267             records = new HashMap<>();
268             list.put(userId, records);
269         }
270         PackageConfigRecord record = records.get(name);
271         if (record != null) {
272             return record;
273         }
274         record = new PackageConfigRecord(name, userId);
275         records.put(name, record);
276         return record;
277     }
278 
findRecord(SparseArray<HashMap<String, PackageConfigRecord>> list, String name, int userId)279     private PackageConfigRecord findRecord(SparseArray<HashMap<String, PackageConfigRecord>> list,
280             String name, int userId) {
281         HashMap<String, PackageConfigRecord> packages = list.get(userId);
282         if (packages == null) {
283             return null;
284         }
285         return packages.get(name);
286     }
287 
removeRecord(SparseArray<HashMap<String, PackageConfigRecord>> list, PackageConfigRecord record)288     private void removeRecord(SparseArray<HashMap<String, PackageConfigRecord>> list,
289             PackageConfigRecord record) {
290         final HashMap<String, PackageConfigRecord> processes = list.get(record.mUserId);
291         if (processes != null) {
292             processes.remove(record.mName);
293         }
294     }
295 
296     private static class DeletePackageItem implements PersisterQueue.WriteQueueItem {
297         final int mUserId;
298         final String mPackageName;
299 
DeletePackageItem(int userId, String packageName)300         DeletePackageItem(int userId, String packageName) {
301             mUserId = userId;
302             mPackageName = packageName;
303         }
304 
305         @Override
process()306         public void process() {
307             File userConfigsDir = getUserConfigsDir(mUserId);
308             if (!userConfigsDir.isDirectory()) {
309                 return;
310             }
311             final AtomicFile atomicFile = new AtomicFile(new File(userConfigsDir,
312                     mPackageName + SUFFIX_FILE_NAME));
313             if (atomicFile.exists()) {
314                 atomicFile.delete();
315             }
316         }
317     }
318 
319     private class WriteProcessItem implements PersisterQueue.WriteQueueItem {
320         final PackageConfigRecord mRecord;
321 
WriteProcessItem(PackageConfigRecord record)322         WriteProcessItem(PackageConfigRecord record) {
323             mRecord = record;
324         }
325 
326         @Override
process()327         public void process() {
328             // Write out one user.
329             byte[] data = null;
330             synchronized (mLock) {
331                 try {
332                     data = saveToXml();
333                 } catch (Exception e) {
334                 }
335                 removeRecord(mPendingWrite, mRecord);
336             }
337             if (data != null) {
338                 // Write out xml file while not holding mService lock.
339                 FileOutputStream file = null;
340                 AtomicFile atomicFile = null;
341                 try {
342                     File userConfigsDir = getUserConfigsDir(mRecord.mUserId);
343                     if (!userConfigsDir.isDirectory() && !userConfigsDir.mkdirs()) {
344                         Slog.e(TAG, "Failure creating tasks directory for user " + mRecord.mUserId
345                                 + ": " + userConfigsDir);
346                         return;
347                     }
348                     atomicFile = new AtomicFile(new File(userConfigsDir,
349                             mRecord.mName + SUFFIX_FILE_NAME));
350                     file = atomicFile.startWrite();
351                     file.write(data);
352                     atomicFile.finishWrite(file);
353                 } catch (IOException e) {
354                     if (file != null) {
355                         atomicFile.failWrite(file);
356                     }
357                     Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " + e);
358                 }
359             }
360         }
361 
saveToXml()362         private byte[] saveToXml() throws IOException {
363             final ByteArrayOutputStream os = new ByteArrayOutputStream();
364             final TypedXmlSerializer xmlSerializer = Xml.resolveSerializer(os);
365 
366             xmlSerializer.startDocument(null, true);
367             if (DEBUG) {
368                 Slog.d(TAG, "Writing package configuration=" + mRecord);
369             }
370             xmlSerializer.startTag(null, TAG_CONFIG);
371             xmlSerializer.attribute(null, ATTR_PACKAGE_NAME, mRecord.mName);
372             xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode);
373             xmlSerializer.endTag(null, TAG_CONFIG);
374             xmlSerializer.endDocument();
375             xmlSerializer.flush();
376 
377             return os.toByteArray();
378         }
379     }
380 }
381