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