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