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

Commit 0825a072 authored by Mayank Dandwani's avatar Mayank Dandwani Committed by Android (Google) Code Review
Browse files

Merge "Cache Sticky broadcast Intents in the client side." into main

parents 768c9f7d 432146ce
Loading
Loading
Loading
Loading
+215 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.app;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context.RegisterReceiverFlags;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbManager;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.net.TetheringManager;
import android.net.nsd.NsdManager;
import android.net.wifi.WifiManager;
import android.net.wifi.p2p.WifiP2pManager;
import android.os.IpcDataCache;
import android.os.IpcDataCache.Config;
import android.os.UpdateLock;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.view.WindowManagerPolicyConstants;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;

/** @hide */
public class BroadcastStickyCache {

    @VisibleForTesting
    public static final String[] STICKY_BROADCAST_ACTIONS = {
            AudioManager.ACTION_HDMI_AUDIO_PLUG,
            AudioManager.ACTION_HEADSET_PLUG,
            AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED,
            AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED,
            AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
            AudioManager.RINGER_MODE_CHANGED_ACTION,
            ConnectivityManager.CONNECTIVITY_ACTION,
            Intent.ACTION_BATTERY_CHANGED,
            Intent.ACTION_DEVICE_STORAGE_FULL,
            Intent.ACTION_DEVICE_STORAGE_LOW,
            Intent.ACTION_SIM_STATE_CHANGED,
            NsdManager.ACTION_NSD_STATE_CHANGED,
            TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED,
            TetheringManager.ACTION_TETHER_STATE_CHANGED,
            UpdateLock.UPDATE_LOCK_CHANGED,
            UsbManager.ACTION_USB_STATE,
            WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED,
            WifiManager.NETWORK_STATE_CHANGED_ACTION,
            WifiManager.SUPPLICANT_STATE_CHANGED_ACTION,
            WifiManager.WIFI_STATE_CHANGED_ACTION,
            WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
            WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED,
            "android.net.conn.INET_CONDITION_ACTION" // ConnectivityManager.INET_CONDITION_ACTION
    };

    @VisibleForTesting
    public static final ArrayMap<String, String> sActionApiNameMap = new ArrayMap<>();

    private static final ArrayMap<String, IpcDataCache.Config> sActionConfigMap = new ArrayMap<>();

    private static final ArrayMap<StickyBroadcastFilter, IpcDataCache<Void, Intent>>
            sFilterCacheMap = new ArrayMap<>();

    static {
        sActionApiNameMap.put(AudioManager.ACTION_HDMI_AUDIO_PLUG, "hdmi_audio_plug");
        sActionApiNameMap.put(AudioManager.ACTION_HEADSET_PLUG, "headset_plug");
        sActionApiNameMap.put(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED,
                "sco_audio_state_changed");
        sActionApiNameMap.put(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED,
                "action_sco_audio_state_updated");
        sActionApiNameMap.put(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
                "internal_ringer_mode_changed_action");
        sActionApiNameMap.put(AudioManager.RINGER_MODE_CHANGED_ACTION,
                "ringer_mode_changed");
        sActionApiNameMap.put(ConnectivityManager.CONNECTIVITY_ACTION,
                "connectivity_change");
        sActionApiNameMap.put(Intent.ACTION_BATTERY_CHANGED, "battery_changed");
        sActionApiNameMap.put(Intent.ACTION_DEVICE_STORAGE_FULL, "device_storage_full");
        sActionApiNameMap.put(Intent.ACTION_DEVICE_STORAGE_LOW, "device_storage_low");
        sActionApiNameMap.put(Intent.ACTION_SIM_STATE_CHANGED, "sim_state_changed");
        sActionApiNameMap.put(NsdManager.ACTION_NSD_STATE_CHANGED, "nsd_state_changed");
        sActionApiNameMap.put(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED,
                "service_providers_updated");
        sActionApiNameMap.put(TetheringManager.ACTION_TETHER_STATE_CHANGED,
                "tether_state_changed");
        sActionApiNameMap.put(UpdateLock.UPDATE_LOCK_CHANGED, "update_lock_changed");
        sActionApiNameMap.put(UsbManager.ACTION_USB_STATE, "usb_state");
        sActionApiNameMap.put(WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED,
                "wifi_scan_availability_changed");
        sActionApiNameMap.put(WifiManager.NETWORK_STATE_CHANGED_ACTION,
                "network_state_change");
        sActionApiNameMap.put(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION,
                "supplicant_state_change");
        sActionApiNameMap.put(WifiManager.WIFI_STATE_CHANGED_ACTION, "wifi_state_changed");
        sActionApiNameMap.put(
                WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION, "wifi_p2p_state_changed");
        sActionApiNameMap.put(
                WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED, "hdmi_plugged");
        sActionApiNameMap.put(
                "android.net.conn.INET_CONDITION_ACTION", "inet_condition_action");
    }

