1 /* 2 * Copyright (C) 2021 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 package com.android.server.am; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.compat.annotation.ChangeId; 21 import android.compat.annotation.Disabled; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.ComponentInfo; 26 import android.content.pm.PackageManager; 27 import android.content.pm.PackageManager.NameNotFoundException; 28 import android.content.pm.PackageManager.Property; 29 import android.content.pm.PackageManagerInternal; 30 import android.content.pm.ResolveInfo; 31 import android.content.pm.ServiceInfo; 32 import android.os.Binder; 33 import android.os.ServiceManager; 34 import android.os.UserHandle; 35 import android.text.TextUtils; 36 import android.util.ArrayMap; 37 import android.util.Log; 38 import android.util.Slog; 39 40 import com.android.internal.annotations.GuardedBy; 41 import com.android.internal.content.PackageMonitor; 42 import com.android.internal.os.BackgroundThread; 43 import com.android.server.FgThread; 44 import com.android.server.LocalServices; 45 import com.android.server.compat.PlatformCompat; 46 47 import java.io.PrintWriter; 48 import java.util.List; 49 import java.util.Objects; 50 import java.util.function.Supplier; 51 52 /** 53 * @deprecated This feature is no longer used. Delete this class. 54 * 55 * Also delete Intnt.(set|get)OriginalIntent. 56 */ 57 @Deprecated 58 public class ComponentAliasResolver { 59 private static final String TAG = "ComponentAliasResolver"; 60 private static final boolean DEBUG = true; 61 62 /** 63 * This flag has to be enabled for the "android" package to use component aliases. 64 */ 65 @ChangeId 66 @Disabled 67 public static final long USE_EXPERIMENTAL_COMPONENT_ALIAS = 196254758L; 68 69 private final Object mLock = new Object(); 70 private final ActivityManagerService mAm; 71 private final Context mContext; 72 73 @GuardedBy("mLock") 74 private boolean mEnabledByDeviceConfig; 75 76 @GuardedBy("mLock") 77 private boolean mEnabled; 78 79 @GuardedBy("mLock") 80 private String mOverrideString; 81 82 @GuardedBy("mLock") 83 private final ArrayMap<ComponentName, ComponentName> mFromTo = new ArrayMap<>(); 84 85 @GuardedBy("mLock") 86 private PlatformCompat mPlatformCompat; 87 88 private static final String OPT_IN_PROPERTY = "com.android.EXPERIMENTAL_COMPONENT_ALIAS_OPT_IN"; 89 90 private static final String ALIAS_FILTER_ACTION = 91 "com.android.intent.action.EXPERIMENTAL_IS_ALIAS"; 92 private static final String ALIAS_FILTER_ACTION_ALT = 93 "android.intent.action.EXPERIMENTAL_IS_ALIAS"; 94 private static final String META_DATA_ALIAS_TARGET = "alias_target"; 95 96 private static final int PACKAGE_QUERY_FLAGS = 97 PackageManager.MATCH_UNINSTALLED_PACKAGES 98 | PackageManager.MATCH_ANY_USER 99 | PackageManager.MATCH_DIRECT_BOOT_AWARE 100 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 101 | PackageManager.GET_META_DATA; 102 ComponentAliasResolver(ActivityManagerService service)103 public ComponentAliasResolver(ActivityManagerService service) { 104 mAm = service; 105 mContext = service.mContext; 106 } 107 isEnabled()108 public boolean isEnabled() { 109 synchronized (mLock) { 110 return mEnabled; 111 } 112 } 113 114 /** 115 * When there's any change to packages, we refresh all the aliases. 116 * TODO: In the production version, we should update only the changed package. 117 */ 118 final PackageMonitor mPackageMonitor = new PackageMonitor() { 119 @Override 120 public void onPackageModified(String packageName) { 121 refresh(); 122 } 123 124 @Override 125 public void onPackageAdded(String packageName, int uid) { 126 refresh(); 127 } 128 129 @Override 130 public void onPackageRemoved(String packageName, int uid) { 131 refresh(); 132 } 133 }; 134 135 /** 136 * Call this on systemRead(). 137 */ onSystemReady(boolean enabledByDeviceConfig, String overrides)138 public void onSystemReady(boolean enabledByDeviceConfig, String overrides) { 139 synchronized (mLock) { 140 mPlatformCompat = (PlatformCompat) ServiceManager.getService( 141 Context.PLATFORM_COMPAT_SERVICE); 142 } 143 if (DEBUG) Slog.d(TAG, "Compat listener set."); 144 update(enabledByDeviceConfig, overrides); 145 } 146 147 /** 148 * (Re-)loads aliases from <meta-data> and the device config override. 149 */ update(boolean enabledByDeviceConfig, String overrides)150 public void update(boolean enabledByDeviceConfig, String overrides) { 151 synchronized (mLock) { 152 if (mPlatformCompat == null) { 153 return; // System not ready. 154 } 155 // Never enable it. 156 final boolean enabled = false; 157 if (enabled != mEnabled) { 158 Slog.i(TAG, (enabled ? "Enabling" : "Disabling") + " component aliases..."); 159 FgThread.getHandler().post(() -> { 160 // Registering/unregistering a receiver internally takes the AM lock, but AM 161 // calls into this class while holding the AM lock. So do it on a handler to 162 // avoid deadlocks. 163 if (enabled) { 164 mPackageMonitor.register(mAm.mContext, UserHandle.ALL, 165 /* externalStorage= */ false, BackgroundThread.getHandler()); 166 } else { 167 mPackageMonitor.unregister(); 168 } 169 }); 170 } 171 mEnabled = enabled; 172 mEnabledByDeviceConfig = enabledByDeviceConfig; 173 mOverrideString = overrides; 174 175 if (mEnabled) { 176 refreshLocked(); 177 } else { 178 mFromTo.clear(); 179 } 180 } 181 } 182 refresh()183 private void refresh() { 184 synchronized (mLock) { 185 update(mEnabledByDeviceConfig, mOverrideString); 186 } 187 } 188 189 @GuardedBy("mLock") refreshLocked()190 private void refreshLocked() { 191 if (DEBUG) Slog.d(TAG, "Refreshing aliases..."); 192 mFromTo.clear(); 193 loadFromMetadataLocked(); 194 loadOverridesLocked(); 195 } 196 197 /** 198 * Scans all the "alias" components and inserts the from-to pairs to the map. 199 */ 200 @GuardedBy("mLock") loadFromMetadataLocked()201 private void loadFromMetadataLocked() { 202 if (DEBUG) Slog.d(TAG, "Scanning service aliases..."); 203 204 // PM.queryInetntXxx() doesn't support "OR" queries, so we search for 205 // both the com.android... action and android... action on by one. 206 // It's okay if a single component handles both actions because the resulting aliases 207 // will be stored in a map and duplicates will naturally be removed. 208 loadFromMetadataLockedInner(new Intent(ALIAS_FILTER_ACTION_ALT)); 209 loadFromMetadataLockedInner(new Intent(ALIAS_FILTER_ACTION)); 210 } 211 loadFromMetadataLockedInner(Intent i)212 private void loadFromMetadataLockedInner(Intent i) { 213 final List<ResolveInfo> services = mContext.getPackageManager().queryIntentServicesAsUser( 214 i, PACKAGE_QUERY_FLAGS, UserHandle.USER_SYSTEM); 215 216 extractAliasesLocked(services); 217 218 if (DEBUG) Slog.d(TAG, "Scanning receiver aliases..."); 219 final List<ResolveInfo> receivers = mContext.getPackageManager() 220 .queryBroadcastReceiversAsUser(i, PACKAGE_QUERY_FLAGS, UserHandle.USER_SYSTEM); 221 222 extractAliasesLocked(receivers); 223 224 // TODO: Scan for other component types as well. 225 } 226 227 /** 228 * Make sure a given package is opted into component alias, by having a 229 * "com.android.EXPERIMENTAL_COMPONENT_ALIAS_OPT_IN" property set to true in the manifest. 230 * 231 * The implementation isn't optimized -- in every call we scan the package's properties, 232 * even thought we're likely going to call it with the same packages multiple times. 233 * But that's okay since this feature is experimental, and this code path won't be called 234 * until explicitly enabled. 235 */ 236 @GuardedBy("mLock") isEnabledForPackageLocked(String packageName)237 private boolean isEnabledForPackageLocked(String packageName) { 238 boolean enabled = false; 239 try { 240 final Property p = mContext.getPackageManager().getProperty( 241 OPT_IN_PROPERTY, packageName); 242 enabled = p.getBoolean(); 243 } catch (NameNotFoundException e) { 244 } 245 if (!enabled) { 246 Slog.w(TAG, "USE_EXPERIMENTAL_COMPONENT_ALIAS not enabled for " + packageName); 247 } 248 return enabled; 249 } 250 251 /** 252 * Make sure an alias and its target are the same package, or, the target is in a "sub" package. 253 */ validateAlias(ComponentName from, ComponentName to)254 private static boolean validateAlias(ComponentName from, ComponentName to) { 255 final String fromPackage = from.getPackageName(); 256 final String toPackage = to.getPackageName(); 257 258 if (Objects.equals(fromPackage, toPackage)) { // Same package? 259 return true; 260 } 261 if (toPackage.startsWith(fromPackage + ".")) { // Prefix? 262 return true; 263 } 264 Slog.w(TAG, "Invalid alias: " 265 + from.flattenToShortString() + " -> " + to.flattenToShortString()); 266 return false; 267 } 268 269 @GuardedBy("mLock") validateAndAddAliasLocked(ComponentName from, ComponentName to)270 private void validateAndAddAliasLocked(ComponentName from, ComponentName to) { 271 if (DEBUG) { 272 Slog.d(TAG, 273 "" + from.flattenToShortString() + " -> " + to.flattenToShortString()); 274 } 275 if (!validateAlias(from, to)) { 276 return; 277 } 278 279 // Make sure both packages have 280 if (!isEnabledForPackageLocked(from.getPackageName()) 281 || !isEnabledForPackageLocked(to.getPackageName())) { 282 return; 283 } 284 285 mFromTo.put(from, to); 286 } 287 288 @GuardedBy("mLock") extractAliasesLocked(List<ResolveInfo> components)289 private void extractAliasesLocked(List<ResolveInfo> components) { 290 for (ResolveInfo ri : components) { 291 final ComponentInfo ci = ri.getComponentInfo(); 292 final ComponentName from = ci.getComponentName(); 293 final ComponentName to = unflatten(ci.metaData.getString(META_DATA_ALIAS_TARGET)); 294 if (to == null) { 295 continue; 296 } 297 validateAndAddAliasLocked(from, to); 298 } 299 } 300 301 /** 302 * Parses an "override" string and inserts the from-to pairs to the map. 303 * 304 * The format is: 305 * ALIAS-COMPONENT-1 ":" TARGET-COMPONENT-1 ( "," ALIAS-COMPONENT-2 ":" TARGET-COMPONENT-2 )* 306 */ 307 @GuardedBy("mLock") loadOverridesLocked()308 private void loadOverridesLocked() { 309 if (DEBUG) Slog.d(TAG, "Loading aliases overrides ..."); 310 for (String line : mOverrideString.split("\\,+")) { 311 final String[] fields = line.split("\\:+", 2); 312 if (TextUtils.isEmpty(fields[0])) { 313 continue; 314 } 315 final ComponentName from = unflatten(fields[0]); 316 if (from == null) { 317 continue; 318 } 319 320 if (fields.length == 1) { 321 if (DEBUG) Slog.d(TAG, "" + from.flattenToShortString() + " [removed]"); 322 mFromTo.remove(from); 323 } else { 324 final ComponentName to = unflatten(fields[1]); 325 if (to == null) { 326 continue; 327 } 328 329 validateAndAddAliasLocked(from, to); 330 } 331 } 332 } 333 unflatten(String name)334 private static ComponentName unflatten(String name) { 335 final ComponentName cn = ComponentName.unflattenFromString(name); 336 if (cn != null) { 337 return cn; 338 } 339 Slog.e(TAG, "Invalid component name detected: " + name); 340 return null; 341 } 342 343 /** 344 * Dump the aliases for dumpsys / bugrports. 345 */ dump(PrintWriter pw)346 public void dump(PrintWriter pw) { 347 synchronized (mLock) { 348 pw.println("ACTIVITY MANAGER COMPONENT-ALIAS (dumpsys activity component-alias)"); 349 pw.print(" Enabled: "); pw.println(mEnabled); 350 351 pw.println(" Aliases:"); 352 for (int i = 0; i < mFromTo.size(); i++) { 353 ComponentName from = mFromTo.keyAt(i); 354 ComponentName to = mFromTo.valueAt(i); 355 pw.print(" "); 356 pw.print(from.flattenToShortString()); 357 pw.print(" -> "); 358 pw.print(to.flattenToShortString()); 359 pw.println(); 360 } 361 pw.println(); 362 } 363 } 364 365 /** 366 * Contains alias resolution information. 367 */ 368 public static class Resolution<T> { 369 /** "From" component. Null if component alias is disabled. */ 370 @Nullable 371 public final T source; 372 373 /** "To" component. Null if component alias is disabled, or the source isn't an alias. */ 374 @Nullable 375 public final T resolved; 376 Resolution(T source, T resolved)377 public Resolution(T source, T resolved) { 378 this.source = source; 379 this.resolved = resolved; 380 } 381 382 @Nullable isAlias()383 public boolean isAlias() { 384 return this.resolved != null; 385 } 386 387 @Nullable getAlias()388 public T getAlias() { 389 return isAlias() ? source : null; 390 } 391 392 @Nullable getTarget()393 public T getTarget() { 394 return isAlias() ? resolved : null; 395 } 396 } 397 398 @NonNull resolveComponentAlias( @onNull Supplier<ComponentName> aliasSupplier)399 public Resolution<ComponentName> resolveComponentAlias( 400 @NonNull Supplier<ComponentName> aliasSupplier) { 401 final long identity = Binder.clearCallingIdentity(); 402 try { 403 synchronized (mLock) { 404 if (!mEnabled) { 405 return new Resolution<>(null, null); 406 } 407 408 final ComponentName alias = aliasSupplier.get(); 409 final ComponentName target = mFromTo.get(alias); 410 411 if (target != null) { 412 if (DEBUG) { 413 Exception stacktrace = null; 414 if (Log.isLoggable(TAG, Log.VERBOSE)) { 415 stacktrace = new RuntimeException("STACKTRACE"); 416 } 417 Slog.d(TAG, "Alias resolved: " + alias.flattenToShortString() 418 + " -> " + target.flattenToShortString(), stacktrace); 419 } 420 } 421 return new Resolution<>(alias, target); 422 } 423 } finally { 424 Binder.restoreCallingIdentity(identity); 425 } 426 } 427 428 @Nullable resolveService( @onNull Intent service, @Nullable String resolvedType, int packageFlags, int userId, int callingUid)429 public Resolution<ComponentName> resolveService( 430 @NonNull Intent service, @Nullable String resolvedType, 431 int packageFlags, int userId, int callingUid) { 432 Resolution<ComponentName> result = resolveComponentAlias(() -> { 433 PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); 434 435 ResolveInfo rInfo = pmi.resolveService(service, 436 resolvedType, packageFlags, userId, callingUid); 437 ServiceInfo sInfo = rInfo != null ? rInfo.serviceInfo : null; 438 if (sInfo == null) { 439 return null; // Service not found. 440 } 441 return new ComponentName(sInfo.applicationInfo.packageName, sInfo.name); 442 }); 443 444 // TODO: To make it consistent with resolveReceiver(), let's ensure the target service 445 // is resolvable, and if not, return null. 446 447 if (result != null && result.isAlias()) { 448 // It's an alias. Keep the original intent, and rewrite it. 449 service.setOriginalIntent(new Intent(service)); 450 451 service.setPackage(null); 452 service.setComponent(result.getTarget()); 453 } 454 return result; 455 } 456 457 @Nullable resolveReceiver(@onNull Intent intent, @NonNull ResolveInfo receiver, @Nullable String resolvedType, int packageFlags, int userId, int callingUid, boolean forSend)458 public Resolution<ResolveInfo> resolveReceiver(@NonNull Intent intent, 459 @NonNull ResolveInfo receiver, @Nullable String resolvedType, 460 int packageFlags, int userId, int callingUid, boolean forSend) { 461 // Resolve this alias. 462 final Resolution<ComponentName> resolution = resolveComponentAlias(() -> 463 receiver.activityInfo.getComponentName()); 464 final ComponentName target = resolution.getTarget(); 465 if (target == null) { 466 return new Resolution<>(receiver, null); // It's not an alias. 467 } 468 469 // Convert the target component name to a ResolveInfo. 470 471 final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); 472 473 // Rewrite the intent to search the target intent. 474 // - We don't actually rewrite the intent we deliver to the receiver here, which is what 475 // resolveService() does, because this intent many be send to other receivers as well. 476 // - But we don't have to do that here either, because the actual receiver component 477 // will be set in BroadcastQueue anyway, before delivering the intent to each receiver. 478 // - However, we're not able to set the original intent either, for the time being. 479 Intent i = new Intent(intent); 480 i.setPackage(null); 481 i.setComponent(resolution.getTarget()); 482 483 List<ResolveInfo> resolved = pmi.queryIntentReceivers( 484 i, resolvedType, packageFlags, callingUid, userId, forSend); 485 if (resolved == null || resolved.size() == 0) { 486 // Target component not found. 487 Slog.w(TAG, "Alias target " + target.flattenToShortString() + " not found"); 488 return null; 489 } 490 return new Resolution<>(receiver, resolved.get(0)); 491 } 492 } 493