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