• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.READ_COMPAT_CHANGE_CONFIG;
22 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
23 
24 import android.annotation.UserIdInt;
25 import android.app.ActivityManager;
26 import android.app.IActivityManager;
27 import android.content.Context;
28 import android.content.pm.ApplicationInfo;
29 import android.content.pm.PackageManagerInternal;
30 import android.os.Binder;
31 import android.os.Build;
32 import android.os.RemoteException;
33 import android.os.UserHandle;
34 import android.util.Slog;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.internal.compat.AndroidBuildClassifier;
38 import com.android.internal.compat.ChangeReporter;
39 import com.android.internal.compat.CompatibilityChangeConfig;
40 import com.android.internal.compat.CompatibilityChangeInfo;
41 import com.android.internal.compat.IOverrideValidator;
42 import com.android.internal.compat.IPlatformCompat;
43 import com.android.internal.util.DumpUtils;
44 import com.android.server.LocalServices;
45 
46 import java.io.FileDescriptor;
47 import java.io.PrintWriter;
48 import java.util.Arrays;
49 
50 /**
51  * System server internal API for gating and reporting compatibility changes.
52  */
53 public class PlatformCompat extends IPlatformCompat.Stub {
54 
55     private static final String TAG = "Compatibility";
56 
57     private final Context mContext;
58     private final ChangeReporter mChangeReporter;
59     private final CompatConfig mCompatConfig;
60 
61     private static int sMinTargetSdk = Build.VERSION_CODES.P;
62     private static int sMaxTargetSdk = Build.VERSION_CODES.Q;
63 
PlatformCompat(Context context)64     public PlatformCompat(Context context) {
65         mContext = context;
66         mChangeReporter = new ChangeReporter(
67                 ChangeReporter.SOURCE_SYSTEM_SERVER);
68         mCompatConfig = CompatConfig.create(new AndroidBuildClassifier(), mContext);
69     }
70 
71     @VisibleForTesting
PlatformCompat(Context context, CompatConfig compatConfig)72     PlatformCompat(Context context, CompatConfig compatConfig) {
73         mContext = context;
74         mChangeReporter = new ChangeReporter(
75                 ChangeReporter.SOURCE_SYSTEM_SERVER);
76         mCompatConfig = compatConfig;
77     }
78 
79     @Override
reportChange(long changeId, ApplicationInfo appInfo)80     public void reportChange(long changeId, ApplicationInfo appInfo) {
81         checkCompatChangeLogPermission();
82         reportChange(changeId, appInfo.uid,
83                 ChangeReporter.STATE_LOGGED);
84     }
85 
86     @Override
reportChangeByPackageName(long changeId, String packageName, int userId)87     public void reportChangeByPackageName(long changeId, String packageName, int userId) {
88         checkCompatChangeLogPermission();
89         ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
90         if (appInfo == null) {
91             return;
92         }
93         reportChange(changeId, appInfo);
94     }
95 
96     @Override
reportChangeByUid(long changeId, int uid)97     public void reportChangeByUid(long changeId, int uid) {
98         checkCompatChangeLogPermission();
99         reportChange(changeId, uid, ChangeReporter.STATE_LOGGED);
100     }
101 
102     @Override
isChangeEnabled(long changeId, ApplicationInfo appInfo)103     public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
104         checkCompatChangeReadAndLogPermission();
105         return isChangeEnabledInternal(changeId, appInfo);
106     }
107 
108     /**
109      * Internal version of the above method. Does not perform costly permission check.
110      */
isChangeEnabledInternal(long changeId, ApplicationInfo appInfo)111     public boolean isChangeEnabledInternal(long changeId, ApplicationInfo appInfo) {
112         if (mCompatConfig.isChangeEnabled(changeId, appInfo)) {
113             reportChange(changeId, appInfo.uid,
114                     ChangeReporter.STATE_ENABLED);
115             return true;
116         }
117         reportChange(changeId, appInfo.uid,
118                 ChangeReporter.STATE_DISABLED);
119         return false;
120     }
121 
122     @Override
isChangeEnabledByPackageName(long changeId, String packageName, @UserIdInt int userId)123     public boolean isChangeEnabledByPackageName(long changeId, String packageName,
124             @UserIdInt int userId) {
125         checkCompatChangeReadAndLogPermission();
126         ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
127         if (appInfo == null) {
128             return true;
129         }
130         return isChangeEnabled(changeId, appInfo);
131     }
132 
133     @Override
isChangeEnabledByUid(long changeId, int uid)134     public boolean isChangeEnabledByUid(long changeId, int uid) {
135         checkCompatChangeReadAndLogPermission();
136         String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
137         if (packages == null || packages.length == 0) {
138             return true;
139         }
140         boolean enabled = true;
141         for (String packageName : packages) {
142             enabled = enabled && isChangeEnabledByPackageName(changeId, packageName,
143                     UserHandle.getUserId(uid));
144         }
145         return enabled;
146     }
147 
148     /**
149      * Register a listener for change state overrides. Only one listener per change is allowed.
150      *
151      * <p>{@code listener.onCompatChange(String)} method is guaranteed to be called with
152      * packageName before the app is killed upon an override change. The state of a change is not
153      * guaranteed to change when {@code listener.onCompatChange(String)} is called.
154      *
155      * @param changeId to get updates for
156      * @param listener the listener that will be called upon a potential change for package.
157      * @throws IllegalStateException if a listener was already registered for changeId
158      * @returns {@code true} if a change with changeId was already known, or (@code false}
159      * otherwise.
160      */
registerListener(long changeId, CompatChange.ChangeListener listener)161     public boolean registerListener(long changeId, CompatChange.ChangeListener listener) {
162         return mCompatConfig.registerListener(changeId, listener);
163     }
164 
165     @Override
setOverrides(CompatibilityChangeConfig overrides, String packageName)166     public void setOverrides(CompatibilityChangeConfig overrides, String packageName)
167             throws RemoteException, SecurityException {
168         checkCompatChangeOverridePermission();
169         mCompatConfig.addOverrides(overrides, packageName);
170         killPackage(packageName);
171     }
172 
173     @Override
setOverridesForTest(CompatibilityChangeConfig overrides, String packageName)174     public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName)
175             throws RemoteException, SecurityException {
176         checkCompatChangeOverridePermission();
177         mCompatConfig.addOverrides(overrides, packageName);
178     }
179 
180     @Override
enableTargetSdkChanges(String packageName, int targetSdkVersion)181     public int enableTargetSdkChanges(String packageName, int targetSdkVersion)
182             throws RemoteException, SecurityException {
183         checkCompatChangeOverridePermission();
184         int numChanges = mCompatConfig.enableTargetSdkChangesForPackage(packageName,
185                                                                         targetSdkVersion);
186         killPackage(packageName);
187         return numChanges;
188     }
189 
190     @Override
disableTargetSdkChanges(String packageName, int targetSdkVersion)191     public int disableTargetSdkChanges(String packageName, int targetSdkVersion)
192             throws RemoteException, SecurityException {
193         checkCompatChangeOverridePermission();
194         int numChanges = mCompatConfig.disableTargetSdkChangesForPackage(packageName,
195                                                                          targetSdkVersion);
196         killPackage(packageName);
197         return numChanges;
198     }
199 
200     @Override
clearOverrides(String packageName)201     public void clearOverrides(String packageName) throws RemoteException, SecurityException {
202         checkCompatChangeOverridePermission();
203         mCompatConfig.removePackageOverrides(packageName);
204         killPackage(packageName);
205     }
206 
207     @Override
clearOverridesForTest(String packageName)208     public void clearOverridesForTest(String packageName)
209             throws RemoteException, SecurityException {
210         checkCompatChangeOverridePermission();
211         mCompatConfig.removePackageOverrides(packageName);
212     }
213 
214     @Override
clearOverride(long changeId, String packageName)215     public boolean clearOverride(long changeId, String packageName)
216             throws RemoteException, SecurityException {
217         checkCompatChangeOverridePermission();
218         boolean existed = mCompatConfig.removeOverride(changeId, packageName);
219         killPackage(packageName);
220         return existed;
221     }
222 
223     @Override
getAppConfig(ApplicationInfo appInfo)224     public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) {
225         checkCompatChangeReadAndLogPermission();
226         return mCompatConfig.getAppConfig(appInfo);
227     }
228 
229     @Override
listAllChanges()230     public CompatibilityChangeInfo[] listAllChanges() {
231         checkCompatChangeReadPermission();
232         return mCompatConfig.dumpChanges();
233     }
234 
235     @Override
listUIChanges()236     public CompatibilityChangeInfo[] listUIChanges() {
237         return Arrays.stream(listAllChanges()).filter(
238                 x -> isShownInUI(x)).toArray(CompatibilityChangeInfo[]::new);
239     }
240 
241     /**
242      * Check whether the change is known to the compat config.
243      *
244      * @return {@code true} if the change is known.
245      */
isKnownChangeId(long changeId)246     public boolean isKnownChangeId(long changeId) {
247         return mCompatConfig.isKnownChangeId(changeId);
248 
249     }
250 
251     /**
252      * Retrieves the set of disabled changes for a given app. Any change ID not in the returned
253      * array is by default enabled for the app.
254      *
255      * @param appInfo The app in question
256      * @return A sorted long array of change IDs. We use a primitive array to minimize memory
257      * footprint: Every app process will store this array statically so we aim to reduce
258      * overhead as much as possible.
259      */
getDisabledChanges(ApplicationInfo appInfo)260     public long[] getDisabledChanges(ApplicationInfo appInfo) {
261         return mCompatConfig.getDisabledChanges(appInfo);
262     }
263 
264     /**
265      * Look up a change ID by name.
266      *
267      * @param name Name of the change to look up
268      * @return The change ID, or {@code -1} if no change with that name exists.
269      */
lookupChangeId(String name)270     public long lookupChangeId(String name) {
271         return mCompatConfig.lookupChangeId(name);
272     }
273 
274     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)275     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
276         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
277         checkCompatChangeReadAndLogPermission();
278         mCompatConfig.dumpConfig(pw);
279     }
280 
281     @Override
getOverrideValidator()282     public IOverrideValidator getOverrideValidator() {
283         return mCompatConfig.getOverrideValidator();
284     }
285 
286     /**
287      * Clears information stored about events reported on behalf of an app.
288      * To be called once upon app start or end. A second call would be a no-op.
289      *
290      * @param appInfo the app to reset
291      */
resetReporting(ApplicationInfo appInfo)292     public void resetReporting(ApplicationInfo appInfo) {
293         mChangeReporter.resetReportedChanges(appInfo.uid);
294     }
295 
getApplicationInfo(String packageName, int userId)296     private ApplicationInfo getApplicationInfo(String packageName, int userId) {
297         return LocalServices.getService(PackageManagerInternal.class).getApplicationInfo(
298                 packageName, 0, userId, userId);
299     }
300 
reportChange(long changeId, int uid, int state)301     private void reportChange(long changeId, int uid, int state) {
302         mChangeReporter.reportChange(uid, changeId, state);
303     }
304 
killPackage(String packageName)305     private void killPackage(String packageName) {
306         int uid = LocalServices.getService(PackageManagerInternal.class).getPackageUid(packageName,
307                     0, UserHandle.myUserId());
308 
309         if (uid < 0) {
310             Slog.w(TAG, "Didn't find package " + packageName + " on device.");
311             return;
312         }
313 
314         Slog.d(TAG, "Killing package " + packageName + " (UID " + uid + ").");
315         killUid(UserHandle.getAppId(uid),
316                 UserHandle.USER_ALL, "PlatformCompat overrides");
317     }
318 
killUid(int appId, int userId, String reason)319     private void killUid(int appId, int userId, String reason) {
320         final long identity = Binder.clearCallingIdentity();
321         try {
322             IActivityManager am = ActivityManager.getService();
323             if (am != null) {
324                 try {
325                     am.killUid(appId, userId, reason);
326                 } catch (RemoteException e) {
327                     /* ignore - same process */
328                 }
329             }
330         } finally {
331             Binder.restoreCallingIdentity(identity);
332         }
333     }
334 
checkCompatChangeLogPermission()335     private void checkCompatChangeLogPermission() throws SecurityException {
336         if (mContext.checkCallingOrSelfPermission(LOG_COMPAT_CHANGE)
337                 != PERMISSION_GRANTED) {
338             throw new SecurityException("Cannot log compat change usage");
339         }
340     }
341 
checkCompatChangeReadPermission()342     private void checkCompatChangeReadPermission() throws SecurityException {
343         if (mContext.checkCallingOrSelfPermission(READ_COMPAT_CHANGE_CONFIG)
344                 != PERMISSION_GRANTED) {
345             throw new SecurityException("Cannot read compat change");
346         }
347     }
348 
checkCompatChangeOverridePermission()349     private void checkCompatChangeOverridePermission() throws SecurityException {
350         if (mContext.checkCallingOrSelfPermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
351                 != PERMISSION_GRANTED) {
352             throw new SecurityException("Cannot override compat change");
353         }
354     }
355 
checkCompatChangeReadAndLogPermission()356     private void checkCompatChangeReadAndLogPermission() throws SecurityException {
357         checkCompatChangeReadPermission();
358         checkCompatChangeLogPermission();
359     }
360 
isShownInUI(CompatibilityChangeInfo change)361     private boolean isShownInUI(CompatibilityChangeInfo change) {
362         if (change.getLoggingOnly()) {
363             return false;
364         }
365         if (change.getEnableAfterTargetSdk() > 0) {
366             if (change.getEnableAfterTargetSdk() < sMinTargetSdk
367                     || change.getEnableAfterTargetSdk() > sMaxTargetSdk) {
368                 return false;
369             }
370         }
371         return true;
372     }
373 }
374