blob: 39c6e7552e3c7926a16e746d002627c3fe9a2056 [file] [log] [blame]
Mathew Inwoode188acc2019-06-20 15:13:33 +01001/*
2 * Copyright (C) 2019 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
17package com.android.server.compat;
18
Andrei Onea18041f782019-10-01 14:34:56 +010019import android.compat.Compatibility.ChangeConfig;
Mathew Inwoode188acc2019-06-20 15:13:33 +010020import android.content.pm.ApplicationInfo;
atrost7e6060f2019-07-24 14:12:48 +010021import android.os.Environment;
Mathew Inwoode188acc2019-06-20 15:13:33 +010022import android.text.TextUtils;
23import android.util.LongArray;
24import android.util.LongSparseArray;
atrost7e6060f2019-07-24 14:12:48 +010025import android.util.Slog;
Mathew Inwoode188acc2019-06-20 15:13:33 +010026
27import com.android.internal.annotations.GuardedBy;
28import com.android.internal.annotations.VisibleForTesting;
Andrei Oneaea997f22019-09-12 18:08:59 +010029import com.android.internal.compat.CompatibilityChangeConfig;
Andrei Onea18041f782019-10-01 14:34:56 +010030import com.android.internal.compat.CompatibilityChangeInfo;
atrost7e6060f2019-07-24 14:12:48 +010031import com.android.server.compat.config.Change;
32import com.android.server.compat.config.XmlParser;
Mathew Inwoode188acc2019-06-20 15:13:33 +010033
atrost7e6060f2019-07-24 14:12:48 +010034import org.xmlpull.v1.XmlPullParserException;
35
36import java.io.BufferedInputStream;
37import java.io.File;
38import java.io.FileInputStream;
39import java.io.IOException;
40import java.io.InputStream;
Andrei Oneac61bf0c2019-07-10 16:43:09 +010041import java.io.PrintWriter;
Andrei Onea18041f782019-10-01 14:34:56 +010042import java.util.HashSet;
43import java.util.Set;
atrost7e6060f2019-07-24 14:12:48 +010044
45import javax.xml.datatype.DatatypeConfigurationException;
atrostf69bbe12019-11-06 16:00:38 +000046
Mathew Inwoode188acc2019-06-20 15:13:33 +010047/**
48 * This class maintains state relating to platform compatibility changes.
49 *
50 * <p>It stores the default configuration for each change, and any per-package overrides that have
51 * been configured.
52 */
atrostf69bbe12019-11-06 16:00:38 +000053final class CompatConfig {
Mathew Inwoode188acc2019-06-20 15:13:33 +010054
atrost7e6060f2019-07-24 14:12:48 +010055 private static final String TAG = "CompatConfig";
atrost7e6060f2019-07-24 14:12:48 +010056
57 private static final CompatConfig sInstance = new CompatConfig().initConfigFromLib(
58 Environment.buildPath(
atrost7aa64a72019-08-29 14:22:13 +010059 Environment.getRootDirectory(), "etc", "compatconfig"));
Mathew Inwoode188acc2019-06-20 15:13:33 +010060
61 @GuardedBy("mChanges")
62 private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>();
63
64 @VisibleForTesting
atrostf69bbe12019-11-06 16:00:38 +000065 CompatConfig() {
Mathew Inwoode188acc2019-06-20 15:13:33 +010066 }
67
68 /**
69 * @return The static instance of this class to be used within the system server.
70 */
atrostf69bbe12019-11-06 16:00:38 +000071 static CompatConfig get() {
Mathew Inwoode188acc2019-06-20 15:13:33 +010072 return sInstance;
73 }
74
75 /**
76 * Add a change. This is intended to be used by code that reads change config from the
77 * filesystem. This should be done at system startup time.
78 *
79 * @param change The change to add. Any change with the same ID will be overwritten.
80 */
atrostf69bbe12019-11-06 16:00:38 +000081 void addChange(CompatChange change) {
Mathew Inwoode188acc2019-06-20 15:13:33 +010082 synchronized (mChanges) {
83 mChanges.put(change.getId(), change);
84 }
85 }
86
87 /**
88 * Retrieves the set of disabled changes for a given app. Any change ID not in the returned
89 * array is by default enabled for the app.
90 *
91 * @param app The app in question
92 * @return A sorted long array of change IDs. We use a primitive array to minimize memory
atrostf69bbe12019-11-06 16:00:38 +000093 * footprint: Every app process will store this array statically so we aim to reduce
94 * overhead as much as possible.
Mathew Inwoode188acc2019-06-20 15:13:33 +010095 */
atrostf69bbe12019-11-06 16:00:38 +000096 long[] getDisabledChanges(ApplicationInfo app) {
Mathew Inwoode188acc2019-06-20 15:13:33 +010097 LongArray disabled = new LongArray();
98 synchronized (mChanges) {
99 for (int i = 0; i < mChanges.size(); ++i) {
100 CompatChange c = mChanges.valueAt(i);
101 if (!c.isEnabled(app)) {
102 disabled.add(c.getId());
103 }
104 }
105 }
106 // Note: we don't need to explicitly sort the array, as the behaviour of LongSparseArray
107 // (mChanges) ensures it's already sorted.
108 return disabled.toArray();
109 }
110
111 /**
112 * Look up a change ID by name.
113 *
114 * @param name Name of the change to look up
115 * @return The change ID, or {@code -1} if no change with that name exists.
116 */
atrostf69bbe12019-11-06 16:00:38 +0000117 long lookupChangeId(String name) {
Mathew Inwoode188acc2019-06-20 15:13:33 +0100118 synchronized (mChanges) {
119 for (int i = 0; i < mChanges.size(); ++i) {
120 if (TextUtils.equals(mChanges.valueAt(i).getName(), name)) {
121 return mChanges.keyAt(i);
122 }
123 }
124 }
125 return -1;
126 }
127
128 /**
129 * Find if a given change is enabled for a given application.
130 *
131 * @param changeId The ID of the change in question
atrostf69bbe12019-11-06 16:00:38 +0000132 * @param app App to check for
Mathew Inwoode188acc2019-06-20 15:13:33 +0100133 * @return {@code true} if the change is enabled for this app. Also returns {@code true} if the
atrostf69bbe12019-11-06 16:00:38 +0000134 * change ID is not known, as unknown changes are enabled by default.
Mathew Inwoode188acc2019-06-20 15:13:33 +0100135 */
atrostf69bbe12019-11-06 16:00:38 +0000136 boolean isChangeEnabled(long changeId, ApplicationInfo app) {
Mathew Inwoode188acc2019-06-20 15:13:33 +0100137 synchronized (mChanges) {
138 CompatChange c = mChanges.get(changeId);
139 if (c == null) {
140 // we know nothing about this change: default behaviour is enabled.
141 return true;
142 }
143 return c.isEnabled(app);
144 }
145 }
146
147 /**
148 * Overrides the enabled state for a given change and app. This method is intended to be used
149 * *only* for debugging purposes, ultimately invoked either by an adb command, or from some
150 * developer settings UI.
151 *
152 * <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
153 *
atrostf69bbe12019-11-06 16:00:38 +0000154 * @param changeId The ID of the change to be overridden. Note, this call will succeed even
155 * if
156 * this change is not known; it will only have any effect if any code in the
157 * platform is gated on the ID given.
Mathew Inwoode188acc2019-06-20 15:13:33 +0100158 * @param packageName The app package name to override the change for.
atrostf69bbe12019-11-06 16:00:38 +0000159 * @param enabled If the change should be enabled or disabled.
Andrei Oneac1ba0192019-06-28 18:42:22 +0100160 * @return {@code true} if the change existed before adding the override.
Mathew Inwoode188acc2019-06-20 15:13:33 +0100161 */
atrostf69bbe12019-11-06 16:00:38 +0000162 boolean addOverride(long changeId, String packageName, boolean enabled) {
Andrei Oneac1ba0192019-06-28 18:42:22 +0100163 boolean alreadyKnown = true;
Mathew Inwoode188acc2019-06-20 15:13:33 +0100164 synchronized (mChanges) {
165 CompatChange c = mChanges.get(changeId);
166 if (c == null) {
Andrei Oneac1ba0192019-06-28 18:42:22 +0100167 alreadyKnown = false;
Mathew Inwoode188acc2019-06-20 15:13:33 +0100168 c = new CompatChange(changeId);
169 addChange(c);
170 }
171 c.addPackageOverride(packageName, enabled);
172 }
Andrei Oneac1ba0192019-06-28 18:42:22 +0100173 return alreadyKnown;
Mathew Inwoode188acc2019-06-20 15:13:33 +0100174 }
175
176 /**
atrostf69bbe12019-11-06 16:00:38 +0000177 * Check whether the change is known to the compat config.
178 *
179 * @return {@code true} if the change is known.
180 */
181 boolean isKnownChangeId(long changeId) {
182 synchronized (mChanges) {
183 CompatChange c = mChanges.get(changeId);
184 return c != null;
185 }
186 }
187
188 /**
Mathew Inwoode188acc2019-06-20 15:13:33 +0100189 * Removes an override previously added via {@link #addOverride(long, String, boolean)}. This
190 * restores the default behaviour for the given change and app, once any app processes have been
191 * restarted.
192 *
atrostf69bbe12019-11-06 16:00:38 +0000193 * @param changeId The ID of the change that was overridden.
Mathew Inwoode188acc2019-06-20 15:13:33 +0100194 * @param packageName The app package name that was overridden.
Andrei Oneac1ba0192019-06-28 18:42:22 +0100195 * @return {@code true} if an override existed;
Mathew Inwoode188acc2019-06-20 15:13:33 +0100196 */
atrostf69bbe12019-11-06 16:00:38 +0000197 boolean removeOverride(long changeId, String packageName) {
Andrei Oneac1ba0192019-06-28 18:42:22 +0100198 boolean overrideExists = false;
Mathew Inwoode188acc2019-06-20 15:13:33 +0100199 synchronized (mChanges) {
200 CompatChange c = mChanges.get(changeId);
201 if (c != null) {
Andrei Oneac1ba0192019-06-28 18:42:22 +0100202 overrideExists = true;
Mathew Inwoode188acc2019-06-20 15:13:33 +0100203 c.removePackageOverride(packageName);
204 }
205 }
Andrei Oneac1ba0192019-06-28 18:42:22 +0100206 return overrideExists;
Mathew Inwoode188acc2019-06-20 15:13:33 +0100207 }
atrostf69bbe12019-11-06 16:00:38 +0000208
Andrei Oneaea997f22019-09-12 18:08:59 +0100209 /**
210 * Overrides the enabled state for a given change and app. This method is intended to be used
211 * *only* for debugging purposes.
212 *
213 * <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
214 *
atrostf69bbe12019-11-06 16:00:38 +0000215 * @param overrides list of overrides to default changes config.
Andrei Oneaea997f22019-09-12 18:08:59 +0100216 * @param packageName app for which the overrides will be applied.
217 */
atrostf69bbe12019-11-06 16:00:38 +0000218 void addOverrides(CompatibilityChangeConfig overrides, String packageName) {
Andrei Oneaea997f22019-09-12 18:08:59 +0100219 synchronized (mChanges) {
atrostf69bbe12019-11-06 16:00:38 +0000220 for (Long changeId : overrides.enabledChanges()) {
Andrei Oneaea997f22019-09-12 18:08:59 +0100221 addOverride(changeId, packageName, true);
222 }
atrostf69bbe12019-11-06 16:00:38 +0000223 for (Long changeId : overrides.disabledChanges()) {
Andrei Oneaea997f22019-09-12 18:08:59 +0100224 addOverride(changeId, packageName, false);
225 }
226 }
227 }
228
229 /**
230 * Removes all overrides previously added via {@link #addOverride(long, String, boolean)} or
atrost0be238b2019-11-11 16:54:04 +0000231 * {@link #addOverrides(CompatibilityChangeConfig, String)} for a certain package.
Andrei Oneaea997f22019-09-12 18:08:59 +0100232 *
233 * <p>This restores the default behaviour for the given change and app, once any app
234 * processes have been restarted.
235 *
236 * @param packageName The package for which the overrides should be purged.
237 */
atrostf69bbe12019-11-06 16:00:38 +0000238 void removePackageOverrides(String packageName) {
Andrei Oneaea997f22019-09-12 18:08:59 +0100239 synchronized (mChanges) {
240 for (int i = 0; i < mChanges.size(); ++i) {
241 mChanges.valueAt(i).removePackageOverride(packageName);
242 }
243 }
244 }
Mathew Inwoode188acc2019-06-20 15:13:33 +0100245
atrost0be238b2019-11-11 16:54:04 +0000246 boolean registerListener(long changeId, CompatChange.ChangeListener listener) {
247 boolean alreadyKnown = true;
248 synchronized (mChanges) {
249 CompatChange c = mChanges.get(changeId);
250 if (c == null) {
251 alreadyKnown = false;
252 c = new CompatChange(changeId);
253 addChange(c);
254 }
255 c.registerListener(listener);
256 }
257 return alreadyKnown;
258 }
259
260 @VisibleForTesting
261 void clearChanges() {
262 synchronized (mChanges) {
263 mChanges.clear();
264 }
265 }
266
Andrei Oneac61bf0c2019-07-10 16:43:09 +0100267 /**
atrostf69bbe12019-11-06 16:00:38 +0000268 * Dumps the current list of compatibility config information.
269 *
270 * @param pw The {@link PrintWriter} instance to which the information will be dumped.
271 */
272 void dumpConfig(PrintWriter pw) {
Andrei Oneac61bf0c2019-07-10 16:43:09 +0100273 synchronized (mChanges) {
274 if (mChanges.size() == 0) {
275 pw.println("No compat overrides.");
276 return;
277 }
278 for (int i = 0; i < mChanges.size(); ++i) {
279 CompatChange c = mChanges.valueAt(i);
280 pw.println(c.toString());
281 }
282 }
283 }
284
Andrei Onea18041f782019-10-01 14:34:56 +0100285 /**
286 * Get the config for a given app.
287 *
288 * @param applicationInfo the {@link ApplicationInfo} for which the info should be dumped.
289 * @return A {@link CompatibilityChangeConfig} which contains the compat config info for the
atrostf69bbe12019-11-06 16:00:38 +0000290 * given app.
Andrei Onea18041f782019-10-01 14:34:56 +0100291 */
292
atrostf69bbe12019-11-06 16:00:38 +0000293 CompatibilityChangeConfig getAppConfig(ApplicationInfo applicationInfo) {
Andrei Onea18041f782019-10-01 14:34:56 +0100294 Set<Long> enabled = new HashSet<>();
295 Set<Long> disabled = new HashSet<>();
296 synchronized (mChanges) {
297 for (int i = 0; i < mChanges.size(); ++i) {
298 CompatChange c = mChanges.valueAt(i);
299 if (c.isEnabled(applicationInfo)) {
300 enabled.add(c.getId());
301 } else {
302 disabled.add(c.getId());
303 }
304 }
305 }
306 return new CompatibilityChangeConfig(new ChangeConfig(enabled, disabled));
307 }
308
309 /**
310 * Dumps all the compatibility change information.
311 *
312 * @return An array of {@link CompatibilityChangeInfo} with the current changes.
313 */
atrostf69bbe12019-11-06 16:00:38 +0000314 CompatibilityChangeInfo[] dumpChanges() {
Andrei Onea18041f782019-10-01 14:34:56 +0100315 synchronized (mChanges) {
316 CompatibilityChangeInfo[] changeInfos = new CompatibilityChangeInfo[mChanges.size()];
317 for (int i = 0; i < mChanges.size(); ++i) {
318 CompatChange change = mChanges.valueAt(i);
319 changeInfos[i] = new CompatibilityChangeInfo(change.getId(),
atrostf69bbe12019-11-06 16:00:38 +0000320 change.getName(),
321 change.getEnableAfterTargetSdk(),
322 change.getDisabled());
Andrei Onea18041f782019-10-01 14:34:56 +0100323 }
324 return changeInfos;
325 }
326 }
327
atrost7e6060f2019-07-24 14:12:48 +0100328 CompatConfig initConfigFromLib(File libraryDir) {
329 if (!libraryDir.exists() || !libraryDir.isDirectory()) {
330 Slog.e(TAG, "No directory " + libraryDir + ", skipping");
331 return this;
332 }
333 for (File f : libraryDir.listFiles()) {
atrost7aa64a72019-08-29 14:22:13 +0100334 Slog.d(TAG, "Found a config file: " + f.getPath());
atrost7e6060f2019-07-24 14:12:48 +0100335 //TODO(b/138222363): Handle duplicate ids across config files.
atrost7aa64a72019-08-29 14:22:13 +0100336 readConfig(f);
atrost7e6060f2019-07-24 14:12:48 +0100337 }
338 return this;
339 }
340
341 private void readConfig(File configFile) {
342 try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) {
343 for (Change change : XmlParser.read(in).getCompatChange()) {
atrost7aa64a72019-08-29 14:22:13 +0100344 Slog.d(TAG, "Adding: " + change.toString());
atrost7e6060f2019-07-24 14:12:48 +0100345 addChange(new CompatChange(change));
346 }
347 } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
348 Slog.e(TAG, "Encountered an error while reading/parsing compat config file", e);
349 }
350 }
351
Mathew Inwoode188acc2019-06-20 15:13:33 +0100352}