    /**
     * Checks whether we can use caching for the given filter.
     */
    public static boolean useCache(@Nullable IntentFilter filter) {
        return Flags.useStickyBcastCache()
                && filter != null
                && filter.safeCountActions() == 1
                && ArrayUtils.contains(STICKY_BROADCAST_ACTIONS, filter.getAction(0));
    }

    public static void invalidateCache(@NonNull String action) {
        if (!Flags.useStickyBcastCache()
                || !ArrayUtils.contains(STICKY_BROADCAST_ACTIONS, action)) {
            return;
        }
        IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
                sActionApiNameMap.get(action));
    }

    public static void invalidateAllCaches() {
        for (int i = sActionApiNameMap.size() - 1; i >= 0; i--) {
            IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
                    sActionApiNameMap.valueAt(i));
        }
    }

    /**
     * Returns the cached {@link Intent} based on the filter, if exits otherwise
     * fetches the value from the service.
     */
    @Nullable
    public static Intent getIntent(
            @NonNull IApplicationThread applicationThread,
            @NonNull String mBasePackageName,
            @Nullable String attributionTag,
            @NonNull IntentFilter filter,
            @Nullable String broadcastPermission,
            @UserIdInt int userId,
            @RegisterReceiverFlags int flags) {
        IpcDataCache<Void, Intent> intentDataCache = findIpcDataCache(filter);

        if (intentDataCache == null) {
            final String action = filter.getAction(0);
            final StickyBroadcastFilter stickyBroadcastFilter =
                    new StickyBroadcastFilter(filter, action);
            final Config config = getConfig(action);

            intentDataCache =
                    new IpcDataCache<>(config,
                            (query) -> ActivityManager.getService().registerReceiverWithFeature(
                                    applicationThread,
                                    mBasePackageName,
                                    attributionTag,
                                    /* receiverId= */ "null",
                                    /* receiver= */ null,
                                    filter,
                                    broadcastPermission,
                                    userId,
                                    flags));
            sFilterCacheMap.put(stickyBroadcastFilter, intentDataCache);
        }
        return intentDataCache.query(null);
    }

    @VisibleForTesting
    public static void clearCacheForTest() {
        sFilterCacheMap.clear();
    }

    @Nullable
    private static IpcDataCache<Void, Intent> findIpcDataCache(
            @NonNull IntentFilter filter) {
        for (int i = sFilterCacheMap.size() - 1; i >= 0; i--) {
            StickyBroadcastFilter existingFilter = sFilterCacheMap.keyAt(i);
            if (filter.getAction(0).equals(existingFilter.action())
                    && IntentFilter.filterEquals(existingFilter.filter(), filter)) {
                return sFilterCacheMap.valueAt(i);
            }
        }
        return null;
    }

    @NonNull
    private static IpcDataCache.Config getConfig(@NonNull String action) {
        if (!sActionConfigMap.containsKey(action)) {
            // We only need 1 entry per cache but just to be on the safer side we are choosing 32
            // although we don't expect more than 1.
            sActionConfigMap.put(action,
                    new Config(32, IpcDataCache.MODULE_SYSTEM, sActionApiNameMap.get(action)));
        }

        return sActionConfigMap.get(action);
    }

    @VisibleForTesting
    private record StickyBroadcastFilter(@NonNull IntentFilter filter, @NonNull String action) {
    }
}
+17 −4
Original line number Diff line number Diff line
@@ -1922,10 +1922,23 @@ class ContextImpl extends Context {
            }
        }
        try {
            final Intent intent = ActivityManager.getService().registerReceiverWithFeature(
                    mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
                    AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId,
            final Intent intent;
            if (receiver == null && BroadcastStickyCache.useCache(filter)) {
                intent = BroadcastStickyCache.getIntent(
                        mMainThread.getApplicationThread(),
                        mBasePackageName,
                        getAttributionTag(),
                        filter,
                        broadcastPermission,
                        userId,
                        flags);
            } else {
                intent = ActivityManager.getService().registerReceiverWithFeature(
                        mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
                        AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission,
                        userId, flags);
            }

            if (intent != null) {
                intent.setExtrasClassLoader(getClassLoader());
                // TODO: determine at registration time if caller is
+4 −0
Original line number Diff line number Diff line
@@ -177,6 +177,10 @@
        {
            "file_patterns": ["(/|^)AppOpsManager.java"],
            "name": "CtsAppOpsTestCases"
        },
        {
            "file_patterns": ["(/|^)BroadcastStickyCache.java"],
            "name": "BroadcastUnitTests"
        }
    ]
}
+42 −2
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ import android.app.ApplicationExitInfo;
import android.app.ApplicationThreadConstants;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
import android.app.BroadcastStickyCache;
import android.app.IApplicationThread;
import android.app.compat.CompatChanges;
import android.appwidget.AppWidgetManager;
@@ -182,6 +183,13 @@ class BroadcastController {
    @GuardedBy("mService")
    final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();

    /**
     * If {@code false} invalidate the list of {@link android.os.IpcDataCache} present inside the
     * {@link BroadcastStickyCache} class.
     * The invalidation is required to start caching of the sticky broadcast in the client side.
     */
    private volatile boolean mAreStickyCachesInvalidated = false;

    /**
     * Resolver for broadcast intents to registered receivers.
     * Holds BroadcastFilter (subclass of IntentFilter).
@@ -288,6 +296,11 @@ class BroadcastController {
            IIntentReceiver receiver, IntentFilter filter, String permission,
            int userId, int flags) {
        mService.enforceNotIsolatedCaller("registerReceiver");

        if (!mAreStickyCachesInvalidated) {
            BroadcastStickyCache.invalidateAllCaches();
            mAreStickyCachesInvalidated = true;
        }
        ArrayList<StickyBroadcast> stickyBroadcasts = null;
        ProcessRecord callerApp = null;
        final boolean visibleToInstantApps =
@@ -700,6 +713,7 @@ class BroadcastController {
            String[] excludedPackages, int appOp, Bundle bOptions,
            boolean serialized, boolean sticky, int userId) {
        mService.enforceNotIsolatedCaller("broadcastIntent");
        final int result;

        synchronized (mService) {
            intent = verifyBroadcastLocked(intent);
@@ -722,7 +736,7 @@ class BroadcastController {

            final long origId = Binder.clearCallingIdentity();
            try {
                return broadcastIntentLocked(callerApp,
                result = broadcastIntentLocked(callerApp,
                        callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
                        intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
                        resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
@@ -733,6 +747,11 @@ class BroadcastController {
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            }
        }

        if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
            BroadcastStickyCache.invalidateCache(intent.getAction());
        }
        return result;
    }

    // Not the binder call surface
@@ -743,6 +762,7 @@ class BroadcastController {
            boolean serialized, boolean sticky, int userId,
            BackgroundStartPrivileges backgroundStartPrivileges,
            @Nullable int[] broadcastAllowList) {
        final int result;
        synchronized (mService) {
            intent = verifyBroadcastLocked(intent);

@@ -750,7 +770,7 @@ class BroadcastController {
            String[] requiredPermissions = requiredPermission == null ? null
                    : new String[] {requiredPermission};
            try {
                return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
                result =  broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
                        resultToApp, resultTo, resultCode, resultData, resultExtras,
                        requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
                        uid, realCallingUid, realCallingPid, userId,
@@ -760,6 +780,11 @@ class BroadcastController {
                Binder.restoreCallingIdentity(origId);
            }
        }

        if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
            BroadcastStickyCache.invalidateCache(intent.getAction());
        }
        return result;
    }

    @GuardedBy("mService")
@@ -1458,6 +1483,7 @@ class BroadcastController {
                    list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
                            callingUid, callerAppProcessState, resolvedType));
                }
                BroadcastStickyCache.invalidateCache(intent.getAction());
            }
        }

@@ -1724,6 +1750,7 @@ class BroadcastController {
            Slog.w(TAG, msg);
            throw new SecurityException(msg);
        }
        final ArrayList<String> changedStickyBroadcasts = new ArrayList<>();
        synchronized (mStickyBroadcasts) {
            ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
            if (stickies != null) {
@@ -1740,12 +1767,16 @@ class BroadcastController {
                    if (list.size() <= 0) {
                        stickies.remove(intent.getAction());
                    }
                    changedStickyBroadcasts.add(intent.getAction());
                }
                if (stickies.size() <= 0) {
                    mStickyBroadcasts.remove(userId);
                }
            }
        }
        for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
            BroadcastStickyCache.invalidateCache(changedStickyBroadcasts.get(i));
        }
    }

    void finishReceiver(IBinder caller, int resultCode, String resultData,
@@ -2124,9 +2155,18 @@ class BroadcastController {
    }

    void removeStickyBroadcasts(int userId) {
        final ArrayList<String> changedStickyBroadcasts = new ArrayList<>();
        synchronized (mStickyBroadcasts) {
            final ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
                    mStickyBroadcasts.get(userId);
            if (stickies != null) {
                changedStickyBroadcasts.addAll(stickies.keySet());
            }
            mStickyBroadcasts.remove(userId);
        }
        for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
            BroadcastStickyCache.invalidateCache(changedStickyBroadcasts.get(i));
        }
    }

    @NeverCompile
+33 −21
Original line number Diff line number Diff line
@@ -47,8 +47,10 @@ import static com.android.server.am.Flags.FLAG_AVOID_RESOLVING_TYPE;
import static com.android.server.am.ProcessList.NETWORK_STATE_BLOCK;
import static com.android.server.am.ProcessList.NETWORK_STATE_NO_CHANGE;
import static com.android.server.am.ProcessList.NETWORK_STATE_UNBLOCK;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -105,6 +107,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IProgressListener;
import android.os.IpcDataCache;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
@@ -959,6 +962,10 @@ public class ActivityManagerServiceTest {
    @Test
    @SuppressWarnings("GuardedBy")
    public void testBroadcastStickyIntent_verifyTypeNotResolved() throws Exception {
        MockitoSession mockitoSession =
                ExtendedMockito.mockitoSession().mockStatic(IpcDataCache.class).startMocking();

        try {
            final Intent intent = new Intent(TEST_ACTION1);
            final Uri uri = new Uri.Builder()
                    .scheme(SCHEME_CONTENT)
@@ -971,6 +978,8 @@ public class ActivityManagerServiceTest {
                    StickyBroadcast.create(intent, false, Process.myUid(), PROCESS_STATE_UNKNOWN,
                            TEST_MIME_TYPE));
            when(mContentResolver.getType(uri)).thenReturn(TEST_MIME_TYPE);
            ExtendedMockito.doNothing().when(
                    () -> IpcDataCache.invalidateCache(anyString(), anyString()));

            addUidRecord(TEST_UID, TEST_PACKAGE);
            final ProcessRecord procRecord = mAms.getProcessRecordLocked(TEST_PACKAGE, TEST_UID);
@@ -981,6 +990,9 @@ public class ActivityManagerServiceTest {
                    Context.RECEIVER_EXPORTED);
            assertNotNull(resultIntent);
            verify(mContentResolver, never()).getType(any());
        } finally {
            mockitoSession.finishMocking();
        }
    }

    @SuppressWarnings("GuardedBy")
Loading