1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.app; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.annotation.UserIdInt; 21 import android.content.Context.RegisterReceiverFlags; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.hardware.usb.UsbManager; 25 import android.media.AudioManager; 26 import android.net.ConnectivityManager; 27 import android.net.TetheringManager; 28 import android.net.nsd.NsdManager; 29 import android.net.wifi.WifiManager; 30 import android.net.wifi.p2p.WifiP2pManager; 31 import android.os.IpcDataCache; 32 import android.os.IpcDataCache.Config; 33 import android.os.ParcelFileDescriptor; 34 import android.os.UpdateLock; 35 import android.telephony.TelephonyManager; 36 import android.util.ArrayMap; 37 import android.util.IndentingPrintWriter; 38 import android.view.WindowManagerPolicyConstants; 39 40 import com.android.internal.annotations.GuardedBy; 41 import com.android.internal.annotations.VisibleForTesting; 42 import com.android.internal.util.ArrayUtils; 43 import com.android.internal.util.FastPrintWriter; 44 45 import java.io.FileOutputStream; 46 import java.io.PrintWriter; 47 48 /** @hide */ 49 public class BroadcastStickyCache { 50 51 @VisibleForTesting 52 public static final String[] STICKY_BROADCAST_ACTIONS = { 53 AudioManager.ACTION_HDMI_AUDIO_PLUG, 54 AudioManager.ACTION_HEADSET_PLUG, 55 AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED, 56 AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED, 57 AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION, 58 AudioManager.RINGER_MODE_CHANGED_ACTION, 59 ConnectivityManager.CONNECTIVITY_ACTION, 60 Intent.ACTION_BATTERY_CHANGED, 61 Intent.ACTION_DEVICE_STORAGE_FULL, 62 Intent.ACTION_DEVICE_STORAGE_LOW, 63 Intent.ACTION_SIM_STATE_CHANGED, 64 NsdManager.ACTION_NSD_STATE_CHANGED, 65 TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED, 66 TetheringManager.ACTION_TETHER_STATE_CHANGED, 67 UpdateLock.UPDATE_LOCK_CHANGED, 68 UsbManager.ACTION_USB_STATE, 69 WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED, 70 WifiManager.NETWORK_STATE_CHANGED_ACTION, 71 WifiManager.SUPPLICANT_STATE_CHANGED_ACTION, 72 WifiManager.WIFI_STATE_CHANGED_ACTION, 73 WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION, 74 WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED, 75 "android.net.conn.INET_CONDITION_ACTION" // ConnectivityManager.INET_CONDITION_ACTION 76 }; 77 78 @VisibleForTesting 79 public static final ArrayMap<String, String> sActionApiNameMap = new ArrayMap<>(); 80 81 @GuardedBy("BroadcastStickyCache.class") 82 private static final ArrayMap<String, IpcDataCache.Config> sActionConfigMap = new ArrayMap<>(); 83 84 @GuardedBy("BroadcastStickyCache.class") 85 private static final ArrayMap<StickyBroadcastFilter, IpcDataCache<Void, Intent>> 86 sFilterCacheMap = new ArrayMap<>(); 87 88 static { sActionApiNameMap.put(AudioManager.ACTION_HDMI_AUDIO_PLUG, "hdmi_audio_plug")89 sActionApiNameMap.put(AudioManager.ACTION_HDMI_AUDIO_PLUG, "hdmi_audio_plug"); sActionApiNameMap.put(AudioManager.ACTION_HEADSET_PLUG, "headset_plug")90 sActionApiNameMap.put(AudioManager.ACTION_HEADSET_PLUG, "headset_plug"); sActionApiNameMap.put(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED, "sco_audio_state_changed")91 sActionApiNameMap.put(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED, 92 "sco_audio_state_changed"); sActionApiNameMap.put(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED, "action_sco_audio_state_updated")93 sActionApiNameMap.put(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED, 94 "action_sco_audio_state_updated"); sActionApiNameMap.put(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION, "internal_ringer_mode_changed_action")95 sActionApiNameMap.put(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION, 96 "internal_ringer_mode_changed_action"); sActionApiNameMap.put(AudioManager.RINGER_MODE_CHANGED_ACTION, "ringer_mode_changed")97 sActionApiNameMap.put(AudioManager.RINGER_MODE_CHANGED_ACTION, 98 "ringer_mode_changed"); sActionApiNameMap.put(ConnectivityManager.CONNECTIVITY_ACTION, "connectivity_change")99 sActionApiNameMap.put(ConnectivityManager.CONNECTIVITY_ACTION, 100 "connectivity_change"); sActionApiNameMap.put(Intent.ACTION_BATTERY_CHANGED, "battery_changed")101 sActionApiNameMap.put(Intent.ACTION_BATTERY_CHANGED, "battery_changed"); sActionApiNameMap.put(Intent.ACTION_DEVICE_STORAGE_FULL, "device_storage_full")102 sActionApiNameMap.put(Intent.ACTION_DEVICE_STORAGE_FULL, "device_storage_full"); sActionApiNameMap.put(Intent.ACTION_DEVICE_STORAGE_LOW, "device_storage_low")103 sActionApiNameMap.put(Intent.ACTION_DEVICE_STORAGE_LOW, "device_storage_low"); sActionApiNameMap.put(Intent.ACTION_SIM_STATE_CHANGED, "sim_state_changed")104 sActionApiNameMap.put(Intent.ACTION_SIM_STATE_CHANGED, "sim_state_changed"); sActionApiNameMap.put(NsdManager.ACTION_NSD_STATE_CHANGED, "nsd_state_changed")105 sActionApiNameMap.put(NsdManager.ACTION_NSD_STATE_CHANGED, "nsd_state_changed"); sActionApiNameMap.put(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED, "service_providers_updated")106 sActionApiNameMap.put(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED, 107 "service_providers_updated"); sActionApiNameMap.put(TetheringManager.ACTION_TETHER_STATE_CHANGED, "tether_state_changed")108 sActionApiNameMap.put(TetheringManager.ACTION_TETHER_STATE_CHANGED, 109 "tether_state_changed"); sActionApiNameMap.put(UpdateLock.UPDATE_LOCK_CHANGED, "update_lock_changed")110 sActionApiNameMap.put(UpdateLock.UPDATE_LOCK_CHANGED, "update_lock_changed"); sActionApiNameMap.put(UsbManager.ACTION_USB_STATE, "usb_state")111 sActionApiNameMap.put(UsbManager.ACTION_USB_STATE, "usb_state"); sActionApiNameMap.put(WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED, "wifi_scan_availability_changed")112 sActionApiNameMap.put(WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED, 113 "wifi_scan_availability_changed"); sActionApiNameMap.put(WifiManager.NETWORK_STATE_CHANGED_ACTION, "network_state_change")114 sActionApiNameMap.put(WifiManager.NETWORK_STATE_CHANGED_ACTION, 115 "network_state_change"); sActionApiNameMap.put(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION, "supplicant_state_change")116 sActionApiNameMap.put(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION, 117 "supplicant_state_change"); sActionApiNameMap.put(WifiManager.WIFI_STATE_CHANGED_ACTION, "wifi_state_changed")118 sActionApiNameMap.put(WifiManager.WIFI_STATE_CHANGED_ACTION, "wifi_state_changed"); sActionApiNameMap.put( WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION, "wifi_p2p_state_changed")119 sActionApiNameMap.put( 120 WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION, "wifi_p2p_state_changed"); sActionApiNameMap.put( WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED, "hdmi_plugged")121 sActionApiNameMap.put( 122 WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED, "hdmi_plugged"); 123 sActionApiNameMap.put( 124 "android.net.conn.INET_CONDITION_ACTION", "inet_condition_action"); 125 } 126 127 /** 128 * Checks whether we can use caching for the given filter. 129 */ useCache(@ullable IntentFilter filter)130 public static boolean useCache(@Nullable IntentFilter filter) { 131 return Flags.useStickyBcastCache() 132 && filter != null 133 && filter.safeCountActions() == 1 134 && ArrayUtils.contains(STICKY_BROADCAST_ACTIONS, filter.getAction(0)); 135 } 136 invalidateCache(@onNull String action)137 public static void invalidateCache(@NonNull String action) { 138 if (!Flags.useStickyBcastCache() 139 || !ArrayUtils.contains(STICKY_BROADCAST_ACTIONS, action)) { 140 return; 141 } 142 IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, 143 sActionApiNameMap.get(action)); 144 } 145 invalidateAllCaches()146 public static void invalidateAllCaches() { 147 for (int i = sActionApiNameMap.size() - 1; i >= 0; i--) { 148 IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, 149 sActionApiNameMap.valueAt(i)); 150 } 151 } 152 153 /** 154 * Returns the cached {@link Intent} based on the filter, if exits otherwise 155 * fetches the value from the service. 156 */ 157 @Nullable getIntent( @onNull IApplicationThread applicationThread, @NonNull String mBasePackageName, @Nullable String attributionTag, @NonNull IntentFilter filter, @Nullable String broadcastPermission, @UserIdInt int userId, @RegisterReceiverFlags int flags)158 public static Intent getIntent( 159 @NonNull IApplicationThread applicationThread, 160 @NonNull String mBasePackageName, 161 @Nullable String attributionTag, 162 @NonNull IntentFilter filter, 163 @Nullable String broadcastPermission, 164 @UserIdInt int userId, 165 @RegisterReceiverFlags int flags) { 166 IpcDataCache<Void, Intent> intentDataCache; 167 168 synchronized (BroadcastStickyCache.class) { 169 intentDataCache = findIpcDataCache(filter); 170 171 if (intentDataCache == null) { 172 final String action = filter.getAction(0); 173 final StickyBroadcastFilter stickyBroadcastFilter = 174 new StickyBroadcastFilter(filter, action); 175 final Config config = getConfig(action); 176 177 intentDataCache = 178 new IpcDataCache<>(config, 179 (query) -> ActivityManager.getService().registerReceiverWithFeature( 180 applicationThread, 181 mBasePackageName, 182 attributionTag, 183 /* receiverId= */ "null", 184 /* receiver= */ null, 185 filter, 186 broadcastPermission, 187 userId, 188 flags)); 189 sFilterCacheMap.put(stickyBroadcastFilter, intentDataCache); 190 } 191 } 192 return intentDataCache.query(null); 193 } 194 195 @VisibleForTesting clearCacheForTest()196 public static void clearCacheForTest() { 197 synchronized (BroadcastStickyCache.class) { 198 sFilterCacheMap.clear(); 199 } 200 } 201 202 @Nullable 203 @GuardedBy("BroadcastStickyCache.class") findIpcDataCache( @onNull IntentFilter filter)204 private static IpcDataCache<Void, Intent> findIpcDataCache( 205 @NonNull IntentFilter filter) { 206 for (int i = sFilterCacheMap.size() - 1; i >= 0; i--) { 207 StickyBroadcastFilter existingFilter = sFilterCacheMap.keyAt(i); 208 if (filter.getAction(0).equals(existingFilter.action()) 209 && IntentFilter.filterEquals(existingFilter.filter(), filter)) { 210 return sFilterCacheMap.valueAt(i); 211 } 212 } 213 return null; 214 } 215 216 @NonNull 217 @GuardedBy("BroadcastStickyCache.class") getConfig(@onNull String action)218 private static IpcDataCache.Config getConfig(@NonNull String action) { 219 if (!sActionConfigMap.containsKey(action)) { 220 // We only need 1 entry per cache but just to be on the safer side we are choosing 32 221 // although we don't expect more than 1. 222 sActionConfigMap.put(action, 223 new Config(32, IpcDataCache.MODULE_SYSTEM, 224 sActionApiNameMap.get(action)).cacheNulls(true)); 225 } 226 227 return sActionConfigMap.get(action); 228 } 229 dumpCacheInfo(@onNull ParcelFileDescriptor pfd)230 public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd) { 231 if (!Flags.useStickyBcastCache()) { 232 return; 233 } 234 final PrintWriter pw = new FastPrintWriter(new FileOutputStream(pfd.getFileDescriptor())); 235 synchronized (BroadcastStickyCache.class) { 236 dumpCacheLocked(pw); 237 } 238 pw.flush(); 239 } 240 241 @GuardedBy("BroadcastStickyCache.class") dumpCacheLocked(@onNull PrintWriter pw)242 private static void dumpCacheLocked(@NonNull PrintWriter pw) { 243 final IndentingPrintWriter ipw = new IndentingPrintWriter( 244 pw, " " /* singleIndent */, " " /* prefix */); 245 ipw.println("Cached sticky broadcasts:"); 246 ipw.increaseIndent(); 247 final int count = sFilterCacheMap.size(); 248 if (count == 0) { 249 ipw.println("<empty>"); 250 } else { 251 for (int i = 0; i < count; ++i) { 252 final StickyBroadcastFilter stickyBroadcast = sFilterCacheMap.keyAt(i); 253 final IpcDataCache<Void, Intent> ipcDataCache = sFilterCacheMap.valueAt(i); 254 ipw.print("Entry #"); 255 ipw.print(i); 256 ipw.println(":"); 257 ipw.increaseIndent(); 258 ipw.print("action", stickyBroadcast.action).println(); 259 ipw.print("filter", stickyBroadcast.filter.toLongString()).println(); 260 ipcDataCache.dumpCacheEntries(pw); 261 ipw.decreaseIndent(); 262 } 263 } 264 ipw.decreaseIndent(); 265 } 266 267 @VisibleForTesting StickyBroadcastFilter(@onNull IntentFilter filter, @NonNull String action)268 private record StickyBroadcastFilter(@NonNull IntentFilter filter, @NonNull String action) { 269 } 270 } 271