Loading core/java/android/app/BroadcastStickyCache.java 0 → 100644 +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) { } } core/java/android/app/ContextImpl.java +17 −4 Original line number Diff line number Diff line Loading @@ -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 Loading core/java/android/app/TEST_MAPPING +4 −0 Original line number Diff line number Diff line Loading @@ -177,6 +177,10 @@ { "file_patterns": ["(/|^)AppOpsManager.java"], "name": "CtsAppOpsTestCases" }, { "file_patterns": ["(/|^)BroadcastStickyCache.java"], "name": "BroadcastUnitTests" } ] } services/core/java/com/android/server/am/BroadcastController.java +42 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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). Loading Loading @@ -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 = Loading Loading @@ -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); Loading @@ -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, Loading @@ -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 Loading @@ -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); Loading @@ -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, Loading @@ -760,6 +780,11 @@ class BroadcastController { Binder.restoreCallingIdentity(origId); } } if (sticky && result == ActivityManager.BROADCAST_SUCCESS) { BroadcastStickyCache.invalidateCache(intent.getAction()); } return result; } @GuardedBy("mService") Loading Loading @@ -1458,6 +1483,7 @@ class BroadcastController { list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive, callingUid, callerAppProcessState, resolvedType)); } BroadcastStickyCache.invalidateCache(intent.getAction()); } } Loading Loading @@ -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) { Loading @@ -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, Loading Loading @@ -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 Loading services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java +33 −21 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) Loading @@ -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); Loading @@ -981,6 +990,9 @@ public class ActivityManagerServiceTest { Context.RECEIVER_EXPORTED); assertNotNull(resultIntent); verify(mContentResolver, never()).getType(any()); } finally { mockitoSession.finishMocking(); } } @SuppressWarnings("GuardedBy") Loading Loading
core/java/android/app/BroadcastStickyCache.java 0 → 100644 +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) { } }
core/java/android/app/ContextImpl.java +17 −4 Original line number Diff line number Diff line Loading @@ -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 Loading
core/java/android/app/TEST_MAPPING +4 −0 Original line number Diff line number Diff line Loading @@ -177,6 +177,10 @@ { "file_patterns": ["(/|^)AppOpsManager.java"], "name": "CtsAppOpsTestCases" }, { "file_patterns": ["(/|^)BroadcastStickyCache.java"], "name": "BroadcastUnitTests" } ] }
services/core/java/com/android/server/am/BroadcastController.java +42 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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). Loading Loading @@ -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 = Loading Loading @@ -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); Loading @@ -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, Loading @@ -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 Loading @@ -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); Loading @@ -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, Loading @@ -760,6 +780,11 @@ class BroadcastController { Binder.restoreCallingIdentity(origId); } } if (sticky && result == ActivityManager.BROADCAST_SUCCESS) { BroadcastStickyCache.invalidateCache(intent.getAction()); } return result; } @GuardedBy("mService") Loading Loading @@ -1458,6 +1483,7 @@ class BroadcastController { list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive, callingUid, callerAppProcessState, resolvedType)); } BroadcastStickyCache.invalidateCache(intent.getAction()); } } Loading Loading @@ -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) { Loading @@ -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, Loading Loading @@ -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 Loading
services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java +33 −21 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) Loading @@ -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); Loading @@ -981,6 +990,9 @@ public class ActivityManagerServiceTest { Context.RECEIVER_EXPORTED); assertNotNull(resultIntent); verify(mContentResolver, never()).getType(any()); } finally { mockitoSession.finishMocking(); } } @SuppressWarnings("GuardedBy") Loading