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