Merge "Update system APIs based on feedback."
This commit is contained in:
@@ -5003,6 +5003,31 @@ package android.app {
|
|||||||
field @NonNull public static final android.os.Parcelable.Creator<android.app.BackgroundServiceStartNotAllowedException> CREATOR;
|
field @NonNull public static final android.os.Parcelable.Creator<android.app.BackgroundServiceStartNotAllowedException> CREATOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class BroadcastOptions {
|
||||||
|
method public void clearDeferralPolicy();
|
||||||
|
method public void clearDeliveryGroupMatchingFilter();
|
||||||
|
method public void clearDeliveryGroupMatchingKey();
|
||||||
|
method public void clearDeliveryGroupPolicy();
|
||||||
|
method @NonNull public static android.app.BroadcastOptions fromBundle(@NonNull android.os.Bundle);
|
||||||
|
method public int getDeferralPolicy();
|
||||||
|
method @Nullable public android.content.IntentFilter getDeliveryGroupMatchingFilter();
|
||||||
|
method @Nullable public String getDeliveryGroupMatchingKey();
|
||||||
|
method public int getDeliveryGroupPolicy();
|
||||||
|
method public boolean isShareIdentityEnabled();
|
||||||
|
method @NonNull public static android.app.BroadcastOptions makeBasic();
|
||||||
|
method @NonNull public android.app.BroadcastOptions setDeferralPolicy(int);
|
||||||
|
method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingFilter(@NonNull android.content.IntentFilter);
|
||||||
|
method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String, @NonNull String);
|
||||||
|
method @NonNull public android.app.BroadcastOptions setDeliveryGroupPolicy(int);
|
||||||
|
method @NonNull public android.app.BroadcastOptions setShareIdentityEnabled(boolean);
|
||||||
|
method @NonNull public android.os.Bundle toBundle();
|
||||||
|
field public static final int DEFERRAL_POLICY_DEFAULT = 0; // 0x0
|
||||||
|
field public static final int DEFERRAL_POLICY_NONE = 1; // 0x1
|
||||||
|
field public static final int DEFERRAL_POLICY_UNTIL_ACTIVE = 2; // 0x2
|
||||||
|
field public static final int DELIVERY_GROUP_POLICY_ALL = 0; // 0x0
|
||||||
|
field public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1; // 0x1
|
||||||
|
}
|
||||||
|
|
||||||
public class DatePickerDialog extends android.app.AlertDialog implements android.widget.DatePicker.OnDateChangedListener android.content.DialogInterface.OnClickListener {
|
public class DatePickerDialog extends android.app.AlertDialog implements android.widget.DatePicker.OnDateChangedListener android.content.DialogInterface.OnClickListener {
|
||||||
ctor public DatePickerDialog(@NonNull android.content.Context);
|
ctor public DatePickerDialog(@NonNull android.content.Context);
|
||||||
ctor public DatePickerDialog(@NonNull android.content.Context, @StyleRes int);
|
ctor public DatePickerDialog(@NonNull android.content.Context, @StyleRes int);
|
||||||
|
|||||||
@@ -761,25 +761,20 @@ package android.app {
|
|||||||
|
|
||||||
public class BroadcastOptions {
|
public class BroadcastOptions {
|
||||||
method public void clearRequireCompatChange();
|
method public void clearRequireCompatChange();
|
||||||
method public boolean isDeferUntilActive();
|
method public int getPendingIntentBackgroundActivityStartMode();
|
||||||
method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
|
method @Deprecated public boolean isDeferUntilActive();
|
||||||
method public static android.app.BroadcastOptions makeBasic();
|
method @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed();
|
||||||
method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
|
method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
|
||||||
method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
|
method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
|
||||||
method @NonNull public android.app.BroadcastOptions setDeferUntilActive(boolean);
|
method @Deprecated @NonNull public android.app.BroadcastOptions setDeferUntilActive(boolean);
|
||||||
method public void setDeliveryGroupMatchingFilter(@NonNull android.content.IntentFilter);
|
|
||||||
method public void setDeliveryGroupMatchingKey(@NonNull String, @NonNull String);
|
|
||||||
method public void setDeliveryGroupPolicy(int);
|
|
||||||
method public void setDontSendToRestrictedApps(boolean);
|
method public void setDontSendToRestrictedApps(boolean);
|
||||||
method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
|
method @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
|
||||||
|
method @NonNull public android.app.BroadcastOptions setPendingIntentBackgroundActivityStartMode(int);
|
||||||
method public void setRequireAllOfPermissions(@Nullable String[]);
|
method public void setRequireAllOfPermissions(@Nullable String[]);
|
||||||
method public void setRequireCompatChange(long, boolean);
|
method public void setRequireCompatChange(long, boolean);
|
||||||
method public void setRequireNoneOfPermissions(@Nullable String[]);
|
method public void setRequireNoneOfPermissions(@Nullable String[]);
|
||||||
method @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, int, int, @Nullable String);
|
method @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, int, int, @Nullable String);
|
||||||
method @Deprecated @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);
|
method @Deprecated @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);
|
||||||
method public android.os.Bundle toBundle();
|
|
||||||
field public static final int DELIVERY_GROUP_POLICY_ALL = 0; // 0x0
|
|
||||||
field public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1; // 0x1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DownloadManager {
|
public class DownloadManager {
|
||||||
|
|||||||
@@ -264,6 +264,7 @@ package android.app {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class BroadcastOptions {
|
public class BroadcastOptions {
|
||||||
|
ctor public BroadcastOptions();
|
||||||
ctor public BroadcastOptions(@NonNull android.os.Bundle);
|
ctor public BroadcastOptions(@NonNull android.os.Bundle);
|
||||||
method @Deprecated public int getMaxManifestReceiverApiLevel();
|
method @Deprecated public int getMaxManifestReceiverApiLevel();
|
||||||
method public long getTemporaryAppAllowlistDuration();
|
method public long getTemporaryAppAllowlistDuration();
|
||||||
|
|||||||
@@ -27,10 +27,12 @@ import android.app.compat.CompatChanges;
|
|||||||
import android.compat.annotation.ChangeId;
|
import android.compat.annotation.ChangeId;
|
||||||
import android.compat.annotation.Disabled;
|
import android.compat.annotation.Disabled;
|
||||||
import android.compat.annotation.EnabledSince;
|
import android.compat.annotation.EnabledSince;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.BundleMerger;
|
||||||
import android.os.PowerExemptionManager;
|
import android.os.PowerExemptionManager;
|
||||||
import android.os.PowerExemptionManager.ReasonCode;
|
import android.os.PowerExemptionManager.ReasonCode;
|
||||||
import android.os.PowerExemptionManager.TempAllowListType;
|
import android.os.PowerExemptionManager.TempAllowListType;
|
||||||
@@ -45,28 +47,43 @@ import java.util.Objects;
|
|||||||
* Helper class for building an options Bundle that can be used with
|
* Helper class for building an options Bundle that can be used with
|
||||||
* {@link android.content.Context#sendBroadcast(android.content.Intent)
|
* {@link android.content.Context#sendBroadcast(android.content.Intent)
|
||||||
* Context.sendBroadcast(Intent)} and related methods.
|
* Context.sendBroadcast(Intent)} and related methods.
|
||||||
* {@hide}
|
|
||||||
*/
|
*/
|
||||||
@SystemApi
|
|
||||||
public class BroadcastOptions extends ComponentOptions {
|
public class BroadcastOptions extends ComponentOptions {
|
||||||
|
private @Flags int mFlags;
|
||||||
private long mTemporaryAppAllowlistDuration;
|
private long mTemporaryAppAllowlistDuration;
|
||||||
private @TempAllowListType int mTemporaryAppAllowlistType;
|
private @TempAllowListType int mTemporaryAppAllowlistType;
|
||||||
private @ReasonCode int mTemporaryAppAllowlistReasonCode;
|
private @ReasonCode int mTemporaryAppAllowlistReasonCode;
|
||||||
private @Nullable String mTemporaryAppAllowlistReason;
|
private @Nullable String mTemporaryAppAllowlistReason;
|
||||||
private int mMinManifestReceiverApiLevel = 0;
|
private int mMinManifestReceiverApiLevel = 0;
|
||||||
private int mMaxManifestReceiverApiLevel = Build.VERSION_CODES.CUR_DEVELOPMENT;
|
private int mMaxManifestReceiverApiLevel = Build.VERSION_CODES.CUR_DEVELOPMENT;
|
||||||
private boolean mDontSendToRestrictedApps = false;
|
|
||||||
private boolean mAllowBackgroundActivityStarts;
|
|
||||||
private String[] mRequireAllOfPermissions;
|
private String[] mRequireAllOfPermissions;
|
||||||
private String[] mRequireNoneOfPermissions;
|
private String[] mRequireNoneOfPermissions;
|
||||||
private long mRequireCompatChangeId = CHANGE_INVALID;
|
private long mRequireCompatChangeId = CHANGE_INVALID;
|
||||||
private boolean mRequireCompatChangeEnabled = true;
|
|
||||||
private boolean mIsAlarmBroadcast = false;
|
|
||||||
private long mIdForResponseEvent;
|
private long mIdForResponseEvent;
|
||||||
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
|
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
|
||||||
private @Nullable String mDeliveryGroupMatchingKey;
|
private @Nullable String mDeliveryGroupMatchingKey;
|
||||||
|
private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
|
||||||
private @Nullable IntentFilter mDeliveryGroupMatchingFilter;
|
private @Nullable IntentFilter mDeliveryGroupMatchingFilter;
|
||||||
private boolean mIsDeferUntilActive = false;
|
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.
|
* Change ID which is invalid.
|
||||||
@@ -95,6 +112,11 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
@Disabled
|
@Disabled
|
||||||
public static final long CHANGE_ALWAYS_DISABLED = 210856463L;
|
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
|
* How long to temporarily put an app on the power allowlist when executing this broadcast
|
||||||
* to it.
|
* to it.
|
||||||
@@ -123,18 +145,6 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
private static final String KEY_MAX_MANIFEST_RECEIVER_API_LEVEL
|
private static final String KEY_MAX_MANIFEST_RECEIVER_API_LEVEL
|
||||||
= "android:broadcast.maxManifestReceiverApiLevel";
|
= "android:broadcast.maxManifestReceiverApiLevel";
|
||||||
|
|
||||||
/**
|
|
||||||
* Corresponds to {@link #setDontSendToRestrictedApps}.
|
|
||||||
*/
|
|
||||||
private static final String KEY_DONT_SEND_TO_RESTRICTED_APPS =
|
|
||||||
"android:broadcast.dontSendToRestrictedApps";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Corresponds to {@link #setBackgroundActivityStartsAllowed}.
|
|
||||||
*/
|
|
||||||
private static final String KEY_ALLOW_BACKGROUND_ACTIVITY_STARTS =
|
|
||||||
"android:broadcast.allowBackgroundActivityStarts";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Corresponds to {@link #setRequireAllOfPermissions}
|
* Corresponds to {@link #setRequireAllOfPermissions}
|
||||||
* @hide
|
* @hide
|
||||||
@@ -192,14 +202,45 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
private static final String KEY_ID_FOR_RESPONSE_EVENT =
|
private static final String KEY_ID_FOR_RESPONSE_EVENT =
|
||||||
"android:broadcast.idForResponseEvent";
|
"android:broadcast.idForResponseEvent";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Corresponds to {@link #setDeliveryGroupPolicy(int)}.
|
||||||
|
*/
|
||||||
|
private static final String KEY_DELIVERY_GROUP_POLICY =
|
||||||
|
"android:broadcast.deliveryGroupPolicy";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Corresponds to {@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 list of delivery group policies which specify how multiple broadcasts belonging to
|
||||||
* the same delivery group has to be handled.
|
* the same delivery group has to be handled.
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
@IntDef(flag = true, prefix = { "DELIVERY_GROUP_POLICY_" }, value = {
|
@IntDef(prefix = { "DELIVERY_GROUP_POLICY_" }, value = {
|
||||||
DELIVERY_GROUP_POLICY_ALL,
|
DELIVERY_GROUP_POLICY_ALL,
|
||||||
DELIVERY_GROUP_POLICY_MOST_RECENT,
|
DELIVERY_GROUP_POLICY_MOST_RECENT,
|
||||||
|
DELIVERY_GROUP_POLICY_MERGED,
|
||||||
})
|
})
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
public @interface DeliveryGroupPolicy {}
|
public @interface DeliveryGroupPolicy {}
|
||||||
@@ -207,27 +248,80 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
/**
|
/**
|
||||||
* Delivery group policy that indicates that all the broadcasts in the delivery group
|
* Delivery group policy that indicates that all the broadcasts in the delivery group
|
||||||
* need to be delivered as is.
|
* need to be delivered as is.
|
||||||
*
|
|
||||||
* @hide
|
|
||||||
*/
|
*/
|
||||||
@SystemApi
|
|
||||||
public static final int DELIVERY_GROUP_POLICY_ALL = 0;
|
public static final int DELIVERY_GROUP_POLICY_ALL = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delivery group policy that indicates that only the most recent broadcast in the delivery
|
* 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.
|
* 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
|
* @hide
|
||||||
*/
|
*/
|
||||||
@SystemApi
|
public static final int DELIVERY_GROUP_POLICY_MERGED = 2;
|
||||||
public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1;
|
|
||||||
|
|
||||||
public static BroadcastOptions makeBasic() {
|
/** {@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();
|
BroadcastOptions opts = new BroadcastOptions();
|
||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BroadcastOptions() {
|
/** @hide */
|
||||||
|
@TestApi
|
||||||
|
public BroadcastOptions() {
|
||||||
super();
|
super();
|
||||||
resetTemporaryAppAllowlist();
|
resetTemporaryAppAllowlist();
|
||||||
}
|
}
|
||||||
@@ -237,6 +331,7 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
public BroadcastOptions(@NonNull Bundle opts) {
|
public BroadcastOptions(@NonNull Bundle opts) {
|
||||||
super(opts);
|
super(opts);
|
||||||
// Match the logic in toBundle().
|
// Match the logic in toBundle().
|
||||||
|
mFlags = opts.getInt(KEY_FLAGS, 0);
|
||||||
if (opts.containsKey(KEY_TEMPORARY_APP_ALLOWLIST_DURATION)) {
|
if (opts.containsKey(KEY_TEMPORARY_APP_ALLOWLIST_DURATION)) {
|
||||||
mTemporaryAppAllowlistDuration = opts.getLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION);
|
mTemporaryAppAllowlistDuration = opts.getLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION);
|
||||||
mTemporaryAppAllowlistType = opts.getInt(KEY_TEMPORARY_APP_ALLOWLIST_TYPE);
|
mTemporaryAppAllowlistType = opts.getInt(KEY_TEMPORARY_APP_ALLOWLIST_TYPE);
|
||||||
@@ -249,15 +344,18 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
mMinManifestReceiverApiLevel = opts.getInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, 0);
|
mMinManifestReceiverApiLevel = opts.getInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, 0);
|
||||||
mMaxManifestReceiverApiLevel = opts.getInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL,
|
mMaxManifestReceiverApiLevel = opts.getInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL,
|
||||||
Build.VERSION_CODES.CUR_DEVELOPMENT);
|
Build.VERSION_CODES.CUR_DEVELOPMENT);
|
||||||
mDontSendToRestrictedApps = opts.getBoolean(KEY_DONT_SEND_TO_RESTRICTED_APPS, false);
|
|
||||||
mAllowBackgroundActivityStarts = opts.getBoolean(KEY_ALLOW_BACKGROUND_ACTIVITY_STARTS,
|
|
||||||
false);
|
|
||||||
mRequireAllOfPermissions = opts.getStringArray(KEY_REQUIRE_ALL_OF_PERMISSIONS);
|
mRequireAllOfPermissions = opts.getStringArray(KEY_REQUIRE_ALL_OF_PERMISSIONS);
|
||||||
mRequireNoneOfPermissions = opts.getStringArray(KEY_REQUIRE_NONE_OF_PERMISSIONS);
|
mRequireNoneOfPermissions = opts.getStringArray(KEY_REQUIRE_NONE_OF_PERMISSIONS);
|
||||||
mRequireCompatChangeId = opts.getLong(KEY_REQUIRE_COMPAT_CHANGE_ID, CHANGE_INVALID);
|
mRequireCompatChangeId = opts.getLong(KEY_REQUIRE_COMPAT_CHANGE_ID, CHANGE_INVALID);
|
||||||
mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true);
|
|
||||||
mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
|
mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
|
||||||
mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false);
|
mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
|
||||||
|
DELIVERY_GROUP_POLICY_ALL);
|
||||||
|
mDeliveryGroupMatchingKey = 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -265,8 +363,10 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
* power allowlist when this broadcast is being delivered to it.
|
* power allowlist when this broadcast is being delivered to it.
|
||||||
* @param duration The duration in milliseconds; 0 means to not place on allowlist.
|
* @param duration The duration in milliseconds; 0 means to not place on allowlist.
|
||||||
* @deprecated use {@link #setTemporaryAppAllowlist(long, int, int, String)} instead.
|
* @deprecated use {@link #setTemporaryAppAllowlist(long, int, int, String)} instead.
|
||||||
|
* @hide
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
|
@SystemApi
|
||||||
@RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
|
@RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
|
||||||
android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
|
android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
|
||||||
android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND})
|
android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND})
|
||||||
@@ -280,6 +380,8 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
* Set a duration for which the system should temporary place an application on the
|
* 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
|
* power allowlist when this broadcast is being delivered to it, specify the temp allowlist
|
||||||
* type.
|
* type.
|
||||||
|
* @hide
|
||||||
|
*
|
||||||
* @param duration the duration in milliseconds.
|
* @param duration the duration in milliseconds.
|
||||||
* 0 means to not place on allowlist, and clears previous call to this method.
|
* 0 means to not place on allowlist, and clears previous call to this method.
|
||||||
* @param type one of {@link TempAllowListType}.
|
* @param type one of {@link TempAllowListType}.
|
||||||
@@ -290,6 +392,7 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
* @param reason A human-readable reason explaining why the app is temp allowlisted. Only
|
* @param reason A human-readable reason explaining why the app is temp allowlisted. Only
|
||||||
* used for logging purposes. Could be null or empty string.
|
* used for logging purposes. Could be null or empty string.
|
||||||
*/
|
*/
|
||||||
|
@SystemApi
|
||||||
@RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
|
@RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
|
||||||
android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
|
android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
|
||||||
android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND})
|
android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND})
|
||||||
@@ -318,26 +421,6 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
mTemporaryAppAllowlistReason = null;
|
mTemporaryAppAllowlistReason = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set PendingIntent activity is allowed to be started in the background if the caller
|
|
||||||
* can start background activities.
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
|
||||||
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.
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
|
||||||
public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
|
|
||||||
return super.isPendingIntentBackgroundActivityLaunchAllowed();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return {@link #setTemporaryAppAllowlist}.
|
* Return {@link #setTemporaryAppAllowlist}.
|
||||||
* @hide
|
* @hide
|
||||||
@@ -450,9 +533,15 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
* Sets whether pending intent can be sent for an application with background restrictions
|
* 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
|
* @param dontSendToRestrictedApps if true, pending intent will not be sent for an application
|
||||||
* with background restrictions. Default value is {@code false}
|
* with background restrictions. Default value is {@code false}
|
||||||
|
* @hide
|
||||||
*/
|
*/
|
||||||
|
@SystemApi
|
||||||
public void setDontSendToRestrictedApps(boolean dontSendToRestrictedApps) {
|
public void setDontSendToRestrictedApps(boolean dontSendToRestrictedApps) {
|
||||||
mDontSendToRestrictedApps = dontSendToRestrictedApps;
|
if (dontSendToRestrictedApps) {
|
||||||
|
mFlags |= FLAG_DONT_SEND_TO_RESTRICTED_APPS;
|
||||||
|
} else {
|
||||||
|
mFlags &= ~FLAG_DONT_SEND_TO_RESTRICTED_APPS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -460,24 +549,31 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
* @return #setDontSendToRestrictedApps
|
* @return #setDontSendToRestrictedApps
|
||||||
*/
|
*/
|
||||||
public boolean isDontSendToRestrictedApps() {
|
public boolean isDontSendToRestrictedApps() {
|
||||||
return mDontSendToRestrictedApps;
|
return (mFlags & FLAG_DONT_SEND_TO_RESTRICTED_APPS) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the process will be able to start activities from background for the duration of
|
* Sets the process will be able to start activities from background for the duration of
|
||||||
* the broadcast dispatch. Default value is {@code false}
|
* the broadcast dispatch. Default value is {@code false}
|
||||||
|
* @hide
|
||||||
*/
|
*/
|
||||||
|
@SystemApi
|
||||||
@RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND)
|
@RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND)
|
||||||
public void setBackgroundActivityStartsAllowed(boolean allowBackgroundActivityStarts) {
|
public void setBackgroundActivityStartsAllowed(boolean allowBackgroundActivityStarts) {
|
||||||
mAllowBackgroundActivityStarts = allowBackgroundActivityStarts;
|
if (allowBackgroundActivityStarts) {
|
||||||
|
mFlags |= FLAG_ALLOW_BACKGROUND_ACTIVITY_STARTS;
|
||||||
|
} else {
|
||||||
|
mFlags &= ~FLAG_ALLOW_BACKGROUND_ACTIVITY_STARTS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hide
|
* @hide
|
||||||
* @return #setAllowBackgroundActivityStarts
|
* @return #setAllowBackgroundActivityStarts
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public boolean allowsBackgroundActivityStarts() {
|
public boolean allowsBackgroundActivityStarts() {
|
||||||
return mAllowBackgroundActivityStarts;
|
return (mFlags & FLAG_ALLOW_BACKGROUND_ACTIVITY_STARTS) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -528,6 +624,7 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
* <p>
|
* <p>
|
||||||
* This requirement applies to both manifest registered and runtime
|
* This requirement applies to both manifest registered and runtime
|
||||||
* registered receivers.
|
* registered receivers.
|
||||||
|
* @hide
|
||||||
*
|
*
|
||||||
* @param changeId the {@link ChangeId} to inspect
|
* @param changeId the {@link ChangeId} to inspect
|
||||||
* @param enabled the required enabled state of the inspected
|
* @param enabled the required enabled state of the inspected
|
||||||
@@ -535,18 +632,24 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
* @see CompatChanges#isChangeEnabled
|
* @see CompatChanges#isChangeEnabled
|
||||||
* @see #clearRequireCompatChange()
|
* @see #clearRequireCompatChange()
|
||||||
*/
|
*/
|
||||||
|
@SystemApi
|
||||||
public void setRequireCompatChange(long changeId, boolean enabled) {
|
public void setRequireCompatChange(long changeId, boolean enabled) {
|
||||||
mRequireCompatChangeId = changeId;
|
mRequireCompatChangeId = changeId;
|
||||||
mRequireCompatChangeEnabled = enabled;
|
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
|
* Clear any previously defined requirement for this broadcast requested via
|
||||||
* {@link #setRequireCompatChange(long, boolean)}.
|
* {@link #setRequireCompatChange(long, boolean)}.
|
||||||
|
* @hide
|
||||||
*/
|
*/
|
||||||
|
@SystemApi
|
||||||
public void clearRequireCompatChange() {
|
public void clearRequireCompatChange() {
|
||||||
mRequireCompatChangeId = CHANGE_INVALID;
|
setRequireCompatChange(CHANGE_INVALID, true);
|
||||||
mRequireCompatChangeEnabled = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -558,7 +661,11 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
* @param senderIsAlarm Whether the broadcast is alarm-triggered.
|
* @param senderIsAlarm Whether the broadcast is alarm-triggered.
|
||||||
*/
|
*/
|
||||||
public void setAlarmBroadcast(boolean senderIsAlarm) {
|
public void setAlarmBroadcast(boolean senderIsAlarm) {
|
||||||
mIsAlarmBroadcast = senderIsAlarm;
|
if (senderIsAlarm) {
|
||||||
|
mFlags |= FLAG_IS_ALARM_BROADCAST;
|
||||||
|
} else {
|
||||||
|
mFlags &= ~FLAG_IS_ALARM_BROADCAST;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -567,7 +674,44 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public boolean isAlarmBroadcast() {
|
public boolean isAlarmBroadcast() {
|
||||||
return mIsAlarmBroadcast;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -606,8 +750,8 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
@TestApi
|
@TestApi
|
||||||
public boolean testRequireCompatChange(int uid) {
|
public boolean testRequireCompatChange(int uid) {
|
||||||
if (mRequireCompatChangeId != CHANGE_INVALID) {
|
if (mRequireCompatChangeId != CHANGE_INVALID) {
|
||||||
return CompatChanges.isChangeEnabled(mRequireCompatChangeId,
|
final boolean requireEnabled = (mFlags & FLAG_REQUIRE_COMPAT_CHANGE_ENABLED) != 0;
|
||||||
uid) == mRequireCompatChangeEnabled;
|
return CompatChanges.isChangeEnabled(mRequireCompatChangeId, uid) == requireEnabled;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -637,15 +781,77 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
return mIdForResponseEvent;
|
return mIdForResponseEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@hide} */
|
||||||
|
@SystemApi
|
||||||
|
@Deprecated
|
||||||
|
// STOPSHIP: remove entirely after this API change lands in AOSP
|
||||||
|
public @NonNull BroadcastOptions setDeferUntilActive(boolean shouldDefer) {
|
||||||
|
if (shouldDefer) {
|
||||||
|
setDeferralPolicy(DEFERRAL_POLICY_UNTIL_ACTIVE);
|
||||||
|
} else {
|
||||||
|
setDeferralPolicy(DEFERRAL_POLICY_NONE);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@hide} */
|
||||||
|
@SystemApi
|
||||||
|
@Deprecated
|
||||||
|
// STOPSHIP: remove entirely after this API change lands in AOSP
|
||||||
|
public boolean isDeferUntilActive() {
|
||||||
|
return (mDeferralPolicy == DEFERRAL_POLICY_UNTIL_ACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* Set delivery group policy for this broadcast to specify how multiple broadcasts belonging to
|
||||||
* the same delivery group has to be handled.
|
* the same delivery group has to be handled.
|
||||||
*
|
|
||||||
* @hide
|
|
||||||
*/
|
*/
|
||||||
@SystemApi
|
@NonNull
|
||||||
public void setDeliveryGroupPolicy(@DeliveryGroupPolicy int policy) {
|
public BroadcastOptions setDeliveryGroupPolicy(@DeliveryGroupPolicy int policy) {
|
||||||
mDeliveryGroupPolicy = 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -658,16 +864,36 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
* <p> If neither matching key using this API nor matching filter using
|
* <p> If neither matching key using this API nor matching filter using
|
||||||
* {@link #setDeliveryGroupMatchingFilter(IntentFilter)} is specified, then by default
|
* {@link #setDeliveryGroupMatchingFilter(IntentFilter)} is specified, then by default
|
||||||
* {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
|
* {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
|
||||||
*
|
|
||||||
* @hide
|
|
||||||
*/
|
*/
|
||||||
@SystemApi
|
@NonNull
|
||||||
public void setDeliveryGroupMatchingKey(@NonNull String namespace, @NonNull String key) {
|
public BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String namespace,
|
||||||
Preconditions.checkArgument(!namespace.contains("/"),
|
@NonNull String key) {
|
||||||
"namespace should not contain '/'");
|
Preconditions.checkArgument(!namespace.contains(":"),
|
||||||
Preconditions.checkArgument(!key.contains("/"),
|
"namespace should not contain ':'");
|
||||||
"key should not contain '/'");
|
Preconditions.checkArgument(!key.contains(":"),
|
||||||
mDeliveryGroupMatchingKey = namespace + "/" + key;
|
"key should not contain ':'");
|
||||||
|
mDeliveryGroupMatchingKey = namespace + ":" + 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() {
|
||||||
|
return mDeliveryGroupMatchingKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the namespace and key that was previously set using
|
||||||
|
* {@link #setDeliveryGroupMatchingKey(String, String)}.
|
||||||
|
*/
|
||||||
|
public void clearDeliveryGroupMatchingKey() {
|
||||||
|
mDeliveryGroupMatchingKey = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -680,47 +906,158 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
* <p> If neither matching key using {@link #setDeliveryGroupMatchingKey(String, String)} nor
|
* <p> If neither matching key using {@link #setDeliveryGroupMatchingKey(String, String)} nor
|
||||||
* matching filter using this API is specified, then by default
|
* matching filter using this API is specified, then by default
|
||||||
* {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
|
* {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
|
||||||
*
|
|
||||||
* @hide
|
|
||||||
*/
|
*/
|
||||||
@SystemApi
|
@NonNull
|
||||||
public void setDeliveryGroupMatchingFilter(@NonNull IntentFilter matchingFilter) {
|
public BroadcastOptions setDeliveryGroupMatchingFilter(@NonNull IntentFilter matchingFilter) {
|
||||||
mDeliveryGroupMatchingFilter = Objects.requireNonNull(matchingFilter);
|
mDeliveryGroupMatchingFilter = Objects.requireNonNull(matchingFilter);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether the broadcast should not run until the process is in an active process state
|
|
||||||
* (ie, a process exists for the app and the app is not in a cached process state).
|
|
||||||
*
|
|
||||||
* Whether an app's process state is considered active is independent of its standby bucket.
|
|
||||||
*
|
|
||||||
* A broadcast that is deferred until the process is active will not execute until the process
|
|
||||||
* is brought to an active state by some other action, like a job, alarm, or service binding. As
|
|
||||||
* a result, the broadcast may be delayed indefinitely. This deferral only applies to runtime
|
|
||||||
* registered receivers of a broadcast. Any manifest receivers will run immediately, similar to
|
|
||||||
* how a manifest receiver would start a new process in order to run a broadcast receiver.
|
|
||||||
*
|
|
||||||
* Ordered broadcasts, alarm broadcasts, interactive broadcasts, and manifest broadcasts are
|
|
||||||
* never deferred.
|
|
||||||
*
|
|
||||||
* Unordered broadcasts and unordered broadcasts with completion callbacks may be
|
|
||||||
* deferred. Completion callbacks for broadcasts deferred until active are
|
|
||||||
* best-effort. Completion callbacks will run when all eligible processes have finished
|
|
||||||
* executing the broadcast. Processes in inactive process states that defer the broadcast are
|
|
||||||
* not considered eligible and may not execute the broadcast prior to the completion callback.
|
|
||||||
*
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
@SystemApi
|
|
||||||
public @NonNull BroadcastOptions setDeferUntilActive(boolean shouldDefer) {
|
|
||||||
mIsDeferUntilActive = shouldDefer;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hide */
|
/**
|
||||||
|
* 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)}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public IntentFilter getDeliveryGroupMatchingFilter() {
|
||||||
|
return mDeliveryGroupMatchingFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the {@link IntentFilter} object that was previously set using
|
||||||
|
* {@link #setDeliveryGroupMatchingFilter(IntentFilter)}.
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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
|
@SystemApi
|
||||||
public boolean isDeferUntilActive() {
|
@Override
|
||||||
return mIsDeferUntilActive;
|
@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(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 int getPendingIntentBackgroundActivityStartMode() {
|
||||||
|
return 0;
|
||||||
|
// return super.getPendingIntentBackgroundActivityStartMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -730,31 +1067,29 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
* Note that the returned Bundle is still owned by the BroadcastOptions
|
* Note that the returned Bundle is still owned by the BroadcastOptions
|
||||||
* object; you must not modify it, but can supply it to the sendBroadcast
|
* object; you must not modify it, but can supply it to the sendBroadcast
|
||||||
* methods that take an options Bundle.
|
* 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
|
@Override
|
||||||
public Bundle toBundle() {
|
public @NonNull Bundle toBundle() {
|
||||||
Bundle b = super.toBundle();
|
Bundle b = super.toBundle();
|
||||||
|
if (mFlags != 0) {
|
||||||
|
b.putInt(KEY_FLAGS, mFlags);
|
||||||
|
}
|
||||||
if (isTemporaryAppAllowlistSet()) {
|
if (isTemporaryAppAllowlistSet()) {
|
||||||
b.putLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION, mTemporaryAppAllowlistDuration);
|
b.putLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION, mTemporaryAppAllowlistDuration);
|
||||||
b.putInt(KEY_TEMPORARY_APP_ALLOWLIST_TYPE, mTemporaryAppAllowlistType);
|
b.putInt(KEY_TEMPORARY_APP_ALLOWLIST_TYPE, mTemporaryAppAllowlistType);
|
||||||
b.putInt(KEY_TEMPORARY_APP_ALLOWLIST_REASON_CODE, mTemporaryAppAllowlistReasonCode);
|
b.putInt(KEY_TEMPORARY_APP_ALLOWLIST_REASON_CODE, mTemporaryAppAllowlistReasonCode);
|
||||||
b.putString(KEY_TEMPORARY_APP_ALLOWLIST_REASON, mTemporaryAppAllowlistReason);
|
b.putString(KEY_TEMPORARY_APP_ALLOWLIST_REASON, mTemporaryAppAllowlistReason);
|
||||||
}
|
}
|
||||||
if (mIsAlarmBroadcast) {
|
|
||||||
b.putBoolean(KEY_ALARM_BROADCAST, true);
|
|
||||||
}
|
|
||||||
if (mMinManifestReceiverApiLevel != 0) {
|
if (mMinManifestReceiverApiLevel != 0) {
|
||||||
b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel);
|
b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel);
|
||||||
}
|
}
|
||||||
if (mMaxManifestReceiverApiLevel != Build.VERSION_CODES.CUR_DEVELOPMENT) {
|
if (mMaxManifestReceiverApiLevel != Build.VERSION_CODES.CUR_DEVELOPMENT) {
|
||||||
b.putInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL, mMaxManifestReceiverApiLevel);
|
b.putInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL, mMaxManifestReceiverApiLevel);
|
||||||
}
|
}
|
||||||
if (mDontSendToRestrictedApps) {
|
|
||||||
b.putBoolean(KEY_DONT_SEND_TO_RESTRICTED_APPS, true);
|
|
||||||
}
|
|
||||||
if (mAllowBackgroundActivityStarts) {
|
|
||||||
b.putBoolean(KEY_ALLOW_BACKGROUND_ACTIVITY_STARTS, true);
|
|
||||||
}
|
|
||||||
if (mRequireAllOfPermissions != null) {
|
if (mRequireAllOfPermissions != null) {
|
||||||
b.putStringArray(KEY_REQUIRE_ALL_OF_PERMISSIONS, mRequireAllOfPermissions);
|
b.putStringArray(KEY_REQUIRE_ALL_OF_PERMISSIONS, mRequireAllOfPermissions);
|
||||||
}
|
}
|
||||||
@@ -763,11 +1098,44 @@ public class BroadcastOptions extends ComponentOptions {
|
|||||||
}
|
}
|
||||||
if (mRequireCompatChangeId != CHANGE_INVALID) {
|
if (mRequireCompatChangeId != CHANGE_INVALID) {
|
||||||
b.putLong(KEY_REQUIRE_COMPAT_CHANGE_ID, mRequireCompatChangeId);
|
b.putLong(KEY_REQUIRE_COMPAT_CHANGE_ID, mRequireCompatChangeId);
|
||||||
b.putBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, mRequireCompatChangeEnabled);
|
|
||||||
}
|
}
|
||||||
if (mIdForResponseEvent != 0) {
|
if (mIdForResponseEvent != 0) {
|
||||||
b.putLong(KEY_ID_FOR_RESPONSE_EVENT, mIdForResponseEvent);
|
b.putLong(KEY_ID_FOR_RESPONSE_EVENT, mIdForResponseEvent);
|
||||||
}
|
}
|
||||||
return b.isEmpty() ? null : b;
|
if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) {
|
||||||
|
b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy);
|
||||||
|
}
|
||||||
|
if (mDeliveryGroupMatchingKey != null) {
|
||||||
|
b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupMatchingKey);
|
||||||
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
388
core/java/android/os/BundleMerger.java
Normal file
388
core/java/android/os/BundleMerger.java
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.os;
|
||||||
|
|
||||||
|
import android.annotation.IntDef;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.util.ArrayMap;
|
||||||
|
import android.util.ArraySet;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configured rules for merging two {@link Bundle} instances.
|
||||||
|
* <p>
|
||||||
|
* By default, values from both {@link Bundle} instances are blended together on
|
||||||
|
* a key-wise basis, and conflicting value definitions for a key are dropped.
|
||||||
|
* <p>
|
||||||
|
* Nuanced strategies for handling conflicting value definitions can be applied
|
||||||
|
* using {@link #setMergeStrategy(String, int)} and
|
||||||
|
* {@link #setDefaultMergeStrategy(int)}.
|
||||||
|
* <p>
|
||||||
|
* When conflicting values have <em>inconsistent</em> data types (such as trying
|
||||||
|
* to merge a {@link String} and a {@link Integer}), both conflicting values are
|
||||||
|
* rejected and the key becomes undefined, regardless of the requested strategy.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public class BundleMerger implements Parcelable {
|
||||||
|
private static final String TAG = "BundleMerger";
|
||||||
|
|
||||||
|
private @Strategy int mDefaultStrategy = STRATEGY_REJECT;
|
||||||
|
|
||||||
|
private final ArrayMap<String, Integer> mStrategies = new ArrayMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge strategy that rejects both conflicting values.
|
||||||
|
*/
|
||||||
|
public static final int STRATEGY_REJECT = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge strategy that selects the first of conflicting values.
|
||||||
|
*/
|
||||||
|
public static final int STRATEGY_FIRST = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge strategy that selects the last of conflicting values.
|
||||||
|
*/
|
||||||
|
public static final int STRATEGY_LAST = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge strategy that selects the "minimum" of conflicting values which are
|
||||||
|
* {@link Comparable} with each other.
|
||||||
|
*/
|
||||||
|
public static final int STRATEGY_COMPARABLE_MIN = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge strategy that selects the "maximum" of conflicting values which are
|
||||||
|
* {@link Comparable} with each other.
|
||||||
|
*/
|
||||||
|
public static final int STRATEGY_COMPARABLE_MAX = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge strategy that numerically adds both conflicting values.
|
||||||
|
*/
|
||||||
|
public static final int STRATEGY_NUMBER_ADD = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge strategy that numerically increments the first conflicting value by
|
||||||
|
* {@code 1} and ignores the last conflicting value.
|
||||||
|
*/
|
||||||
|
public static final int STRATEGY_NUMBER_INCREMENT_FIRST = 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge strategy that numerically increments the first conflicting value by
|
||||||
|
* {@code 1} and also numerically adds both conflicting values.
|
||||||
|
*/
|
||||||
|
public static final int STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD = 25;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge strategy that combines conflicting values using a boolean "and"
|
||||||
|
* operation.
|
||||||
|
*/
|
||||||
|
public static final int STRATEGY_BOOLEAN_AND = 30;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge strategy that combines conflicting values using a boolean "or"
|
||||||
|
* operation.
|
||||||
|
*/
|
||||||
|
public static final int STRATEGY_BOOLEAN_OR = 40;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge strategy that combines two conflicting array values by appending
|
||||||
|
* the last array after the first array.
|
||||||
|
*/
|
||||||
|
public static final int STRATEGY_ARRAY_APPEND = 50;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge strategy that combines two conflicting {@link ArrayList} values by
|
||||||
|
* appending the last {@link ArrayList} after the first {@link ArrayList}.
|
||||||
|
*/
|
||||||
|
public static final int STRATEGY_ARRAY_LIST_APPEND = 60;
|
||||||
|
|
||||||
|
@IntDef(flag = false, prefix = { "STRATEGY_" }, value = {
|
||||||
|
STRATEGY_REJECT,
|
||||||
|
STRATEGY_FIRST,
|
||||||
|
STRATEGY_LAST,
|
||||||
|
STRATEGY_COMPARABLE_MIN,
|
||||||
|
STRATEGY_COMPARABLE_MAX,
|
||||||
|
STRATEGY_NUMBER_ADD,
|
||||||
|
STRATEGY_NUMBER_INCREMENT_FIRST,
|
||||||
|
STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD,
|
||||||
|
STRATEGY_BOOLEAN_AND,
|
||||||
|
STRATEGY_BOOLEAN_OR,
|
||||||
|
STRATEGY_ARRAY_APPEND,
|
||||||
|
STRATEGY_ARRAY_LIST_APPEND,
|
||||||
|
})
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
public @interface Strategy {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a empty set of rules for merging two {@link Bundle} instances.
|
||||||
|
*/
|
||||||
|
public BundleMerger() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private BundleMerger(@NonNull Parcel in) {
|
||||||
|
mDefaultStrategy = in.readInt();
|
||||||
|
final int N = in.readInt();
|
||||||
|
for (int i = 0; i < N; i++) {
|
||||||
|
mStrategies.put(in.readString(), in.readInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(@NonNull Parcel out, int flags) {
|
||||||
|
out.writeInt(mDefaultStrategy);
|
||||||
|
final int N = mStrategies.size();
|
||||||
|
out.writeInt(N);
|
||||||
|
for (int i = 0; i < N; i++) {
|
||||||
|
out.writeString(mStrategies.keyAt(i));
|
||||||
|
out.writeInt(mStrategies.valueAt(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the default merge strategy to be used when there isn't a
|
||||||
|
* more-specific strategy defined for a particular key via
|
||||||
|
* {@link #setMergeStrategy(String, int)}.
|
||||||
|
*/
|
||||||
|
public void setDefaultMergeStrategy(@Strategy int strategy) {
|
||||||
|
mDefaultStrategy = strategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the merge strategy to be used for the given key.
|
||||||
|
* <p>
|
||||||
|
* Subsequent calls for the same key will overwrite any previously
|
||||||
|
* configured strategy.
|
||||||
|
*/
|
||||||
|
public void setMergeStrategy(@NonNull String key, @Strategy int strategy) {
|
||||||
|
mStrategies.put(key, strategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the merge strategy to be used for the given key, as defined by
|
||||||
|
* {@link #setMergeStrategy(String, int)}.
|
||||||
|
* <p>
|
||||||
|
* If no specific strategy has been configured for the given key, this
|
||||||
|
* returns {@link #setDefaultMergeStrategy(int)}.
|
||||||
|
*/
|
||||||
|
public @Strategy int getMergeStrategy(@NonNull String key) {
|
||||||
|
return (int) mStrategies.getOrDefault(key, mDefaultStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a {@link BinaryOperator} which applies the strategies configured
|
||||||
|
* in this object to merge the two given {@link Bundle} arguments.
|
||||||
|
*/
|
||||||
|
public BinaryOperator<Bundle> asBinaryOperator() {
|
||||||
|
return this::merge;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the strategies configured in this object to merge the two given
|
||||||
|
* {@link Bundle} arguments.
|
||||||
|
*
|
||||||
|
* @return the merged {@link Bundle} result. If one argument is {@code null}
|
||||||
|
* it will return the other argument. If both arguments are null it
|
||||||
|
* will return {@code null}.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public @Nullable Bundle merge(@Nullable Bundle first, @Nullable Bundle last) {
|
||||||
|
if (first == null && last == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (first == null) {
|
||||||
|
first = Bundle.EMPTY;
|
||||||
|
}
|
||||||
|
if (last == null) {
|
||||||
|
last = Bundle.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start by bulk-copying all values without attempting to unpack any
|
||||||
|
// custom parcelables; we'll circle back to handle conflicts below
|
||||||
|
final Bundle res = new Bundle();
|
||||||
|
res.putAll(first);
|
||||||
|
res.putAll(last);
|
||||||
|
|
||||||
|
final ArraySet<String> conflictingKeys = new ArraySet<>();
|
||||||
|
conflictingKeys.addAll(first.keySet());
|
||||||
|
conflictingKeys.retainAll(last.keySet());
|
||||||
|
for (int i = 0; i < conflictingKeys.size(); i++) {
|
||||||
|
final String key = conflictingKeys.valueAt(i);
|
||||||
|
final int strategy = getMergeStrategy(key);
|
||||||
|
final Object firstValue = first.get(key);
|
||||||
|
final Object lastValue = last.get(key);
|
||||||
|
try {
|
||||||
|
res.putObject(key, merge(strategy, firstValue, lastValue));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w(TAG, "Failed to merge key " + key + " with " + firstValue + " and "
|
||||||
|
+ lastValue + " using strategy " + strategy, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge the two given values. If only one of the values is defined, it
|
||||||
|
* always wins, otherwise the given strategy is applied.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
public static @Nullable Object merge(@Strategy int strategy,
|
||||||
|
@Nullable Object first, @Nullable Object last) {
|
||||||
|
if (first == null) return last;
|
||||||
|
if (last == null) return first;
|
||||||
|
|
||||||
|
if (first.getClass() != last.getClass()) {
|
||||||
|
throw new IllegalArgumentException("Merging requires consistent classes; first "
|
||||||
|
+ first.getClass() + " last " + last.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (strategy) {
|
||||||
|
case STRATEGY_REJECT:
|
||||||
|
// Only actually reject when the values are different
|
||||||
|
if (Objects.deepEquals(first, last)) {
|
||||||
|
return first;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
case STRATEGY_FIRST:
|
||||||
|
return first;
|
||||||
|
case STRATEGY_LAST:
|
||||||
|
return last;
|
||||||
|
case STRATEGY_COMPARABLE_MIN:
|
||||||
|
return comparableMin(first, last);
|
||||||
|
case STRATEGY_COMPARABLE_MAX:
|
||||||
|
return comparableMax(first, last);
|
||||||
|
case STRATEGY_NUMBER_ADD:
|
||||||
|
return numberAdd(first, last);
|
||||||
|
case STRATEGY_NUMBER_INCREMENT_FIRST:
|
||||||
|
return numberIncrementFirst(first, last);
|
||||||
|
case STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD:
|
||||||
|
return numberAdd(numberIncrementFirst(first, last), last);
|
||||||
|
case STRATEGY_BOOLEAN_AND:
|
||||||
|
return booleanAnd(first, last);
|
||||||
|
case STRATEGY_BOOLEAN_OR:
|
||||||
|
return booleanOr(first, last);
|
||||||
|
case STRATEGY_ARRAY_APPEND:
|
||||||
|
return arrayAppend(first, last);
|
||||||
|
case STRATEGY_ARRAY_LIST_APPEND:
|
||||||
|
return arrayListAppend(first, last);
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static @NonNull Object comparableMin(@NonNull Object first, @NonNull Object last) {
|
||||||
|
return ((Comparable<Object>) first).compareTo(last) < 0 ? first : last;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static @NonNull Object comparableMax(@NonNull Object first, @NonNull Object last) {
|
||||||
|
return ((Comparable<Object>) first).compareTo(last) >= 0 ? first : last;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NonNull Object numberAdd(@NonNull Object first, @NonNull Object last) {
|
||||||
|
if (first instanceof Integer) {
|
||||||
|
return ((Integer) first) + ((Integer) last);
|
||||||
|
} else if (first instanceof Long) {
|
||||||
|
return ((Long) first) + ((Long) last);
|
||||||
|
} else if (first instanceof Float) {
|
||||||
|
return ((Float) first) + ((Float) last);
|
||||||
|
} else if (first instanceof Double) {
|
||||||
|
return ((Double) first) + ((Double) last);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unable to add " + first.getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NonNull Number numberIncrementFirst(@NonNull Object first,
|
||||||
|
@NonNull Object last) {
|
||||||
|
if (first instanceof Integer) {
|
||||||
|
return ((Integer) first) + 1;
|
||||||
|
} else if (first instanceof Long) {
|
||||||
|
return ((Long) first) + 1L;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unable to add " + first.getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NonNull Object booleanAnd(@NonNull Object first, @NonNull Object last) {
|
||||||
|
return ((Boolean) first) && ((Boolean) last);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NonNull Object booleanOr(@NonNull Object first, @NonNull Object last) {
|
||||||
|
return ((Boolean) first) || ((Boolean) last);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NonNull Object arrayAppend(@NonNull Object first, @NonNull Object last) {
|
||||||
|
if (!first.getClass().isArray()) {
|
||||||
|
throw new IllegalArgumentException("Unable to append " + first.getClass());
|
||||||
|
}
|
||||||
|
final Class<?> clazz = first.getClass().getComponentType();
|
||||||
|
final int firstLength = Array.getLength(first);
|
||||||
|
final int lastLength = Array.getLength(last);
|
||||||
|
final Object res = Array.newInstance(clazz, firstLength + lastLength);
|
||||||
|
System.arraycopy(first, 0, res, 0, firstLength);
|
||||||
|
System.arraycopy(last, 0, res, firstLength, lastLength);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static @NonNull Object arrayListAppend(@NonNull Object first, @NonNull Object last) {
|
||||||
|
if (!(first instanceof ArrayList)) {
|
||||||
|
throw new IllegalArgumentException("Unable to append " + first.getClass());
|
||||||
|
}
|
||||||
|
final ArrayList<Object> firstList = (ArrayList<Object>) first;
|
||||||
|
final ArrayList<Object> lastList = (ArrayList<Object>) last;
|
||||||
|
final ArrayList<Object> res = new ArrayList<>(firstList.size() + lastList.size());
|
||||||
|
res.addAll(firstList);
|
||||||
|
res.addAll(lastList);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final @android.annotation.NonNull Parcelable.Creator<BundleMerger> CREATOR =
|
||||||
|
new Parcelable.Creator<BundleMerger>() {
|
||||||
|
@Override
|
||||||
|
public BundleMerger createFromParcel(Parcel in) {
|
||||||
|
return new BundleMerger(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BundleMerger[] newArray(int size) {
|
||||||
|
return new BundleMerger[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user