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