Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a522d4ec authored by Makoto Onuki's avatar Makoto Onuki
Browse files

Component alias prototype support for broadcasts

Note, the tests don't fully pass. There may be bugs still.
But I might have found a problem with `am instrument` and looks like
I need to fix that first.

Bug: 196254758
Test: atest ComponentAliasTests
Change-Id: Ic0324dc739e931f1595dfc5cfdb1a968cd9fbd61
parent 87491ac7
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -376,6 +376,7 @@ import com.android.server.SystemServiceManager;
import com.android.server.ThreadPriorityBooster;
import com.android.server.UserspaceRebootLogger;
import com.android.server.Watchdog;
import com.android.server.am.ComponentAliasResolver.Resolution;
import com.android.server.am.LowMemDetector.MemFactor;
import com.android.server.appop.AppOpsService;
import com.android.server.compat.PlatformCompat;
@@ -12725,9 +12726,26 @@ public class ActivityManagerService extends IActivityManager.Stub
                    }
                }
            }
            // Replace the alias receivers with their targets.
            if (newReceivers != null) {
                for (int i = newReceivers.size() - 1; i >= 0; i--) {
                    final ResolveInfo ri = newReceivers.get(i);
                    final Resolution<ResolveInfo> resolution = mComponentAliasResolver
                            .resolveReceiver(intent, ri, resolvedType, pmFlags, user, callingUid);
                    if (resolution == null) {
                        // It was an alias, but the target was not found.
                        newReceivers.remove(i);
                        continue;
                    }
                    if (resolution.isAlias()) {
                        newReceivers.set(i, resolution.getTarget());
                    }
                }
            }
            if (newReceivers != null && newReceivers.size() == 0) {
                newReceivers = null;
            }
            if (receivers == null) {
                receivers = newReceivers;
            } else if (newReceivers != null) {
+114 −44
Original line number Diff line number Diff line
@@ -27,7 +27,9 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
@@ -38,6 +40,7 @@ import com.android.server.LocalServices;
import java.io.PrintWriter;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;

/**
 * Manages and handles component aliases, which is an experimental feature.
@@ -72,6 +75,13 @@ public class ComponentAliasResolver {
    private static final String ALIAS_FILTER_ACTION = "android.intent.action.EXPERIMENTAL_IS_ALIAS";
    private static final String META_DATA_ALIAS_TARGET = "alias_target";

    private static final int PACKAGE_QUERY_FLAGS =
            PackageManager.MATCH_UNINSTALLED_PACKAGES
                    | PackageManager.MATCH_ANY_USER
                    | PackageManager.MATCH_DIRECT_BOOT_AWARE
                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                    | PackageManager.GET_META_DATA;

    public ComponentAliasResolver(ActivityManagerService service) {
        mAm = service;
        mContext = service.mContext;
@@ -151,24 +161,29 @@ public class ComponentAliasResolver {
     */
    @GuardedBy("mLock")
    private void loadFromMetadataLocked() {
        if (DEBUG) Slog.d(TAG, "Scanning aliases...");
        if (DEBUG) Slog.d(TAG, "Scanning service aliases...");
        Intent i = new Intent(ALIAS_FILTER_ACTION);

        List<ResolveInfo> services = mContext.getPackageManager().queryIntentServicesAsUser(
                i,
                PackageManager.MATCH_UNINSTALLED_PACKAGES
                        | PackageManager.MATCH_ANY_USER
                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                        | PackageManager.GET_META_DATA,
                UserHandle.USER_SYSTEM);
        final List<ResolveInfo> services = mContext.getPackageManager().queryIntentServicesAsUser(
                i, PACKAGE_QUERY_FLAGS, UserHandle.USER_SYSTEM);

        extractAliases(services);

        if (DEBUG) Slog.d(TAG, "Scanning receiver aliases...");
        final List<ResolveInfo> receivers = mContext.getPackageManager()
                .queryBroadcastReceiversAsUser(i, PACKAGE_QUERY_FLAGS, UserHandle.USER_SYSTEM);

        extractAliases(receivers);

        // TODO: Scan for other component types as well.
    }

        for (ResolveInfo ri : services) {
    private void extractAliases(List<ResolveInfo> components) {
        for (ResolveInfo ri : components) {
            final ComponentInfo ci = ri.getComponentInfo();
            final ComponentName from = ci.getComponentName();
            final ComponentName to = ComponentName.unflattenFromString(
                    ci.metaData.getString(META_DATA_ALIAS_TARGET));
            if (!validateComponentName(to)) {
            final ComponentName to = unflatten(ci.metaData.getString(META_DATA_ALIAS_TARGET));
            if (to == null) {
                continue;
            }
            if (DEBUG) {
@@ -176,8 +191,6 @@ public class ComponentAliasResolver {
            }
            mFromTo.put(from, to);
        }

        // TODO: Scan for other component types as well.
    }

    /**
@@ -191,8 +204,11 @@ public class ComponentAliasResolver {
        if (DEBUG) Slog.d(TAG, "Loading aliases overrides ...");
        for (String line : mOverrideString.split("\\,+")) {
            final String[] fields = line.split("\\:+", 2);
            final ComponentName from = ComponentName.unflattenFromString(fields[0]);
            if (!validateComponentName(from)) {
            if (TextUtils.isEmpty(fields[0])) {
                continue;
            }
            final ComponentName from = unflatten(fields[0]);
            if (from == null) {
                continue;
            }

@@ -200,8 +216,8 @@ public class ComponentAliasResolver {
                if (DEBUG) Slog.d(TAG, "" + from.flattenToShortString() + " [removed]");
                mFromTo.remove(from);
            } else {
                final ComponentName to = ComponentName.unflattenFromString(fields[1]);
                if (!validateComponentName(to)) {
                final ComponentName to = unflatten(fields[1]);
                if (to == null) {
                    continue;
                }

@@ -214,12 +230,13 @@ public class ComponentAliasResolver {
        }
    }

    private boolean validateComponentName(ComponentName cn) {
    private ComponentName unflatten(String name) {
        final ComponentName cn = ComponentName.unflattenFromString(name);
        if (cn != null) {
            return true;
            return cn;
        }
        Slog.e(TAG, "Invalid component name detected: " + cn);
        return false;
        Slog.e(TAG, "Invalid component name detected: " + name);
        return null;
    }

    /**
@@ -277,10 +294,9 @@ public class ComponentAliasResolver {
        }
    }

    @Nullable
    public Resolution<ComponentName> resolveService(
            @NonNull Intent service, @Nullable String resolvedType,
            int packageFlags, int userId, int callingUid) {
    @NonNull
    public Resolution<ComponentName> resolveComponentAlias(
            @NonNull Supplier<ComponentName> aliasSupplier) {
        final long identity = Binder.clearCallingIdentity();
        try {
            synchronized (mLock) {
@@ -288,6 +304,31 @@ public class ComponentAliasResolver {
                    return new Resolution<>(null, null);
                }

                final ComponentName alias = aliasSupplier.get();
                final ComponentName target = mFromTo.get(alias);

                if (target != null) {
                    if (DEBUG) {
                        Exception stacktrace = null;
                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
                            stacktrace = new RuntimeException("STACKTRACE");
                        }
                        Slog.d(TAG, "Alias resolved: " + alias.flattenToShortString()
                                + " -> " + target.flattenToShortString(), stacktrace);
                    }
                }
                return new Resolution<>(alias, target);
            }
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    @Nullable
    public Resolution<ComponentName> resolveService(
            @NonNull Intent service, @Nullable String resolvedType,
            int packageFlags, int userId, int callingUid) {
        Resolution<ComponentName> result = resolveComponentAlias(() -> {
            PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);

            ResolveInfo rInfo = pmi.resolveService(service,
@@ -296,26 +337,55 @@ public class ComponentAliasResolver {
            if (sInfo == null) {
                return null; // Service not found.
            }
                final ComponentName alias =
                        new ComponentName(sInfo.applicationInfo.packageName, sInfo.name);
                final ComponentName target = mFromTo.get(alias);
            return new ComponentName(sInfo.applicationInfo.packageName, sInfo.name);
        });

                if (target != null) {
        // TODO: To make it consistent with resolveReceiver(), let's ensure the target service
        // is resolvable, and if not, return null.

        if (result != null && result.isAlias()) {
            // It's an alias. Keep the original intent, and rewrite it.
            service.setOriginalIntent(new Intent(service));

            service.setPackage(null);
                    service.setComponent(target);

                    if (DEBUG) {
                        Slog.d(TAG, "Alias resolved: " + alias.flattenToShortString()
                                + " -> " + target.flattenToShortString());
                    }
            service.setComponent(result.getTarget());
        }
                return new Resolution<>(alias, target);
            }
        } finally {
            Binder.restoreCallingIdentity(identity);
        return result;
    }

    @Nullable
    public Resolution<ResolveInfo> resolveReceiver(@NonNull Intent intent,
            @NonNull ResolveInfo receiver, @Nullable String resolvedType,
            int packageFlags, int userId, int callingUid) {
        // Resolve this alias.
        final Resolution<ComponentName> resolution = resolveComponentAlias(() ->
                receiver.activityInfo.getComponentName());
        final ComponentName target = resolution.getTarget();
        if (target == null) {
            return new Resolution<>(receiver, null); // It's not an alias.
        }

        // Convert the target component name to a ResolveInfo.

        final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);

        // Rewrite the intent to search the target intent.
        // - We don't actually rewrite the intent we deliver to the receiver here, which is what
        //  resolveService() does, because this intent many be send to other receivers as well.
        // - But we don't have to do that here either, because the actual receiver component
        //   will be set in BroadcastQueue anyway, before delivering the intent to each receiver.
        // - However, we're not able to set the original intent either, for the time being.
        Intent i = new Intent(intent);
        i.setPackage(null);
        i.setComponent(resolution.getTarget());

        List<ResolveInfo> resolved = pmi.queryIntentReceivers(i,
                resolvedType, packageFlags, callingUid, userId);
        if (resolved == null || resolved.size() == 0) {
            // Target component not found.
            Slog.w(TAG, "Alias target " + target.flattenToShortString() + " not found");
            return null;
        }
        return new Resolution<>(receiver, resolved.get(0));
    }
}
+31 −0
Original line number Diff line number Diff line
@@ -46,5 +46,36 @@
            <intent-filter><action android:name="android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
            <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_04" /></intent-filter>
        </service>

        <receiver android:name=".b.Alias00" android:exported="true" android:enabled="true" >
            <meta-data android:name="alias_target" android:value="android.content.componentalias.tests/android.content.componentalias.tests.b.Target00" />
            <intent-filter><action android:name="android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
            <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_00" /></intent-filter>
            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
        </receiver>
        <receiver android:name=".b.Alias01" android:exported="true" android:enabled="true" >
            <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.b.Target01" />
            <intent-filter><action android:name="android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
            <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_01" /></intent-filter>
            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
        </receiver>
        <receiver android:name=".b.Alias02" android:exported="true" android:enabled="true" >
            <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.b.Target02" />
            <intent-filter><action android:name="android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
            <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_02" /></intent-filter>
            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
        </receiver>
        <receiver android:name=".b.Alias03" android:exported="true" android:enabled="true" >
            <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.b.Target03" />
            <intent-filter><action android:name="android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
            <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_03" /></intent-filter>
            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
        </receiver>
        <receiver android:name=".b.Alias04" android:exported="true" android:enabled="true" >
            <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.b.Target04" />
            <intent-filter><action android:name="android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
            <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_04" /></intent-filter>
            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
        </receiver>
    </application>
</manifest>
+16 −0
Original line number Diff line number Diff line
@@ -27,5 +27,21 @@
        </service>
        <service android:name=".s.Target04" android:exported="true" android:enabled="true" >
        </service>

        <receiver android:name=".b.Target00" android:exported="true" android:enabled="true" >
            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
        </receiver>
        <receiver android:name=".b.Target01" android:exported="true" android:enabled="true" >
            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
        </receiver>
        <receiver android:name=".b.Target02" android:exported="true" android:enabled="true" >
            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
        </receiver>
        <receiver android:name=".b.Target03" android:exported="true" android:enabled="true" >
            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
        </receiver>
        <receiver android:name=".b.Target04" android:exported="true" android:enabled="true" >
            <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
        </receiver>
    </application>
</manifest>
+93 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.content.componentalias.tests;

import android.content.ComponentName;
import android.content.Context;
import android.provider.DeviceConfig;
import android.util.Log;

import androidx.test.InstrumentationRegistry;

import com.android.compatibility.common.util.DeviceConfigStateHelper;
import com.android.compatibility.common.util.ShellUtils;
import com.android.compatibility.common.util.TestUtils;

import org.junit.AfterClass;
import org.junit.Before;

import java.util.function.Consumer;

public class BaseComponentAliasTest {
    protected static final Context sContext = InstrumentationRegistry.getTargetContext();

    protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper(
            DeviceConfig.NAMESPACE_ACTIVITY_MANAGER);
    @Before
    public void enableComponentAlias() throws Exception {
        sDeviceConfig.set("component_alias_overrides", "");
        sDeviceConfig.set("enable_experimental_component_alias", "true");

        // Device config propagation happens on a handler, so we need to wait for AM to
        // actually set it.
        TestUtils.waitUntil("Wait until component alias is actually enabled", () -> {
            return ShellUtils.runShellCommand("dumpsys activity component-alias")
                    .indexOf("Enabled: true") > 0;
        });
    }

    @AfterClass
    public static void restoreDeviceConfig() throws Exception {
        sDeviceConfig.close();
    }

    protected static void log(String message) {
        Log.i(ComponentAliasTestCommon.TAG, "[" + sContext.getPackageName() + "] " + message);
    }

    /**
     * Defines a test target.
     */
    public static class Combo {
        public final ComponentName alias;
        public final ComponentName target;
        public final String action;

        public Combo(ComponentName alias, ComponentName target, String action) {
            this.alias = alias;
            this.target = target;
            this.action = action;
        }

        @Override
        public String toString() {
            return "Combo{"
                    + "alias=" + toString(alias)
                    + ", target=" + toString(target)
                    + ", action='" + action + '\''
                    + '}';
        }

        private static String toString(ComponentName cn) {
            return cn == null ? "[null]" : cn.flattenToShortString();
        }

        public void apply(Consumer<Combo> callback) {
            log("Testing for: " + this);
            callback.accept(this);
        }
    }
}
Loading