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

Commit 7e7b1709 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Component alias prototype support for broadcasts"

parents 020fa6bc a522d4ec
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -377,6 +377,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;
@@ -12777,9 +12778,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