/* * Copyright (C) 2015 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 static android.app.ActivityOptions.BackgroundActivityStartMode; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.Disabled; import android.compat.annotation.EnabledSince; import android.content.BroadcastReceiver; import android.content.Intent; import android.content.IntentFilter; import android.os.Build; import android.os.Bundle; import android.os.BundleMerger; import android.os.PowerExemptionManager; import android.os.PowerExemptionManager.ReasonCode; import android.os.PowerExemptionManager.TempAllowListType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** * Helper class for building an options Bundle that can be used with * {@link android.content.Context#sendBroadcast(android.content.Intent) * Context.sendBroadcast(Intent)} and related methods. */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public class BroadcastOptions extends ComponentOptions { private @Flags int mFlags; private long mTemporaryAppAllowlistDuration; private @TempAllowListType int mTemporaryAppAllowlistType; private @ReasonCode int mTemporaryAppAllowlistReasonCode; private @Nullable String mTemporaryAppAllowlistReason; private int mMinManifestReceiverApiLevel = 0; private int mMaxManifestReceiverApiLevel = Build.VERSION_CODES.CUR_DEVELOPMENT; private String[] mRequireAllOfPermissions; private String[] mRequireNoneOfPermissions; private long mRequireCompatChangeId = CHANGE_INVALID; private long mIdForResponseEvent; private @DeliveryGroupPolicy int mDeliveryGroupPolicy; private @Nullable String mDeliveryGroupMatchingNamespaceFragment; private @Nullable String mDeliveryGroupMatchingKeyFragment; private @Nullable BundleMerger mDeliveryGroupExtrasMerger; private @Nullable IntentFilter mDeliveryGroupMatchingFilter; private @DeferralPolicy int mDeferralPolicy; /** @hide */ @IntDef(flag = true, prefix = { "FLAG_" }, value = { FLAG_DONT_SEND_TO_RESTRICTED_APPS, FLAG_ALLOW_BACKGROUND_ACTIVITY_STARTS, FLAG_REQUIRE_COMPAT_CHANGE_ENABLED, FLAG_IS_ALARM_BROADCAST, FLAG_SHARE_IDENTITY, FLAG_INTERACTIVE, }) @Retention(RetentionPolicy.SOURCE) public @interface Flags {} private static final int FLAG_DONT_SEND_TO_RESTRICTED_APPS = 1 << 0; private static final int FLAG_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1 << 1; private static final int FLAG_REQUIRE_COMPAT_CHANGE_ENABLED = 1 << 2; private static final int FLAG_IS_ALARM_BROADCAST = 1 << 3; private static final int FLAG_SHARE_IDENTITY = 1 << 4; private static final int FLAG_INTERACTIVE = 1 << 5; /** * Change ID which is invalid. * * @hide */ public static final long CHANGE_INVALID = Long.MIN_VALUE; /** * Change ID which is always enabled, for testing purposes. * * @hide */ @TestApi @ChangeId @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE) public static final long CHANGE_ALWAYS_ENABLED = 209888056L; /** * Change ID which is always disabled, for testing purposes. * * @hide */ @TestApi @ChangeId @Disabled public static final long CHANGE_ALWAYS_DISABLED = 210856463L; /** * Corresponds to {@link #mFlags}. */ private static final String KEY_FLAGS = "android:broadcast.flags"; /** * How long to temporarily put an app on the power allowlist when executing this broadcast * to it. */ private static final String KEY_TEMPORARY_APP_ALLOWLIST_DURATION = "android:broadcast.temporaryAppAllowlistDuration"; private static final String KEY_TEMPORARY_APP_ALLOWLIST_TYPE = "android:broadcast.temporaryAppAllowlistType"; private static final String KEY_TEMPORARY_APP_ALLOWLIST_REASON_CODE = "android:broadcast.temporaryAppAllowlistReasonCode"; private static final String KEY_TEMPORARY_APP_ALLOWLIST_REASON = "android:broadcast.temporaryAppAllowlistReason"; /** * Corresponds to {@link #setMinManifestReceiverApiLevel}. */ private static final String KEY_MIN_MANIFEST_RECEIVER_API_LEVEL = "android:broadcast.minManifestReceiverApiLevel"; /** * Corresponds to {@link #setMaxManifestReceiverApiLevel}. */ private static final String KEY_MAX_MANIFEST_RECEIVER_API_LEVEL = "android:broadcast.maxManifestReceiverApiLevel"; /** * Corresponds to {@link #setRequireAllOfPermissions} * @hide */ public static final String KEY_REQUIRE_ALL_OF_PERMISSIONS = "android:broadcast.requireAllOfPermissions"; /** * Corresponds to {@link #setRequireNoneOfPermissions} * @hide */ public static final String KEY_REQUIRE_NONE_OF_PERMISSIONS = "android:broadcast.requireNoneOfPermissions"; /** * Corresponds to {@link #setRequireCompatChange(long, boolean)} */ private static final String KEY_REQUIRE_COMPAT_CHANGE_ID = "android:broadcast.requireCompatChangeId"; /** * @hide * @deprecated Use {@link android.os.PowerExemptionManager# * TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED} instead. */ @Deprecated public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED = PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED; /** * @hide * @deprecated Use {@link android.os.PowerExemptionManager# * TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED} instead. */ @Deprecated public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED; /** * Corresponds to {@link #recordResponseEventWhileInBackground(long)}. */ private static final String KEY_ID_FOR_RESPONSE_EVENT = "android:broadcast.idForResponseEvent"; /** * Corresponds to {@link #setDeliveryGroupPolicy(int)}. */ private static final String KEY_DELIVERY_GROUP_POLICY = "android:broadcast.deliveryGroupPolicy"; /** * Corresponds to namespace fragment of {@link #setDeliveryGroupMatchingKey(String, String)}. */ private static final String KEY_DELIVERY_GROUP_NAMESPACE = "android:broadcast.deliveryGroupMatchingNamespace"; /** * Corresponds to key fragment of {@link #setDeliveryGroupMatchingKey(String, String)}. */ private static final String KEY_DELIVERY_GROUP_KEY = "android:broadcast.deliveryGroupMatchingKey"; /** * Corresponds to {@link #setDeliveryGroupExtrasMerger(BundleMerger)}. */ private static final String KEY_DELIVERY_GROUP_EXTRAS_MERGER = "android:broadcast.deliveryGroupExtrasMerger"; /** * Corresponds to {@link #setDeliveryGroupMatchingFilter(IntentFilter)}. */ private static final String KEY_DELIVERY_GROUP_MATCHING_FILTER = "android:broadcast.deliveryGroupMatchingFilter"; /** * Corresponds to {@link #setDeferralPolicy(int)} */ private static final String KEY_DEFERRAL_POLICY = "android:broadcast.deferralPolicy"; /** * The list of delivery group policies which specify how multiple broadcasts belonging to * the same delivery group has to be handled. * @hide */ @IntDef(prefix = { "DELIVERY_GROUP_POLICY_" }, value = { DELIVERY_GROUP_POLICY_ALL, DELIVERY_GROUP_POLICY_MOST_RECENT, DELIVERY_GROUP_POLICY_MERGED, }) @Retention(RetentionPolicy.SOURCE) public @interface DeliveryGroupPolicy {} /** * Delivery group policy that indicates that all the broadcasts in the delivery group * need to be delivered as is. */ public static final int DELIVERY_GROUP_POLICY_ALL = 0; /** * Delivery group policy that indicates that only the most recent broadcast in the delivery * group need to be delivered and the rest can be dropped. */ public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1; /** * Delivery group policy that indicates that the extras data from the broadcasts in the * delivery group need to be merged into a single broadcast and the rest can be dropped. * * @hide */ public static final int DELIVERY_GROUP_POLICY_MERGED = 2; /** {@hide} */ @IntDef(prefix = { "DEFERRAL_POLICY_" }, value = { DEFERRAL_POLICY_DEFAULT, DEFERRAL_POLICY_NONE, DEFERRAL_POLICY_UNTIL_ACTIVE, }) @Retention(RetentionPolicy.SOURCE) public @interface DeferralPolicy {} /** * Deferral policy that indicates no desire has been expressed, and that the * system should use a reasonable default behavior. */ public static final int DEFERRAL_POLICY_DEFAULT = 0; /** * Deferral policy that indicates a strong desire that no receiver of this * broadcast should be deferred. */ public static final int DEFERRAL_POLICY_NONE = 1; /** * Deferral policy that indicates a strong desire that each receiver of this * broadcast should be deferred until that receiver's process is in an * active (non-cached) state. Whether an app's process state is considered * active is independent of its standby bucket. * <p> * This policy only applies to runtime registered receivers of a broadcast, * and does not apply to ordered broadcasts, alarm broadcasts, interactive * broadcasts, or manifest broadcasts. * <p> * This policy means that a runtime registered receiver will not typically * execute until that receiver's process is brought to an active state by * some other action, such as a job, alarm, or service binding. As a result, * the receiver may be delayed indefinitely. * <p> * When this policy is set on an unordered broadcast with a completion * callback, the completion callback will run once all eligible processes * have finished receiving the broadcast. Processes in inactive process * state are not considered eligible and may not receive the broadcast prior * to the completion callback. */ public static final int DEFERRAL_POLICY_UNTIL_ACTIVE = 2; /** * Creates a basic {@link BroadcastOptions} with no options initially set. * * @return an instance of {@code BroadcastOptions} against which options can be set */ public static @NonNull BroadcastOptions makeBasic() { BroadcastOptions opts = new BroadcastOptions(); return opts; } /** @hide */ @TestApi public BroadcastOptions() { super(); resetTemporaryAppAllowlist(); } /** @hide */ @TestApi public BroadcastOptions(@NonNull Bundle opts) { super(opts); // Match the logic in toBundle(). mFlags = opts.getInt(KEY_FLAGS, 0); if (opts.containsKey(KEY_TEMPORARY_APP_ALLOWLIST_DURATION)) { mTemporaryAppAllowlistDuration = opts.getLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION); mTemporaryAppAllowlistType = opts.getInt(KEY_TEMPORARY_APP_ALLOWLIST_TYPE); mTemporaryAppAllowlistReasonCode = opts.getInt(KEY_TEMPORARY_APP_ALLOWLIST_REASON_CODE, PowerExemptionManager.REASON_UNKNOWN); mTemporaryAppAllowlistReason = opts.getString(KEY_TEMPORARY_APP_ALLOWLIST_REASON); } else { resetTemporaryAppAllowlist(); } mMinManifestReceiverApiLevel = opts.getInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, 0); mMaxManifestReceiverApiLevel = opts.getInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL, Build.VERSION_CODES.CUR_DEVELOPMENT); mRequireAllOfPermissions = opts.getStringArray(KEY_REQUIRE_ALL_OF_PERMISSIONS); mRequireNoneOfPermissions = opts.getStringArray(KEY_REQUIRE_NONE_OF_PERMISSIONS); mRequireCompatChangeId = opts.getLong(KEY_REQUIRE_COMPAT_CHANGE_ID, CHANGE_INVALID); mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT); mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY, DELIVERY_GROUP_POLICY_ALL); mDeliveryGroupMatchingNamespaceFragment = opts.getString(KEY_DELIVERY_GROUP_NAMESPACE); mDeliveryGroupMatchingKeyFragment = opts.getString(KEY_DELIVERY_GROUP_KEY); mDeliveryGroupExtrasMerger = opts.getParcelable(KEY_DELIVERY_GROUP_EXTRAS_MERGER, BundleMerger.class); mDeliveryGroupMatchingFilter = opts.getParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER, IntentFilter.class); mDeferralPolicy = opts.getInt(KEY_DEFERRAL_POLICY, DEFERRAL_POLICY_DEFAULT); } /** @hide */ @NonNull public static BroadcastOptions makeWithDeferUntilActive(boolean deferUntilActive) { final BroadcastOptions opts = BroadcastOptions.makeBasic(); if (deferUntilActive) { opts.setDeferralPolicy(DEFERRAL_POLICY_UNTIL_ACTIVE); } return opts; } /** * Set a duration for which the system should temporary place an application on the * power allowlist when this broadcast is being delivered to it. * @param duration The duration in milliseconds; 0 means to not place on allowlist. * @deprecated use {@link #setTemporaryAppAllowlist(long, int, int, String)} instead. * @hide */ @Deprecated @SystemApi @RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long duration) { setTemporaryAppAllowlist(duration, PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, PowerExemptionManager.REASON_UNKNOWN, null); } /** * Set a duration for which the system should temporary place an application on the * power allowlist when this broadcast is being delivered to it, specify the temp allowlist * type. * @hide * * @param duration the duration in milliseconds. * 0 means to not place on allowlist, and clears previous call to this method. * @param type one of {@link TempAllowListType}. * {@link PowerExemptionManager#TEMPORARY_ALLOW_LIST_TYPE_NONE} means * to not place on allowlist, and clears previous call to this method. * @param reasonCode one of {@link ReasonCode}, use * {@link PowerExemptionManager#REASON_UNKNOWN} if not sure. * @param reason A human-readable reason explaining why the app is temp allowlisted. Only * used for logging purposes. Could be null or empty string. */ @SystemApi @RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppAllowlist(long duration, @TempAllowListType int type, @ReasonCode int reasonCode, @Nullable String reason) { mTemporaryAppAllowlistDuration = duration; mTemporaryAppAllowlistType = type; mTemporaryAppAllowlistReasonCode = reasonCode; mTemporaryAppAllowlistReason = reason; if (!isTemporaryAppAllowlistSet()) { resetTemporaryAppAllowlist(); } } private boolean isTemporaryAppAllowlistSet() { return mTemporaryAppAllowlistDuration > 0 && mTemporaryAppAllowlistType != PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE; } private void resetTemporaryAppAllowlist() { mTemporaryAppAllowlistDuration = 0; mTemporaryAppAllowlistType = PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE; mTemporaryAppAllowlistReasonCode = PowerExemptionManager.REASON_UNKNOWN; mTemporaryAppAllowlistReason = null; } /** * Return {@link #setTemporaryAppAllowlist}. * @hide */ @TestApi public long getTemporaryAppAllowlistDuration() { return mTemporaryAppAllowlistDuration; } /** * Return {@link #mTemporaryAppAllowlistType}. * @hide */ @TestApi public @TempAllowListType int getTemporaryAppAllowlistType() { return mTemporaryAppAllowlistType; } /** * Return {@link #mTemporaryAppAllowlistReasonCode}. * @hide */ @TestApi public @ReasonCode int getTemporaryAppAllowlistReasonCode() { return mTemporaryAppAllowlistReasonCode; } /** * Return {@link #mTemporaryAppAllowlistReason}. * @hide */ @TestApi public @Nullable String getTemporaryAppAllowlistReason() { return mTemporaryAppAllowlistReason; } /** * Set the minimum target API level of receivers of the broadcast. If an application * is targeting an API level less than this, the broadcast will not be delivered to * them. This only applies to receivers declared in the app's AndroidManifest.xml. * * @deprecated to give developers the most flexibility during beta releases, * we strongly encourage using {@link ChangeId} instead of * target SDK checks; callers should use * {@link #setRequireCompatChange(long, boolean)} instead, * possibly combined with * {@link Intent#FLAG_RECEIVER_REGISTERED_ONLY}. * @hide */ @Deprecated public void setMinManifestReceiverApiLevel(int apiLevel) { mMinManifestReceiverApiLevel = apiLevel; } /** * Return {@link #setMinManifestReceiverApiLevel}. * * @deprecated to give developers the most flexibility during beta releases, * we strongly encourage using {@link ChangeId} instead of * target SDK checks; callers should use * {@link #setRequireCompatChange(long, boolean)} instead, * possibly combined with * {@link Intent#FLAG_RECEIVER_REGISTERED_ONLY}. * @hide */ @Deprecated public int getMinManifestReceiverApiLevel() { return mMinManifestReceiverApiLevel; } /** * Set the maximum target API level of receivers of the broadcast. If an application * is targeting an API level greater than this, the broadcast will not be delivered to * them. This only applies to receivers declared in the app's AndroidManifest.xml. * * @deprecated to give developers the most flexibility during beta releases, * we strongly encourage using {@link ChangeId} instead of * target SDK checks; callers should use * {@link #setRequireCompatChange(long, boolean)} instead, * possibly combined with * {@link Intent#FLAG_RECEIVER_REGISTERED_ONLY}. * @hide */ @TestApi @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @Deprecated public void setMaxManifestReceiverApiLevel(int apiLevel) { mMaxManifestReceiverApiLevel = apiLevel; } /** * Return {@link #setMaxManifestReceiverApiLevel}. * * @deprecated to give developers the most flexibility during beta releases, * we strongly encourage using {@link ChangeId} instead of * target SDK checks; callers should use * {@link #setRequireCompatChange(long, boolean)} instead, * possibly combined with * {@link Intent#FLAG_RECEIVER_REGISTERED_ONLY}. * @hide */ @TestApi @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @Deprecated public int getMaxManifestReceiverApiLevel() { return mMaxManifestReceiverApiLevel; } /** * Sets whether pending intent can be sent for an application with background restrictions * @param dontSendToRestrictedApps if true, pending intent will not be sent for an application * with background restrictions. Default value is {@code false} * @hide */ @SystemApi public void setDontSendToRestrictedApps(boolean dontSendToRestrictedApps) { if (dontSendToRestrictedApps) { mFlags |= FLAG_DONT_SEND_TO_RESTRICTED_APPS; } else { mFlags &= ~FLAG_DONT_SEND_TO_RESTRICTED_APPS; } } /** * @hide * @return #setDontSendToRestrictedApps */ public boolean isDontSendToRestrictedApps() { return (mFlags & FLAG_DONT_SEND_TO_RESTRICTED_APPS) != 0; } /** * Sets the process will be able to start activities from background for the duration of * the broadcast dispatch. Default value is {@code false} * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean allowBackgroundActivityStarts) { if (allowBackgroundActivityStarts) { mFlags |= FLAG_ALLOW_BACKGROUND_ACTIVITY_STARTS; } else { mFlags &= ~FLAG_ALLOW_BACKGROUND_ACTIVITY_STARTS; } } /** * @hide * @return #setAllowBackgroundActivityStarts */ @Deprecated public boolean allowsBackgroundActivityStarts() { return (mFlags & FLAG_ALLOW_BACKGROUND_ACTIVITY_STARTS) != 0; } /** * Use this to configure a broadcast to be sent to apps that hold all permissions in * the list. This is only for use with the {@link Context#sendBroadcast(Intent intent, * @Nullable String receiverPermission, @Nullable Bundle options)}. * * <p> If both {@link #setRequireAllOfPermissions(String[])} and * {@link #setRequireNoneOfPermissions(String[])} are used, then receivers must have all of the * permissions set by {@link #setRequireAllOfPermissions(String[])}, and none of the * permissions set by {@link #setRequireNoneOfPermissions(String[])} to get the broadcast. * * @param requiredPermissions a list of Strings of permission the receiver must have. Set to * null or an empty array to clear any previously set value. * @hide */ @SystemApi public void setRequireAllOfPermissions(@Nullable String[] requiredPermissions) { mRequireAllOfPermissions = requiredPermissions; } /** * Use this to configure a broadcast to be sent to apps that don't hold any permissions in * list. This is only for use with the {@link Context#sendBroadcast(Intent intent, * @Nullable String receiverPermission, @Nullable Bundle options)}. * * <p> If both {@link #setRequireAllOfPermissions(String[])} and * {@link #setRequireNoneOfPermissions(String[])} are used, then receivers must have all of the * permissions set by {@link #setRequireAllOfPermissions(String[])}, and none of the * permissions set by {@link #setRequireNoneOfPermissions(String[])} to get the broadcast. * * @param excludedPermissions a list of Strings of permission the receiver must not have. Set to * null or an empty array to clear any previously set value. * @hide */ @SystemApi public void setRequireNoneOfPermissions(@Nullable String[] excludedPermissions) { mRequireNoneOfPermissions = excludedPermissions; } /** * When set, this broadcast will only be delivered to apps which have the * given {@link ChangeId} in the given state. * <p> * Each {@link BroadcastOptions} instance supports only a single * {@link ChangeId} requirement, so any subsequent calls will override any * previously defined requirement. * <p> * This requirement applies to both manifest registered and runtime * registered receivers. * @hide * * @param changeId the {@link ChangeId} to inspect * @param enabled the required enabled state of the inspected * {@link ChangeId} for this broadcast to be delivered * @see CompatChanges#isChangeEnabled * @see #clearRequireCompatChange() */ @SystemApi public void setRequireCompatChange(long changeId, boolean enabled) { mRequireCompatChangeId = changeId; if (enabled) { mFlags |= FLAG_REQUIRE_COMPAT_CHANGE_ENABLED; } else { mFlags &= ~FLAG_REQUIRE_COMPAT_CHANGE_ENABLED; } } /** * Clear any previously defined requirement for this broadcast requested via * {@link #setRequireCompatChange(long, boolean)}. * @hide */ @SystemApi public void clearRequireCompatChange() { setRequireCompatChange(CHANGE_INVALID, true); } /** * When set, this broadcast will be understood as having originated from an * alarm going off. Only the OS itself can use this option; uses by other * senders will be ignored. * @hide * * @param senderIsAlarm Whether the broadcast is alarm-triggered. */ public void setAlarmBroadcast(boolean senderIsAlarm) { if (senderIsAlarm) { mFlags |= FLAG_IS_ALARM_BROADCAST; } else { mFlags &= ~FLAG_IS_ALARM_BROADCAST; } } /** * Did this broadcast originate from an alarm triggering? * @return true if this broadcast is an alarm message, false otherwise * @hide */ public boolean isAlarmBroadcast() { return (mFlags & FLAG_IS_ALARM_BROADCAST) != 0; } /** * Sets whether the identity of the broadcasting app should be shared with all receivers * that will receive this broadcast. * * <p>Use this option when broadcasting to a receiver that needs to know the identity of the * broadcaster; with this set to {@code true}, the receiver will have access to the broadcasting * app's package name and uid. * * <p>Defaults to {@code false} if not set. * * @param shareIdentityEnabled whether the broadcasting app's identity should be shared with the * receiver * @return {@code this} {@link BroadcastOptions} instance * @see BroadcastReceiver#getSentFromUid() * @see BroadcastReceiver#getSentFromPackage() */ public @NonNull BroadcastOptions setShareIdentityEnabled(boolean shareIdentityEnabled) { if (shareIdentityEnabled) { mFlags |= FLAG_SHARE_IDENTITY; } else { mFlags &= ~FLAG_SHARE_IDENTITY; } return this; } /** * Returns whether the broadcasting app has opted-in to sharing its identity with the receiver. * * @return {@code true} if the broadcasting app has opted in to sharing its identity * @see #setShareIdentityEnabled(boolean) * @see BroadcastReceiver#getSentFromUid() * @see BroadcastReceiver#getSentFromPackage() */ public boolean isShareIdentityEnabled() { return (mFlags & FLAG_SHARE_IDENTITY) != 0; } /** * Did this broadcast originate from a push message from the server? * * @return true if this broadcast is a push message, false otherwise. * @hide */ public boolean isPushMessagingBroadcast() { return mTemporaryAppAllowlistReasonCode == PowerExemptionManager.REASON_PUSH_MESSAGING; } /** * Did this broadcast originate from a push message from the server which was over the allowed * quota? * * @return true if this broadcast is a push message over quota, false otherwise. * @hide */ public boolean isPushMessagingOverQuotaBroadcast() { return mTemporaryAppAllowlistReasonCode == PowerExemptionManager.REASON_PUSH_MESSAGING_OVER_QUOTA; } /** {@hide} */ public long getRequireCompatChangeId() { return mRequireCompatChangeId; } /** * Test if the given app meets the {@link ChangeId} state required by this * broadcast, if any. * * @hide */ @TestApi @android.ravenwood.annotation.RavenwoodThrow public boolean testRequireCompatChange(int uid) { if (mRequireCompatChangeId != CHANGE_INVALID) { final boolean requireEnabled = (mFlags & FLAG_REQUIRE_COMPAT_CHANGE_ENABLED) != 0; return CompatChanges.isChangeEnabled(mRequireCompatChangeId, uid) == requireEnabled; } else { return true; } } /** * Sets whether events (such as posting a notification) originating from an app after it * receives the broadcast while in background should be recorded as responses to the broadcast. * * <p> Note that this will only be considered when sending explicit broadcast intents. * * @param id ID to be used for the response events corresponding to this broadcast. If the * value is {@code 0} (default), then response events will not be recorded. Otherwise, * they will be recorded with the ID provided. * * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from = 0) long id) { mIdForResponseEvent = id; } /** @hide */ @IntRange(from = 0) public long getIdForResponseEvent() { return mIdForResponseEvent; } /** * Sets deferral policy for this broadcast that specifies how this broadcast * can be deferred for delivery at some future point. */ public @NonNull BroadcastOptions setDeferralPolicy(@DeferralPolicy int deferralPolicy) { mDeferralPolicy = deferralPolicy; return this; } /** * Gets deferral policy for this broadcast that specifies how this broadcast * can be deferred for delivery at some future point. */ public @DeferralPolicy int getDeferralPolicy() { return mDeferralPolicy; } /** * Clears any deferral policy for this broadcast that specifies how this * broadcast can be deferred for delivery at some future point. */ public void clearDeferralPolicy() { mDeferralPolicy = DEFERRAL_POLICY_DEFAULT; } /** * Set delivery group policy for this broadcast to specify how multiple broadcasts belonging to * the same delivery group has to be handled. */ @NonNull public BroadcastOptions setDeliveryGroupPolicy(@DeliveryGroupPolicy int policy) { mDeliveryGroupPolicy = policy; return this; } /** * Get the delivery group policy for this broadcast that specifies how multiple broadcasts * belonging to the same delivery group has to be handled. */ public @DeliveryGroupPolicy int getDeliveryGroupPolicy() { return mDeliveryGroupPolicy; } /** * Clears any previously set delivery group policies using * {@link #setDeliveryGroupMatchingKey(String, String)} and resets the delivery group policy to * the default value ({@link #DELIVERY_GROUP_POLICY_ALL}). */ public void clearDeliveryGroupPolicy() { mDeliveryGroupPolicy = DELIVERY_GROUP_POLICY_ALL; } /** * Set namespace and key to identify the delivery group that this broadcast belongs to. * * <p> If {@code namespace} and {@code key} are specified, then another broadcast will be * considered to be in the same delivery group as this iff it has the same {@code namespace} * and {@code key}. * * <p> If not matching key using this API then by default * {@link Intent#filterEquals(Intent)} will be used to identify the delivery group. */ @NonNull public BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String namespace, @NonNull String key) { mDeliveryGroupMatchingNamespaceFragment = Objects.requireNonNull(namespace); mDeliveryGroupMatchingKeyFragment = Objects.requireNonNull(key); return this; } /** * Return the namespace and key that is used to identify the delivery group that this * broadcast belongs to. * * @return the delivery group namespace and key that was previously set using * {@link #setDeliveryGroupMatchingKey(String, String)}, concatenated with a {@code :}. */ @Nullable public String getDeliveryGroupMatchingKey() { if (mDeliveryGroupMatchingNamespaceFragment == null || mDeliveryGroupMatchingKeyFragment == null) { return null; } return String.join(":", mDeliveryGroupMatchingNamespaceFragment, mDeliveryGroupMatchingKeyFragment); } /** * Return the namespace fragment that is used to identify the delivery group that this * broadcast belongs to. * * @return the delivery group namespace fragment that was previously set using * {@link #setDeliveryGroupMatchingKey(String, String)}. * @hide */ @Nullable public String getDeliveryGroupMatchingNamespaceFragment() { return mDeliveryGroupMatchingNamespaceFragment; } /** * Return the key fragment that is used to identify the delivery group that this * broadcast belongs to. * * @return the delivery group key fragment that was previously set using * {@link #setDeliveryGroupMatchingKey(String, String)}. * @hide */ @Nullable public String getDeliveryGroupMatchingKeyFragment() { return mDeliveryGroupMatchingKeyFragment; } /** * Clears the namespace and key that was previously set using * {@link #setDeliveryGroupMatchingKey(String, String)}. */ public void clearDeliveryGroupMatchingKey() { mDeliveryGroupMatchingNamespaceFragment = null; mDeliveryGroupMatchingKeyFragment = null; } /** * Set the {@link IntentFilter} object to identify the delivery group that this broadcast * belongs to. * * <p> If a {@code matchingFilter} is specified, then another broadcast will be considered * to be in the same delivery group as this iff the {@code matchingFilter} matches it's intent. * * <p> If neither matching key using {@link #setDeliveryGroupMatchingKey(String, String)} nor * matching filter using this API is specified, then by default * {@link Intent#filterEquals(Intent)} will be used to identify the delivery group. * * @hide */ @NonNull public BroadcastOptions setDeliveryGroupMatchingFilter(@NonNull IntentFilter matchingFilter) { mDeliveryGroupMatchingFilter = Objects.requireNonNull(matchingFilter); return this; } /** * Return the {@link IntentFilter} object that is used to identify the delivery group * that this broadcast belongs to. * * @return the {@link IntentFilter} object that was previously set using * {@link #setDeliveryGroupMatchingFilter(IntentFilter)}. * @hide */ @Nullable public IntentFilter getDeliveryGroupMatchingFilter() { return mDeliveryGroupMatchingFilter; } /** * Clears the {@link IntentFilter} object that was previously set using * {@link #setDeliveryGroupMatchingFilter(IntentFilter)}. * * @hide */ public void clearDeliveryGroupMatchingFilter() { mDeliveryGroupMatchingFilter = null; } /** * Set the {@link BundleMerger} that specifies how to merge the extras data from * broadcasts in a delivery group. * * <p>Note that this value will be ignored if the delivery group policy is not set as * {@link #DELIVERY_GROUP_POLICY_MERGED}. * * @hide */ @NonNull public BroadcastOptions setDeliveryGroupExtrasMerger(@NonNull BundleMerger extrasMerger) { mDeliveryGroupExtrasMerger = Objects.requireNonNull(extrasMerger); return this; } /** * Return the {@link BundleMerger} that specifies how to merge the extras data from * broadcasts in a delivery group. * * @return the {@link BundleMerger} object that was previously set using * {@link #setDeliveryGroupExtrasMerger(BundleMerger)}. * @hide */ @Nullable public BundleMerger getDeliveryGroupExtrasMerger() { return mDeliveryGroupExtrasMerger; } /** * Clear the {@link BundleMerger} object that was previously set using * {@link #setDeliveryGroupExtrasMerger(BundleMerger)}. * @hide */ public void clearDeliveryGroupExtrasMerger() { mDeliveryGroupExtrasMerger = null; } /** * Sets whether the broadcast should be considered as having originated from * some direct interaction by the user such as a notification tap or button * press. This signal is used internally to ensure the broadcast is * delivered quickly with low latency. * * @hide */ @RequiresPermission(android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE) public @NonNull BroadcastOptions setInteractive(boolean interactive) { if (interactive) { mFlags |= FLAG_INTERACTIVE; } else { mFlags &= ~FLAG_INTERACTIVE; } return this; } /** * Returns whether the broadcast should be considered as having originated * from some direct interaction by the user such as a notification tap or * button press. * * @hide */ @RequiresPermission(android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE) public boolean isInteractive() { return (mFlags & FLAG_INTERACTIVE) != 0; } /** * Set PendingIntent activity is allowed to be started in the background if the caller * can start background activities. * * @deprecated use #setPendingIntentBackgroundActivityStartMode(int) to set the full range * of states * @hide */ @SystemApi @Override @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) { super.setPendingIntentBackgroundActivityLaunchAllowed(allowed); } /** * Get PendingIntent activity is allowed to be started in the background if the caller can start * background activities. * * @deprecated use {@link #getPendingIntentBackgroundActivityStartMode()} since for apps * targeting {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or higher this value might * not match the actual behavior if the value was not explicitly set. * @hide */ @SystemApi @Override @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed() { return super.isPendingIntentBackgroundActivityLaunchAllowed(); } /** * Sets the mode for allowing or denying the senders privileges to start background activities * to the PendingIntent. * * This is typically used when executing {@link PendingIntent#send(Bundle)} or similar * methods. A privileged sender of a PendingIntent should only grant * MODE_BACKGROUND_ACTIVITY_START_ALLOWED if the PendingIntent is from a trusted source and/or * executed on behalf the user. * @hide */ @SystemApi @NonNull @Override // to narrow down the return type public BroadcastOptions setPendingIntentBackgroundActivityStartMode( @BackgroundActivityStartMode int state) { super.setPendingIntentBackgroundActivityStartMode(state); return this; } /** * Gets the mode for allowing or denying the senders privileges to start background activities * to the PendingIntent. * * @see #setPendingIntentBackgroundActivityStartMode(int) * @hide */ @SystemApi @Override // to narrow down the return type public @BackgroundActivityStartMode int getPendingIntentBackgroundActivityStartMode() { return super.getPendingIntentBackgroundActivityStartMode(); } /** * Returns the created options as a Bundle, which can be passed to * {@link android.content.Context#sendBroadcast(android.content.Intent) * Context.sendBroadcast(Intent)} and related methods. * Note that the returned Bundle is still owned by the BroadcastOptions * object; you must not modify it, but can supply it to the sendBroadcast * methods that take an options Bundle. * * @throws IllegalStateException if the broadcast option values are inconsistent. For example, * if the delivery group policy is specified as "MERGED" but no * extras merger is supplied. */ @Override public @NonNull Bundle toBundle() { Bundle b = super.toBundle(); if (mFlags != 0) { b.putInt(KEY_FLAGS, mFlags); } if (isTemporaryAppAllowlistSet()) { b.putLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION, mTemporaryAppAllowlistDuration); b.putInt(KEY_TEMPORARY_APP_ALLOWLIST_TYPE, mTemporaryAppAllowlistType); b.putInt(KEY_TEMPORARY_APP_ALLOWLIST_REASON_CODE, mTemporaryAppAllowlistReasonCode); b.putString(KEY_TEMPORARY_APP_ALLOWLIST_REASON, mTemporaryAppAllowlistReason); } if (mMinManifestReceiverApiLevel != 0) { b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel); } if (mMaxManifestReceiverApiLevel != Build.VERSION_CODES.CUR_DEVELOPMENT) { b.putInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL, mMaxManifestReceiverApiLevel); } if (mRequireAllOfPermissions != null) { b.putStringArray(KEY_REQUIRE_ALL_OF_PERMISSIONS, mRequireAllOfPermissions); } if (mRequireNoneOfPermissions != null) { b.putStringArray(KEY_REQUIRE_NONE_OF_PERMISSIONS, mRequireNoneOfPermissions); } if (mRequireCompatChangeId != CHANGE_INVALID) { b.putLong(KEY_REQUIRE_COMPAT_CHANGE_ID, mRequireCompatChangeId); } if (mIdForResponseEvent != 0) { b.putLong(KEY_ID_FOR_RESPONSE_EVENT, mIdForResponseEvent); } if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) { b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy); } if (mDeliveryGroupMatchingNamespaceFragment != null) { b.putString(KEY_DELIVERY_GROUP_NAMESPACE, mDeliveryGroupMatchingNamespaceFragment); } if (mDeliveryGroupMatchingKeyFragment != null) { b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupMatchingKeyFragment); } if (mDeliveryGroupPolicy == DELIVERY_GROUP_POLICY_MERGED) { if (mDeliveryGroupExtrasMerger != null) { b.putParcelable(KEY_DELIVERY_GROUP_EXTRAS_MERGER, mDeliveryGroupExtrasMerger); } else { throw new IllegalStateException("Extras merger cannot be empty " + "when delivery group policy is 'MERGED'"); } } if (mDeliveryGroupMatchingFilter != null) { b.putParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER, mDeliveryGroupMatchingFilter); } if (mDeferralPolicy != DEFERRAL_POLICY_DEFAULT) { b.putInt(KEY_DEFERRAL_POLICY, mDeferralPolicy); } return b; } /** * Returns a {@link BroadcastOptions} parsed from the given {@link Bundle}, * typically generated from {@link #toBundle()}. */ public static @NonNull BroadcastOptions fromBundle(@NonNull Bundle options) { return new BroadcastOptions(options); } /** {@hide} */ public static @Nullable BroadcastOptions fromBundleNullable(@Nullable Bundle options) { return (options != null) ? new BroadcastOptions(options) : null; } }