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