Merge "Add hidden API to allow PendingIntent sender to allow/block BAL"
This commit is contained in:
@@ -56,6 +56,7 @@ import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.BroadcastOptions;
|
||||
@@ -307,6 +308,7 @@ public class AlarmManagerService extends SystemService {
|
||||
BroadcastOptions mOptsWithFgs = BroadcastOptions.makeBasic();
|
||||
BroadcastOptions mOptsWithoutFgs = BroadcastOptions.makeBasic();
|
||||
BroadcastOptions mOptsTimeBroadcast = BroadcastOptions.makeBasic();
|
||||
ActivityOptions mActivityOptsRestrictBal = ActivityOptions.makeBasic();
|
||||
|
||||
// TODO(b/172085676): Move inside alarm store.
|
||||
private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser =
|
||||
@@ -1610,6 +1612,10 @@ public class AlarmManagerService extends SystemService {
|
||||
@Override
|
||||
public void onStart() {
|
||||
mInjector.init();
|
||||
mOptsWithFgs.setPendingIntentBackgroundActivityLaunchAllowed(false);
|
||||
mOptsWithoutFgs.setPendingIntentBackgroundActivityLaunchAllowed(false);
|
||||
mOptsTimeBroadcast.setPendingIntentBackgroundActivityLaunchAllowed(false);
|
||||
mActivityOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false);
|
||||
mMetricsHelper = new MetricsHelper(getContext(), mLock);
|
||||
|
||||
mListenerDeathRecipient = new IBinder.DeathRecipient() {
|
||||
@@ -4339,7 +4345,16 @@ public class AlarmManagerService extends SystemService {
|
||||
for (int i = 0; i < triggerList.size(); i++) {
|
||||
Alarm alarm = triggerList.get(i);
|
||||
try {
|
||||
alarm.operation.send();
|
||||
// Disallow AlarmManager to start random background activity.
|
||||
final Bundle bundle;
|
||||
if (alarm.operation.isActivity()) {
|
||||
bundle = mActivityOptsRestrictBal.toBundle();
|
||||
} else {
|
||||
bundle = null;
|
||||
}
|
||||
alarm.operation.send(/* context */ null, /* code */0, /* intent */
|
||||
null, /* onFinished */null, /* handler */
|
||||
null, /* requiredPermission */ null, bundle);
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
if (alarm.repeatInterval > 0) {
|
||||
// This IntentSender is no longer valid, but this
|
||||
@@ -4901,9 +4916,19 @@ public class AlarmManagerService extends SystemService {
|
||||
mSendCount++;
|
||||
|
||||
try {
|
||||
final Bundle bundle;
|
||||
if (alarm.mIdleOptions != null) {
|
||||
bundle = alarm.mIdleOptions;
|
||||
} else {
|
||||
if (alarm.operation.isActivity()) {
|
||||
bundle = mActivityOptsRestrictBal.toBundle();
|
||||
} else {
|
||||
bundle = null;
|
||||
}
|
||||
}
|
||||
alarm.operation.send(getContext(), 0,
|
||||
mBackgroundIntent.putExtra(Intent.EXTRA_ALARM_COUNT, alarm.count),
|
||||
mDeliveryTracker, mHandler, null, alarm.mIdleOptions);
|
||||
mDeliveryTracker, mHandler, null, bundle);
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
if (alarm.repeatInterval > 0) {
|
||||
// This IntentSender is no longer valid, but this
|
||||
|
||||
@@ -166,6 +166,14 @@ public class ActivityOptions {
|
||||
*/
|
||||
public static final String KEY_SPLASH_SCREEN_THEME = "android.activity.splashScreenTheme";
|
||||
|
||||
/**
|
||||
* PendingIntent caller allows activity start even if PendingIntent creator is in background.
|
||||
* This only works if the PendingIntent caller is allowed to start background activities,
|
||||
* for example if it's in the foreground, or has BAL permission.
|
||||
* @hide
|
||||
*/
|
||||
public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED =
|
||||
"android.pendingIntent.backgroundActivityAllowed";
|
||||
/**
|
||||
* Callback for when the last frame of the animation is played.
|
||||
* @hide
|
||||
@@ -380,6 +388,12 @@ public class ActivityOptions {
|
||||
/** @hide */
|
||||
public static final int ANIM_REMOTE_ANIMATION = 13;
|
||||
|
||||
/**
|
||||
* Default value for KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED.
|
||||
* @hide
|
||||
**/
|
||||
public static final boolean PENDING_INTENT_BAL_ALLOWED_DEFAULT = true;
|
||||
|
||||
private String mPackageName;
|
||||
private Rect mLaunchBounds;
|
||||
private int mAnimationType = ANIM_UNDEFINED;
|
||||
@@ -431,6 +445,7 @@ public class ActivityOptions {
|
||||
private String mSplashScreenThemeResName;
|
||||
@SplashScreen.SplashScreenStyle
|
||||
private int mSplashScreenStyle;
|
||||
private boolean mPendingIntentBalAllowed = PENDING_INTENT_BAL_ALLOWED_DEFAULT;
|
||||
private boolean mRemoveWithTaskOrganizer;
|
||||
private boolean mLaunchedFromBubble;
|
||||
private boolean mTransientLaunch;
|
||||
@@ -1184,6 +1199,8 @@ public class ActivityOptions {
|
||||
mRemoteTransition = opts.getParcelable(KEY_REMOTE_TRANSITION);
|
||||
mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION);
|
||||
mSplashScreenThemeResName = opts.getString(KEY_SPLASH_SCREEN_THEME);
|
||||
mPendingIntentBalAllowed = opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
|
||||
PENDING_INTENT_BAL_ALLOWED_DEFAULT);
|
||||
mRemoveWithTaskOrganizer = opts.getBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER);
|
||||
mLaunchedFromBubble = opts.getBoolean(KEY_LAUNCHED_FROM_BUBBLE);
|
||||
mTransientLaunch = opts.getBoolean(KEY_TRANSIENT_LAUNCH);
|
||||
@@ -1399,6 +1416,24 @@ public class ActivityOptions {
|
||||
return mSplashScreenStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set PendingIntent activity is allowed to be started in the background if the caller
|
||||
* can start background activities.
|
||||
* @hide
|
||||
*/
|
||||
public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
|
||||
mPendingIntentBalAllowed = allowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PendingIntent activity is allowed to be started in the background if the caller
|
||||
* can start background activities.
|
||||
* @hide
|
||||
*/
|
||||
public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
|
||||
return mPendingIntentBalAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the activity is to be launched into LockTask mode.
|
||||
*
|
||||
@@ -1972,6 +2007,7 @@ public class ActivityOptions {
|
||||
if (mSplashScreenThemeResName != null && !mSplashScreenThemeResName.isEmpty()) {
|
||||
b.putString(KEY_SPLASH_SCREEN_THEME, mSplashScreenThemeResName);
|
||||
}
|
||||
b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
|
||||
if (mRemoveWithTaskOrganizer) {
|
||||
b.putBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER, mRemoveWithTaskOrganizer);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ public class BroadcastOptions {
|
||||
private int mMaxManifestReceiverApiLevel = Build.VERSION_CODES.CUR_DEVELOPMENT;
|
||||
private boolean mDontSendToRestrictedApps = false;
|
||||
private boolean mAllowBackgroundActivityStarts;
|
||||
private boolean mPendingIntentBalAllowed = ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT;
|
||||
|
||||
/**
|
||||
* How long to temporarily put an app on the power allowlist when executing this broadcast
|
||||
@@ -78,6 +79,16 @@ public class BroadcastOptions {
|
||||
private static final String KEY_DONT_SEND_TO_RESTRICTED_APPS =
|
||||
"android:broadcast.dontSendToRestrictedApps";
|
||||
|
||||
/**
|
||||
* PendingIntent caller allows activity start even if PendingIntent creator is in background.
|
||||
* This only works if the PendingIntent caller is allowed to start background activities,
|
||||
* for example if it's in the foreground, or has BAL permission.
|
||||
* TODO: Merge it with ActivityOptions.
|
||||
* @hide
|
||||
*/
|
||||
public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED =
|
||||
"android.pendingIntent.backgroundActivityAllowed";
|
||||
|
||||
/**
|
||||
* Corresponds to {@link #setBackgroundActivityStartsAllowed}.
|
||||
*/
|
||||
@@ -130,6 +141,8 @@ public class BroadcastOptions {
|
||||
mDontSendToRestrictedApps = opts.getBoolean(KEY_DONT_SEND_TO_RESTRICTED_APPS, false);
|
||||
mAllowBackgroundActivityStarts = opts.getBoolean(KEY_ALLOW_BACKGROUND_ACTIVITY_STARTS,
|
||||
false);
|
||||
mPendingIntentBalAllowed = opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
|
||||
ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -300,6 +313,26 @@ public class BroadcastOptions {
|
||||
return mAllowBackgroundActivityStarts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set PendingIntent activity is allowed to be started in the background if the caller
|
||||
* can start background activities.
|
||||
* TODO: Merge it with ActivityOptions.
|
||||
* @hide
|
||||
*/
|
||||
public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
|
||||
mPendingIntentBalAllowed = allowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PendingIntent activity is allowed to be started in the background if the caller
|
||||
* can start background activities.
|
||||
* TODO: Merge it with ActivityOptions.
|
||||
* @hide
|
||||
*/
|
||||
public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
|
||||
return mPendingIntentBalAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the created options as a Bundle, which can be passed to
|
||||
* {@link android.content.Context#sendBroadcast(android.content.Intent)
|
||||
@@ -328,6 +361,8 @@ public class BroadcastOptions {
|
||||
if (mAllowBackgroundActivityStarts) {
|
||||
b.putBoolean(KEY_ALLOW_BACKGROUND_ACTIVITY_STARTS, true);
|
||||
}
|
||||
// TODO: Add API for BroadcastOptions and have a shared base class with ActivityOptions.
|
||||
b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
|
||||
return b.isEmpty() ? null : b;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,9 @@ import android.app.IApplicationThread;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.ProfilerInfo;
|
||||
import android.app.WaitResult;
|
||||
import android.app.compat.CompatChanges;
|
||||
import android.compat.annotation.ChangeId;
|
||||
import android.compat.annotation.EnabledSince;
|
||||
import android.content.ComponentName;
|
||||
import android.content.IIntentSender;
|
||||
import android.content.Intent;
|
||||
@@ -101,6 +104,7 @@ import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Process;
|
||||
@@ -146,6 +150,13 @@ class ActivityStarter {
|
||||
private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING;
|
||||
private static final int INVALID_LAUNCH_MODE = -1;
|
||||
|
||||
/**
|
||||
* Feature flag to protect PendingIntent being abused to start background activity.
|
||||
*/
|
||||
@ChangeId
|
||||
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
|
||||
static final long ENABLE_PENDING_INTENT_BAL_OPTION = 192341120L;
|
||||
|
||||
private final ActivityTaskManagerService mService;
|
||||
private final RootWindowContainer mRootWindowContainer;
|
||||
private final ActivityTaskSupervisor mSupervisor;
|
||||
@@ -987,6 +998,10 @@ class ActivityStarter {
|
||||
abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid,
|
||||
callingPackage);
|
||||
|
||||
// Merge the two options bundles, while realCallerOptions takes precedence.
|
||||
ActivityOptions checkedOptions = options != null
|
||||
? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null;
|
||||
|
||||
boolean restrictedBgActivity = false;
|
||||
if (!abort) {
|
||||
try {
|
||||
@@ -995,15 +1010,12 @@ class ActivityStarter {
|
||||
restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid,
|
||||
callingPid, callingPackage, realCallingUid, realCallingPid, callerApp,
|
||||
request.originatingPendingIntent, request.allowBackgroundActivityStart,
|
||||
intent);
|
||||
intent, checkedOptions);
|
||||
} finally {
|
||||
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge the two options bundles, while realCallerOptions takes precedence.
|
||||
ActivityOptions checkedOptions = options != null
|
||||
? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null;
|
||||
if (request.allowPendingRemoteAnimationRegistryLookup) {
|
||||
checkedOptions = mService.getActivityStartController()
|
||||
.getPendingRemoteAnimationRegistry()
|
||||
@@ -1242,10 +1254,22 @@ class ActivityStarter {
|
||||
return activity != null && packageName.equals(activity.getPackageName());
|
||||
}
|
||||
|
||||
private static boolean isPendingIntentBalAllowedByCaller(ActivityOptions activityOptions) {
|
||||
if (activityOptions == null) {
|
||||
return ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT;
|
||||
}
|
||||
final Bundle options = activityOptions.toBundle();
|
||||
if (options == null) {
|
||||
return ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT;
|
||||
}
|
||||
return options.getBoolean(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
|
||||
ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT);
|
||||
}
|
||||
|
||||
boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid,
|
||||
final String callingPackage, int realCallingUid, int realCallingPid,
|
||||
WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent,
|
||||
boolean allowBackgroundActivityStart, Intent intent) {
|
||||
boolean allowBackgroundActivityStart, Intent intent, ActivityOptions checkedOptions) {
|
||||
// don't abort for the most important UIDs
|
||||
final int callingAppId = UserHandle.getAppId(callingUid);
|
||||
if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID
|
||||
@@ -1314,7 +1338,29 @@ class ActivityStarter {
|
||||
? isCallingUidPersistentSystemProcess
|
||||
: (realCallingAppId == Process.SYSTEM_UID)
|
||||
|| realCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
|
||||
if (realCallingUid != callingUid) {
|
||||
|
||||
// If caller a legacy app, we won't check if caller has BAL permission.
|
||||
final boolean isPiBalOptionEnabled = CompatChanges.isChangeEnabled(
|
||||
ENABLE_PENDING_INTENT_BAL_OPTION, callingUid);
|
||||
|
||||
// Legacy behavior allows to use caller foreground state to bypass BAL restriction.
|
||||
final boolean balAllowedByPiSender =
|
||||
isPendingIntentBalAllowedByCaller(checkedOptions);
|
||||
|
||||
if (balAllowedByPiSender && realCallingUid != callingUid) {
|
||||
if (isPiBalOptionEnabled) {
|
||||
if (ActivityManager.checkComponentPermission(
|
||||
android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
|
||||
realCallingUid, -1, true)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
if (DEBUG_ACTIVITY_STARTS) {
|
||||
Slog.d(TAG, "Activity start allowed: realCallingUid (" + realCallingUid
|
||||
+ ") has BAL permission.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// don't abort if the realCallingUid has a visible window
|
||||
// TODO(b/171459802): We should check appSwitchAllowed also
|
||||
if (realCallingUidHasAnyVisibleWindow) {
|
||||
@@ -1389,9 +1435,9 @@ class ActivityStarter {
|
||||
// If we don't have callerApp at this point, no caller was provided to startActivity().
|
||||
// That's the case for PendingIntent-based starts, since the creator's process might not be
|
||||
// up and alive. If that's the case, we retrieve the WindowProcessController for the send()
|
||||
// caller, so that we can make the decision based on its state.
|
||||
// caller if caller allows, so that we can make the decision based on its state.
|
||||
int callerAppUid = callingUid;
|
||||
if (callerApp == null) {
|
||||
if (callerApp == null && balAllowedByPiSender) {
|
||||
callerApp = mService.getProcessController(realCallingPid, realCallingUid);
|
||||
callerAppUid = realCallingUid;
|
||||
}
|
||||
|
||||
@@ -2077,7 +2077,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
|
||||
final ActivityStarter starter = getActivityStartController().obtainStarter(
|
||||
null /* intent */, "moveTaskToFront");
|
||||
if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage, -1,
|
||||
-1, callerApp, null, false, null)) {
|
||||
-1, callerApp, null, false, null, null)) {
|
||||
if (!isBackgroundActivityStartsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ class AppTaskImpl extends IAppTask.Stub {
|
||||
final ActivityStarter starter = mService.getActivityStartController().obtainStarter(
|
||||
null /* intent */, "moveToFront");
|
||||
if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid,
|
||||
callingPackage, -1, -1, callerApp, null, false, null)) {
|
||||
callingPackage, -1, -1, callerApp, null, false, null, null)) {
|
||||
if (!mService.isBackgroundActivityStartsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ import static org.mockito.Mockito.times;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.BroadcastOptions;
|
||||
@@ -550,15 +551,24 @@ public class AlarmManagerServiceTest {
|
||||
FLAG_STANDALONE, null, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null, 0);
|
||||
}
|
||||
|
||||
|
||||
private PendingIntent getNewMockPendingIntent() {
|
||||
return getNewMockPendingIntent(TEST_CALLING_UID, TEST_CALLING_PACKAGE);
|
||||
return getNewMockPendingIntent(false);
|
||||
}
|
||||
|
||||
private PendingIntent getNewMockPendingIntent(boolean isActivity) {
|
||||
return getNewMockPendingIntent(TEST_CALLING_UID, TEST_CALLING_PACKAGE, isActivity);
|
||||
}
|
||||
|
||||
private PendingIntent getNewMockPendingIntent(int creatorUid, String creatorPackage) {
|
||||
return getNewMockPendingIntent(creatorUid, creatorPackage, false);
|
||||
}
|
||||
|
||||
private PendingIntent getNewMockPendingIntent(int creatorUid, String creatorPackage,
|
||||
boolean isActivity) {
|
||||
final PendingIntent mockPi = mock(PendingIntent.class, Answers.RETURNS_DEEP_STUBS);
|
||||
when(mockPi.getCreatorUid()).thenReturn(creatorUid);
|
||||
when(mockPi.getCreatorPackage()).thenReturn(creatorPackage);
|
||||
when(mockPi.isActivity()).thenReturn(isActivity);
|
||||
return mockPi;
|
||||
}
|
||||
|
||||
@@ -2801,21 +2811,57 @@ public class AlarmManagerServiceTest {
|
||||
anyString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void idleOptionsSentOnExpiration() throws Exception {
|
||||
private void optionsSentOnExpiration(boolean isActivity, Bundle idleOptions)
|
||||
throws Exception {
|
||||
final long triggerTime = mNowElapsedTest + 5000;
|
||||
final PendingIntent alarmPi = getNewMockPendingIntent();
|
||||
final Bundle idleOptions = new Bundle();
|
||||
idleOptions.putChar("TEST_CHAR_KEY", 'x');
|
||||
idleOptions.putInt("TEST_INT_KEY", 53);
|
||||
final PendingIntent alarmPi = getNewMockPendingIntent(isActivity);
|
||||
setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, 0, alarmPi, 0, 0, TEST_CALLING_UID,
|
||||
idleOptions);
|
||||
|
||||
mNowElapsedTest = mTestTimer.getElapsed();
|
||||
mTestTimer.expire();
|
||||
|
||||
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
|
||||
verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class),
|
||||
any(), any(Handler.class), isNull(), eq(idleOptions));
|
||||
any(), any(Handler.class), isNull(), bundleCaptor.capture());
|
||||
if (idleOptions != null) {
|
||||
assertEquals(idleOptions, bundleCaptor.getValue());
|
||||
} else {
|
||||
if (isActivity) {
|
||||
assertFalse("BAL flag needs to be false in alarm manager",
|
||||
bundleCaptor.getValue().getBoolean(
|
||||
ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
|
||||
true));
|
||||
} else {
|
||||
assertNull(bundleCaptor.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void activityIdleOptionsSentOnExpiration() throws Exception {
|
||||
final Bundle idleOptions = new Bundle();
|
||||
idleOptions.putChar("TEST_CHAR_KEY", 'x');
|
||||
idleOptions.putInt("TEST_INT_KEY", 53);
|
||||
optionsSentOnExpiration(true, idleOptions);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void broadcastIdleOptionsSentOnExpiration() throws Exception {
|
||||
final Bundle idleOptions = new Bundle();
|
||||
idleOptions.putChar("TEST_CHAR_KEY", 'x');
|
||||
idleOptions.putInt("TEST_INT_KEY", 53);
|
||||
optionsSentOnExpiration(false, idleOptions);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyActivityOptionsSentOnExpiration() throws Exception {
|
||||
optionsSentOnExpiration(true, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyBroadcastOptionsSentOnExpiration() throws Exception {
|
||||
optionsSentOnExpiration(false, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user