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