/*
 * 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;
    }
}