1 /*
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 
17 package com.android.server.compat;
18 
19 import static android.Manifest.permission.LOG_COMPAT_CHANGE;
20 import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG;
21 import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD;
22 import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;
23 
24 import android.annotation.EnforcePermission;
25 import android.annotation.RequiresNoPermission;
26 import android.annotation.UserIdInt;
27 import android.app.ActivityManager;
28 import android.app.IActivityManager;
29 import android.app.compat.PackageOverride;
30 import android.content.BroadcastReceiver;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.IntentFilter;
34 import android.content.pm.ApplicationInfo;
35 import android.content.pm.PackageManagerInternal;
36 import android.net.Uri;
37 import android.os.Binder;
38 import android.os.Build;
39 import android.os.Process;
40 import android.os.RemoteException;
41 import android.os.UserHandle;
42 import android.util.Slog;
43 
44 import com.android.internal.annotations.VisibleForTesting;
45 import com.android.internal.compat.AndroidBuildClassifier;
46 import com.android.internal.compat.ChangeReporter;
47 import com.android.internal.compat.CompatibilityChangeConfig;
48 import com.android.internal.compat.CompatibilityChangeInfo;
49 import com.android.internal.compat.CompatibilityOverrideConfig;
50 import com.android.internal.compat.CompatibilityOverridesByPackageConfig;
51 import com.android.internal.compat.CompatibilityOverridesToRemoveByPackageConfig;
52 import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
53 import com.android.internal.compat.IOverrideValidator;
54 import com.android.internal.compat.IPlatformCompat;
55 import com.android.internal.util.DumpUtils;
56 import com.android.server.LocalServices;
57 
58 import java.io.FileDescriptor;
59 import java.io.PrintWriter;
60 import java.util.Arrays;
61 import java.util.Collection;
62 import java.util.HashMap;
63 import java.util.Map;
64 
65 /**
66  * System server internal API for gating and reporting compatibility changes.
67  */
68 public class PlatformCompat extends IPlatformCompat.Stub {
69 
70     private static final String TAG = "Compatibility";
71 
72     private final Context mContext;
73     private final ChangeReporter mChangeReporter;
74     private final CompatConfig mCompatConfig;
75     private final AndroidBuildClassifier mBuildClassifier;
76 
PlatformCompat(Context context)77     public PlatformCompat(Context context) {
78         mContext = context;
79         mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_SYSTEM_SERVER);
80         mBuildClassifier = new AndroidBuildClassifier();
81         mCompatConfig = CompatConfig.create(mBuildClassifier, mContext);
82     }
83 
84     @VisibleForTesting
PlatformCompat(Context context, CompatConfig compatConfig, AndroidBuildClassifier buildClassifier)85     PlatformCompat(Context context, CompatConfig compatConfig,
86             AndroidBuildClassifier buildClassifier) {
87         mContext = context;
88         mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_SYSTEM_SERVER);
89         mCompatConfig = compatConfig;
90         mBuildClassifier = buildClassifier;
91 
92         registerPackageReceiver(context);
93     }
94 
95     @Override
96     @EnforcePermission(LOG_COMPAT_CHANGE)
reportChange(long changeId, ApplicationInfo appInfo)97     public void reportChange(long changeId, ApplicationInfo appInfo) {
98         super.reportChange_enforcePermission();
99 
100         reportChangeInternal(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED);
101     }
102 
103     @Override
104     @EnforcePermission(LOG_COMPAT_CHANGE)
reportChangeByPackageName(long changeId, String packageName, @UserIdInt int userId)105     public void reportChangeByPackageName(long changeId, String packageName,
106             @UserIdInt int userId) {
107         super.reportChangeByPackageName_enforcePermission();
108 
109         ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
110         if (appInfo != null) {
111             reportChangeInternal(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED);
112         }
113     }
114 
115     @Override
116     @EnforcePermission(LOG_COMPAT_CHANGE)
reportChangeByUid(long changeId, int uid)117     public void reportChangeByUid(long changeId, int uid) {
118         super.reportChangeByUid_enforcePermission();
119 
120         reportChangeInternal(changeId, uid, ChangeReporter.STATE_LOGGED);
121     }
122 
123     /**
124      * Report the change, but skip over the sdk target version check. This can be used to force the
125      * debug logs.
126      *
127      * @param changeId        of the change to report
128      * @param uid             of the user
129      * @param state           of the change - enabled/disabled/logged
130      */
reportChangeInternal(long changeId, int uid, int state)131     private void reportChangeInternal(long changeId, int uid, int state) {
132         mChangeReporter.reportChange(uid, changeId, state, true);
133     }
134 
135     @Override
136     @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG})
isChangeEnabled(long changeId, ApplicationInfo appInfo)137     public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
138         super.isChangeEnabled_enforcePermission();
139 
140         return isChangeEnabledInternal(changeId, appInfo);
141     }
142 
143     @Override
144     @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG})
isChangeEnabledByPackageName(long changeId, String packageName, @UserIdInt int userId)145     public boolean isChangeEnabledByPackageName(long changeId, String packageName,
146             @UserIdInt int userId) {
147         super.isChangeEnabledByPackageName_enforcePermission();
148 
149         ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
150         if (appInfo == null) {
151             return mCompatConfig.willChangeBeEnabled(changeId, packageName);
152         }
153         return isChangeEnabledInternal(changeId, appInfo);
154     }
155 
156     @Override
157     @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG})
isChangeEnabledByUid(long changeId, int uid)158     public boolean isChangeEnabledByUid(long changeId, int uid) {
159         super.isChangeEnabledByUid_enforcePermission();
160 
161         return isChangeEnabledByUidInternal(changeId, uid);
162     }
163 
164     /**
165      * Internal version of the above method, without logging.
166      *
167      * <p>Does not perform costly permission check.
168      * TODO(b/167551701): Remove this method and add 'loggability' as a changeid property.
169      */
isChangeEnabledInternalNoLogging(long changeId, ApplicationInfo appInfo)170     public boolean isChangeEnabledInternalNoLogging(long changeId, ApplicationInfo appInfo) {
171         return mCompatConfig.isChangeEnabled(changeId, appInfo);
172     }
173 
174     /**
175      * Internal version of {@link #isChangeEnabled(long, ApplicationInfo)}. If the provided appInfo
176      * is not null, also reports the change.
177      *
178      * @param changeId of the change to report
179      * @param appInfo  the app to check
180      *
181      * <p>Does not perform costly permission check.
182      */
isChangeEnabledInternal(long changeId, ApplicationInfo appInfo)183     public boolean isChangeEnabledInternal(long changeId, ApplicationInfo appInfo) {
184         // Fetch the CompatChange. This is done here instead of in mCompatConfig to avoid multiple
185         // fetches.
186         CompatChange c = mCompatConfig.getCompatChange(changeId);
187 
188         boolean enabled = mCompatConfig.isChangeEnabled(c, appInfo);
189         int state = enabled ? ChangeReporter.STATE_ENABLED : ChangeReporter.STATE_DISABLED;
190         if (appInfo != null) {
191             boolean isTargetingLatestSdk =
192                     mCompatConfig.isChangeTargetingLatestSdk(c, appInfo.targetSdkVersion);
193             mChangeReporter.reportChange(appInfo.uid, changeId, state, isTargetingLatestSdk);
194         }
195         return enabled;
196     }
197 
198     /**
199      * Called by the package manager to check if a given change is enabled for a given package name
200      * and the target sdk version while the package is in the parsing state.
201      *
202      * <p>Does not perform costly permission check.
203      *
204      * @param changeId         the ID of the change in question
205      * @param packageName      package name to check for
206      * @param targetSdkVersion target sdk version to check for
207      * @return {@code true} if the change would be enabled for this package name.
208      */
isChangeEnabledInternal(long changeId, String packageName, int targetSdkVersion)209     public boolean isChangeEnabledInternal(long changeId, String packageName,
210             int targetSdkVersion) {
211         if (mCompatConfig.willChangeBeEnabled(changeId, packageName)) {
212             final ApplicationInfo appInfo = new ApplicationInfo();
213             appInfo.packageName = packageName;
214             appInfo.targetSdkVersion = targetSdkVersion;
215             return isChangeEnabledInternalNoLogging(changeId, appInfo);
216         }
217         return false;
218     }
219 
220     /**
221      * Internal version of {@link #isChangeEnabledByUid(long, int)}.
222      *
223      * <p>Does not perform costly permission check.
224      */
isChangeEnabledByUidInternal(long changeId, int uid)225     public boolean isChangeEnabledByUidInternal(long changeId, int uid) {
226         String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
227         if (packages == null || packages.length == 0) {
228             return mCompatConfig.defaultChangeIdValue(changeId);
229         }
230         boolean enabled = true;
231         final int userId = UserHandle.getUserId(uid);
232         for (String packageName : packages) {
233             final var appInfo = getApplicationInfo(packageName, userId);
234             enabled &= isChangeEnabledInternal(changeId, appInfo);
235         }
236         return enabled;
237     }
238 
239     /**
240      * Internal version of {@link #isChangeEnabledByUid(long, int)}.
241      *
242      * <p>Does not perform costly permission check and logging.
243      */
isChangeEnabledByUidInternalNoLogging(long changeId, int uid)244     public boolean isChangeEnabledByUidInternalNoLogging(long changeId, int uid) {
245         String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
246         if (packages == null || packages.length == 0) {
247             return mCompatConfig.defaultChangeIdValue(changeId);
248         }
249         boolean enabled = true;
250         final int userId = UserHandle.getUserId(uid);
251         for (String packageName : packages) {
252             final var appInfo = getApplicationInfo(packageName, userId);
253             enabled &= isChangeEnabledInternalNoLogging(changeId, appInfo);
254         }
255         return enabled;
256     }
257 
258     @Override
259     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
setOverrides(CompatibilityChangeConfig overrides, String packageName)260     public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
261         super.setOverrides_enforcePermission();
262 
263         Map<Long, PackageOverride> overridesMap = new HashMap<>();
264         for (long change : overrides.enabledChanges()) {
265             overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build());
266         }
267         for (long change : overrides.disabledChanges()) {
268             overridesMap.put(change, new PackageOverride.Builder().setEnabled(false)
269                     .build());
270         }
271         mCompatConfig.addPackageOverrides(new CompatibilityOverrideConfig(overridesMap),
272                 packageName, /* skipUnknownChangeIds */ false);
273         killPackage(packageName);
274     }
275 
276     @Override
277     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
setOverridesForTest(CompatibilityChangeConfig overrides, String packageName)278     public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) {
279         super.setOverridesForTest_enforcePermission();
280 
281         Map<Long, PackageOverride> overridesMap = new HashMap<>();
282         for (long change : overrides.enabledChanges()) {
283             overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build());
284         }
285         for (long change : overrides.disabledChanges()) {
286             overridesMap.put(change, new PackageOverride.Builder().setEnabled(false)
287                     .build());
288         }
289         mCompatConfig.addPackageOverrides(new CompatibilityOverrideConfig(overridesMap),
290                 packageName, /* skipUnknownChangeIds */ false);
291     }
292 
293     @Override
294     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
putAllOverridesOnReleaseBuilds( CompatibilityOverridesByPackageConfig overridesByPackage)295     public void putAllOverridesOnReleaseBuilds(
296             CompatibilityOverridesByPackageConfig overridesByPackage) {
297         super.putAllOverridesOnReleaseBuilds_enforcePermission();
298 
299         for (CompatibilityOverrideConfig overrides :
300                 overridesByPackage.packageNameToOverrides.values()) {
301             checkAllCompatOverridesAreOverridable(overrides.overrides.keySet());
302         }
303         mCompatConfig.addAllPackageOverrides(overridesByPackage, /* skipUnknownChangeIds= */ true);
304     }
305 
306     @Override
307     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
putOverridesOnReleaseBuilds(CompatibilityOverrideConfig overrides, String packageName)308     public void putOverridesOnReleaseBuilds(CompatibilityOverrideConfig overrides,
309             String packageName) {
310         super.putOverridesOnReleaseBuilds_enforcePermission();
311 
312         checkAllCompatOverridesAreOverridable(overrides.overrides.keySet());
313         mCompatConfig.addPackageOverrides(overrides, packageName, /* skipUnknownChangeIds= */ true);
314     }
315 
316     @Override
317     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
enableTargetSdkChanges(String packageName, int targetSdkVersion)318     public int enableTargetSdkChanges(String packageName, int targetSdkVersion) {
319         super.enableTargetSdkChanges_enforcePermission();
320 
321         int numChanges =
322                 mCompatConfig.enableTargetSdkChangesForPackage(packageName, targetSdkVersion);
323         killPackage(packageName);
324         return numChanges;
325     }
326 
327     @Override
328     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
disableTargetSdkChanges(String packageName, int targetSdkVersion)329     public int disableTargetSdkChanges(String packageName, int targetSdkVersion) {
330         super.disableTargetSdkChanges_enforcePermission();
331 
332         int numChanges =
333                 mCompatConfig.disableTargetSdkChangesForPackage(packageName, targetSdkVersion);
334         killPackage(packageName);
335         return numChanges;
336     }
337 
338     @Override
339     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
clearOverrides(String packageName)340     public void clearOverrides(String packageName) {
341         super.clearOverrides_enforcePermission();
342 
343         mCompatConfig.removePackageOverrides(packageName);
344         killPackage(packageName);
345     }
346 
347     @Override
348     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
clearOverridesForTest(String packageName)349     public void clearOverridesForTest(String packageName) {
350         super.clearOverridesForTest_enforcePermission();
351 
352         mCompatConfig.removePackageOverrides(packageName);
353     }
354 
355     @Override
356     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
clearOverride(long changeId, String packageName)357     public boolean clearOverride(long changeId, String packageName) {
358         super.clearOverride_enforcePermission();
359 
360         boolean existed = mCompatConfig.removeOverride(changeId, packageName);
361         killPackage(packageName);
362         return existed;
363     }
364 
365     @Override
366     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
clearOverrideForTest(long changeId, String packageName)367     public boolean clearOverrideForTest(long changeId, String packageName) {
368         super.clearOverrideForTest_enforcePermission();
369 
370         return mCompatConfig.removeOverride(changeId, packageName);
371     }
372 
373     @Override
374     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
removeAllOverridesOnReleaseBuilds( CompatibilityOverridesToRemoveByPackageConfig overridesToRemoveByPackage)375     public void removeAllOverridesOnReleaseBuilds(
376             CompatibilityOverridesToRemoveByPackageConfig overridesToRemoveByPackage) {
377         super.removeAllOverridesOnReleaseBuilds_enforcePermission();
378 
379         for (CompatibilityOverridesToRemoveConfig overridesToRemove :
380                 overridesToRemoveByPackage.packageNameToOverridesToRemove.values()) {
381             checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds);
382         }
383         mCompatConfig.removeAllPackageOverrides(overridesToRemoveByPackage);
384     }
385 
386     @Override
387     @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
removeOverridesOnReleaseBuilds( CompatibilityOverridesToRemoveConfig overridesToRemove, String packageName)388     public void removeOverridesOnReleaseBuilds(
389             CompatibilityOverridesToRemoveConfig overridesToRemove,
390             String packageName) {
391         super.removeOverridesOnReleaseBuilds_enforcePermission();
392 
393         checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds);
394         mCompatConfig.removePackageOverrides(overridesToRemove, packageName);
395     }
396 
397     @Override
398     @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG})
getAppConfig(ApplicationInfo appInfo)399     public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) {
400         super.getAppConfig_enforcePermission();
401 
402         return mCompatConfig.getAppConfig(appInfo);
403     }
404 
405     @Override
406     @EnforcePermission(READ_COMPAT_CHANGE_CONFIG)
listAllChanges()407     public CompatibilityChangeInfo[] listAllChanges() {
408         super.listAllChanges_enforcePermission();
409 
410         return mCompatConfig.dumpChanges();
411     }
412 
413     @Override
414     @RequiresNoPermission
listUIChanges()415     public CompatibilityChangeInfo[] listUIChanges() {
416         return Arrays.stream(listAllChanges()).filter(this::isShownInUI).toArray(
417                 CompatibilityChangeInfo[]::new);
418     }
419 
420     /** Checks whether the change is known to the compat config. */
isKnownChangeId(long changeId)421     public boolean isKnownChangeId(long changeId) {
422         return mCompatConfig.isKnownChangeId(changeId);
423     }
424 
425     /**
426      * Retrieves the set of disabled changes for a given app. Any change ID not in the returned
427      * array is by default enabled for the app.
428      *
429      * @param appInfo The app in question
430      * @return A sorted long array of change IDs. We use a primitive array to minimize memory
431      * footprint: Every app process will store this array statically so we aim to reduce
432      * overhead as much as possible.
433      */
getDisabledChanges(ApplicationInfo appInfo)434     public long[] getDisabledChanges(ApplicationInfo appInfo) {
435         return mCompatConfig.getDisabledChanges(appInfo);
436     }
437 
438     /**
439      * Retrieves the set of changes that should be logged for a given app. Any change ID not in the
440      * returned array is ignored for logging purposes.
441      *
442      * @param appInfo The app in question
443      * @return A sorted long array of change IDs. We use a primitive array to minimize memory
444      * footprint: Every app process will store this array statically so we aim to reduce
445      * overhead as much as possible.
446      */
getLoggableChanges(ApplicationInfo appInfo)447     public long[] getLoggableChanges(ApplicationInfo appInfo) {
448         return mCompatConfig.getLoggableChanges(appInfo);
449     }
450 
451     /**
452      * Look up a change ID by name.
453      *
454      * @param name Name of the change to look up
455      * @return The change ID, or {@code -1} if no change with that name exists.
456      */
lookupChangeId(String name)457     public long lookupChangeId(String name) {
458         return mCompatConfig.lookupChangeId(name);
459     }
460 
461     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)462     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
463         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) {
464             return;
465         }
466         mContext.enforceCallingOrSelfPermission(
467                 READ_COMPAT_CHANGE_CONFIG, "Cannot read compat change");
468         mContext.enforceCallingOrSelfPermission(
469                 LOG_COMPAT_CHANGE, "Cannot read log compat change usage");
470         mCompatConfig.dumpConfig(pw);
471     }
472 
473     @Override
474     @RequiresNoPermission
getOverrideValidator()475     public IOverrideValidator getOverrideValidator() {
476         return mCompatConfig.getOverrideValidator();
477     }
478 
479     /**
480      * Clears information stored about events reported on behalf of an app.
481      *
482      * <p>To be called once upon app start or end. A second call would be a no-op.
483      *
484      * @param appInfo the app to reset
485      */
resetReporting(ApplicationInfo appInfo)486     public void resetReporting(ApplicationInfo appInfo) {
487         mChangeReporter.resetReportedChanges(appInfo.uid);
488     }
489 
getApplicationInfo(String packageName, int userId)490     private ApplicationInfo getApplicationInfo(String packageName, int userId) {
491         return LocalServices.getService(PackageManagerInternal.class).getApplicationInfo(
492                 packageName, 0, Process.myUid(), userId);
493     }
494 
killPackage(String packageName)495     private void killPackage(String packageName) {
496         int uid = LocalServices.getService(PackageManagerInternal.class).getPackageUid(packageName,
497                 0, UserHandle.myUserId());
498 
499         if (uid < 0) {
500             Slog.w(TAG, "Didn't find package " + packageName + " on device.");
501             return;
502         }
503 
504         Slog.d(TAG, "Killing package " + packageName + " (UID " + uid + ").");
505         killUid(UserHandle.getAppId(uid));
506     }
507 
killUid(int appId)508     private void killUid(int appId) {
509         final long identity = Binder.clearCallingIdentity();
510         try {
511             IActivityManager am = ActivityManager.getService();
512             if (am != null) {
513                 am.killUid(appId, UserHandle.USER_ALL, "PlatformCompat overrides");
514             }
515         } catch (RemoteException e) {
516             /* ignore - same process */
517         } finally {
518             Binder.restoreCallingIdentity(identity);
519         }
520     }
521 
checkAllCompatOverridesAreOverridable(Collection<Long> changeIds)522     private void checkAllCompatOverridesAreOverridable(Collection<Long> changeIds) {
523         for (Long changeId : changeIds) {
524             if (isKnownChangeId(changeId) && !mCompatConfig.isOverridable(changeId)) {
525                 throw new SecurityException("Only change ids marked as Overridable can be "
526                         + "overridden.");
527             }
528         }
529     }
530 
isShownInUI(CompatibilityChangeInfo change)531     private boolean isShownInUI(CompatibilityChangeInfo change) {
532         if (change.getLoggingOnly()) {
533             return false;
534         }
535         if (change.getId() == CompatChange.CTS_SYSTEM_API_CHANGEID) {
536             return false;
537         }
538         if (change.getEnableSinceTargetSdk() > 0) {
539             return change.getEnableSinceTargetSdk() >= Build.VERSION_CODES.Q
540                     && change.getEnableSinceTargetSdk() <= mBuildClassifier.platformTargetSdk();
541         }
542         return true;
543     }
544 
545     /**
546      * Registers a listener for change state overrides.
547      *
548      * <p>Only one listener per change is allowed.
549      *
550      * <p>{@code listener.onCompatChange(String)} method is guaranteed to be called with
551      * packageName before the app is killed upon an override change. The state of a change is not
552      * guaranteed to change when {@code listener.onCompatChange(String)} is called.
553      *
554      * @param changeId to get updates for
555      * @param listener the listener that will be called upon a potential change for package
556      * @return {@code true} if a change with changeId was already known, or (@code false}
557      * otherwise
558      * @throws IllegalStateException if a listener was already registered for changeId
559      */
registerListener(long changeId, CompatChange.ChangeListener listener)560     public boolean registerListener(long changeId, CompatChange.ChangeListener listener) {
561         return mCompatConfig.registerListener(changeId, listener);
562     }
563 
564     /**
565      * Registers a broadcast receiver that listens for package install, replace or remove.
566      *
567      * @param context the context where the receiver should be registered
568      */
registerPackageReceiver(Context context)569     public void registerPackageReceiver(Context context) {
570         final BroadcastReceiver receiver = new BroadcastReceiver() {
571             @Override
572             public void onReceive(Context context, Intent intent) {
573                 if (intent == null) {
574                     return;
575                 }
576                 final Uri packageData = intent.getData();
577                 if (packageData == null) {
578                     return;
579                 }
580                 final String packageName = packageData.getSchemeSpecificPart();
581                 if (packageName == null) {
582                     return;
583                 }
584                 mCompatConfig.recheckOverrides(packageName);
585             }
586         };
587         IntentFilter filter = new IntentFilter();
588         filter.addAction(Intent.ACTION_PACKAGE_ADDED);
589         filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
590         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
591         filter.addDataScheme("package");
592         context.registerReceiverForAllUsers(receiver, filter, /* broadcastPermission= */
593                 null, /* scheduler= */ null);
594     }
595 
596     /**
597      * Registers the observer for
598      * {@link android.provider.Settings.Global#FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT}.
599      */
registerContentObserver()600     public void registerContentObserver() {
601         mCompatConfig.registerContentObserver();
602     }
603 }
604