1 /*
2  * Copyright (C) 2020 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 android.app.compat;
18 
19 import android.annotation.NonNull;
20 import android.annotation.RequiresPermission;
21 import android.annotation.SystemApi;
22 import android.compat.Compatibility;
23 import android.os.RemoteException;
24 import android.os.UserHandle;
25 import android.util.ArrayMap;
26 
27 import com.android.internal.compat.CompatibilityOverrideConfig;
28 import com.android.internal.compat.CompatibilityOverridesByPackageConfig;
29 import com.android.internal.compat.CompatibilityOverridesToRemoveByPackageConfig;
30 import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
31 
32 import java.util.Map;
33 import java.util.Set;
34 
35 /**
36  * CompatChanges APIs - to be used by platform code only (including mainline
37  * modules).
38  *
39  * @hide
40  */
41 @SystemApi
42 public final class CompatChanges {
43     private static final ChangeIdStateCache QUERY_CACHE = new ChangeIdStateCache();
44 
CompatChanges()45     private CompatChanges() {}
46 
47     /**
48      * Query if a given compatibility change is enabled for the current process. This method is
49      * intended to be called by code running inside a process of the affected app only.
50      *
51      * <p>If this method returns {@code true}, the calling code should implement the compatibility
52      * change, resulting in differing behaviour compared to earlier releases. If this method returns
53      * {@code false}, the calling code should behave as it did in earlier releases.
54      *
55      * @param changeId The ID of the compatibility change in question.
56      * @return {@code true} if the change is enabled for the current app.
57      */
isChangeEnabled(long changeId)58     public static boolean isChangeEnabled(long changeId) {
59         return Compatibility.isChangeEnabled(changeId);
60     }
61 
62     /**
63      * Same as {@code #isChangeEnabled(long)}, except this version should be called on behalf of an
64      * app from a different process that's performing work for the app.
65      *
66      * <p> Note that this involves a binder call to the system server (unless running in the system
67      * server). If the binder call fails, a {@code RuntimeException} will be thrown.
68      *
69      * @param changeId    The ID of the compatibility change in question.
70      * @param packageName The package name of the app in question.
71      * @param user        The user that the operation is done for.
72      * @return {@code true} if the change is enabled for the current app.
73      */
74     @RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
75             android.Manifest.permission.LOG_COMPAT_CHANGE})
isChangeEnabled(long changeId, @NonNull String packageName, @NonNull UserHandle user)76     public static boolean isChangeEnabled(long changeId, @NonNull String packageName,
77             @NonNull UserHandle user) {
78         return QUERY_CACHE.query(ChangeIdStateQuery.byPackageName(changeId, packageName,
79                                                            user.getIdentifier()));
80     }
81 
82     /**
83      * Same as {@code #isChangeEnabled(long)}, except this version should be called on behalf of an
84      * app from a different process that's performing work for the app.
85      *
86      * <p> Note that this involves a binder call to the system server (unless running in the system
87      * server). If the binder call fails, {@code RuntimeException}  will be thrown.
88      *
89      * <p> Returns {@code true} if there are no installed packages for the required UID, or if the
90      * change is enabled for ALL of the installed packages associated with the provided UID. Please
91      * use a more specific API if you want a different behaviour for multi-package UIDs.
92      *
93      * @param changeId The ID of the compatibility change in question.
94      * @param uid      The UID of the app in question.
95      * @return {@code true} if the change is enabled for the current app.
96      */
97     @RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
98             android.Manifest.permission.LOG_COMPAT_CHANGE})
isChangeEnabled(long changeId, int uid)99     public static boolean isChangeEnabled(long changeId, int uid) {
100         return QUERY_CACHE.query(ChangeIdStateQuery.byUid(changeId, uid));
101     }
102 
103     /**
104      * Equivalent to calling {@link #putPackageOverrides(String, Map)} on each entry in {@code
105      * packageNameToOverrides}, but the state of the compat config will be updated only once
106      * instead of for each package.
107      *
108      * @param packageNameToOverrides A map from package name to a map from change ID to the
109      *                               override applied for that package name and change ID.
110      */
111     @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
putAllPackageOverrides( @onNull Map<String, Map<Long, PackageOverride>> packageNameToOverrides)112     public static void putAllPackageOverrides(
113             @NonNull Map<String, Map<Long, PackageOverride>> packageNameToOverrides) {
114         ArrayMap<String, CompatibilityOverrideConfig> packageNameToConfig = new ArrayMap<>();
115         for (String packageName : packageNameToOverrides.keySet()) {
116             packageNameToConfig.put(packageName,
117                     new CompatibilityOverrideConfig(packageNameToOverrides.get(packageName)));
118         }
119         CompatibilityOverridesByPackageConfig config = new CompatibilityOverridesByPackageConfig(
120                 packageNameToConfig);
121         try {
122             QUERY_CACHE.getPlatformCompatService().putAllOverridesOnReleaseBuilds(config);
123         } catch (RemoteException e) {
124             e.rethrowFromSystemServer();
125         }
126     }
127 
128     /**
129      * Associates app compat overrides with the given package and their respective change IDs.
130      * This will check whether the caller is allowed to perform this operation on the given apk and
131      * build. Only the installer package is allowed to set overrides on a non-debuggable final
132      * build and a non-test apk.
133      *
134      * <p>Note that calling this method doesn't remove previously added overrides for the given
135      * package if their change ID isn't in the given map, only replaces those that have the same
136      * change ID.
137      *
138      * @param packageName The package name of the app in question.
139      * @param overrides A map from change ID to the override applied for this change ID.
140      */
141     @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
putPackageOverrides(@onNull String packageName, @NonNull Map<Long, PackageOverride> overrides)142     public static void putPackageOverrides(@NonNull String packageName,
143             @NonNull Map<Long, PackageOverride> overrides) {
144         CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overrides);
145         try {
146             QUERY_CACHE.getPlatformCompatService()
147                 .putOverridesOnReleaseBuilds(config, packageName);
148         } catch (RemoteException e) {
149             e.rethrowFromSystemServer();
150         }
151     }
152 
153     /**
154      * Equivalent to calling {@link #removePackageOverrides(String, Set)} on each entry in {@code
155      * packageNameToOverridesToRemove}, but the state of the compat config will be updated only once
156      * instead of for each package.
157      *
158      * @param packageNameToOverridesToRemove A map from package name to a set of change IDs for
159      *                                       which to remove overrides for that package name.
160      */
161     @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
removeAllPackageOverrides( @onNull Map<String, Set<Long>> packageNameToOverridesToRemove)162     public static void removeAllPackageOverrides(
163             @NonNull Map<String, Set<Long>> packageNameToOverridesToRemove) {
164         ArrayMap<String, CompatibilityOverridesToRemoveConfig> packageNameToConfig =
165                 new ArrayMap<>();
166         for (String packageName : packageNameToOverridesToRemove.keySet()) {
167             packageNameToConfig.put(packageName,
168                     new CompatibilityOverridesToRemoveConfig(
169                             packageNameToOverridesToRemove.get(packageName)));
170         }
171         CompatibilityOverridesToRemoveByPackageConfig config =
172                 new CompatibilityOverridesToRemoveByPackageConfig(packageNameToConfig);
173         try {
174             QUERY_CACHE.getPlatformCompatService().removeAllOverridesOnReleaseBuilds(config);
175         } catch (RemoteException e) {
176             e.rethrowFromSystemServer();
177         }
178     }
179 
180     /**
181      * Removes app compat overrides for the given package. This will check whether the caller is
182      * allowed to perform this operation on the given apk and build. Only the installer package is
183      * allowed to clear overrides on a non-debuggable final build and a non-test apk.
184      *
185      * <p>Note that calling this method with an empty set is a no-op and no overrides will be
186      * removed for the given package.
187      *
188      * @param packageName The package name of the app in question.
189      * @param overridesToRemove A set of change IDs for which to remove overrides.
190      */
191     @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
removePackageOverrides(@onNull String packageName, @NonNull Set<Long> overridesToRemove)192     public static void removePackageOverrides(@NonNull String packageName,
193             @NonNull Set<Long> overridesToRemove) {
194         CompatibilityOverridesToRemoveConfig config = new CompatibilityOverridesToRemoveConfig(
195                 overridesToRemove);
196         try {
197             QUERY_CACHE.getPlatformCompatService()
198                 .removeOverridesOnReleaseBuilds(config, packageName);
199         } catch (RemoteException e) {
200             e.rethrowFromSystemServer();
201         }
202     }
203 }
204