[DO NOT MERGE] Backport BAL restrictions from T to S, this blocks apps from using Alarm

Manager to bypass BAL restrictions.

Test: atest-src BackgroundActivityLaunchTest
Bug: 195756028
Change-Id: I33112ff59d913d8a7244289fe1a43512844e902a
(cherry picked from commit 7a41e2fbc9)
This commit is contained in:
Christophe Pinelli
2022-12-28 04:31:11 +00:00
parent 7c27e2d431
commit 9624f4f1f8
9 changed files with 222 additions and 30 deletions

View File

@@ -56,6 +56,7 @@ import android.annotation.NonNull;
import android.annotation.UserIdInt; import android.annotation.UserIdInt;
import android.app.Activity; import android.app.Activity;
import android.app.ActivityManagerInternal; import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.AppOpsManager; import android.app.AppOpsManager;
import android.app.BroadcastOptions; import android.app.BroadcastOptions;
@@ -317,6 +318,8 @@ public class AlarmManagerService extends SystemService {
private final SparseBooleanArray mPendingSendNextAlarmClockChangedForUser = private final SparseBooleanArray mPendingSendNextAlarmClockChangedForUser =
new SparseBooleanArray(); new SparseBooleanArray();
private boolean mNextAlarmClockMayChange; private boolean mNextAlarmClockMayChange;
ActivityOptions mActivityOptsRestrictBal = ActivityOptions.makeBasic();
BroadcastOptions mBroadcastOptsRestrictBal = BroadcastOptions.makeBasic();
@GuardedBy("mLock") @GuardedBy("mLock")
private final Runnable mAlarmClockUpdater = () -> mNextAlarmClockMayChange = true; private final Runnable mAlarmClockUpdater = () -> mNextAlarmClockMayChange = true;
@@ -1611,6 +1614,11 @@ public class AlarmManagerService extends SystemService {
@Override @Override
public void onStart() { public void onStart() {
mInjector.init(); mInjector.init();
mOptsWithFgs.setPendingIntentBackgroundActivityLaunchAllowed(false);
mOptsWithoutFgs.setPendingIntentBackgroundActivityLaunchAllowed(false);
mOptsTimeBroadcast.setPendingIntentBackgroundActivityLaunchAllowed(false);
mActivityOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false);
mBroadcastOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false);
mMetricsHelper = new MetricsHelper(getContext(), mLock); mMetricsHelper = new MetricsHelper(getContext(), mLock);
mListenerDeathRecipient = new IBinder.DeathRecipient() { mListenerDeathRecipient = new IBinder.DeathRecipient() {
@@ -4306,6 +4314,14 @@ public class AlarmManagerService extends SystemService {
return alarm.creatorUid; return alarm.creatorUid;
} }
private Bundle getAlarmOperationBundle(Alarm alarm) {
if (alarm.mIdleOptions != null) {
return alarm.mIdleOptions;
} else if (alarm.operation.isActivity()) {
return mActivityOptsRestrictBal.toBundle();
}
return mBroadcastOptsRestrictBal.toBundle();
}
@VisibleForTesting @VisibleForTesting
class AlarmHandler extends Handler { class AlarmHandler extends Handler {
@@ -4344,7 +4360,11 @@ public class AlarmManagerService extends SystemService {
for (int i = 0; i < triggerList.size(); i++) { for (int i = 0; i < triggerList.size(); i++) {
Alarm alarm = triggerList.get(i); Alarm alarm = triggerList.get(i);
try { try {
alarm.operation.send(); // Disallow AlarmManager to start random background activity.
final Bundle bundle = getAlarmOperationBundle(alarm);
alarm.operation.send(/* context */ null, /* code */0, /* intent */
null, /* onFinished */null, /* handler */
null, /* requiredPermission */ null, bundle);
} catch (PendingIntent.CanceledException e) { } catch (PendingIntent.CanceledException e) {
if (alarm.repeatInterval > 0) { if (alarm.repeatInterval > 0) {
// This IntentSender is no longer valid, but this // This IntentSender is no longer valid, but this
@@ -4908,9 +4928,10 @@ public class AlarmManagerService extends SystemService {
mSendCount++; mSendCount++;
try { try {
final Bundle bundle = getAlarmOperationBundle(alarm);
alarm.operation.send(getContext(), 0, alarm.operation.send(getContext(), 0,
mBackgroundIntent.putExtra(Intent.EXTRA_ALARM_COUNT, alarm.count), mBackgroundIntent.putExtra(Intent.EXTRA_ALARM_COUNT, alarm.count),
mDeliveryTracker, mHandler, null, alarm.mIdleOptions); mDeliveryTracker, mHandler, null, bundle);
} catch (PendingIntent.CanceledException e) { } catch (PendingIntent.CanceledException e) {
if (alarm.repeatInterval > 0) { if (alarm.repeatInterval > 0) {
// This IntentSender is no longer valid, but this // This IntentSender is no longer valid, but this

View File

@@ -69,7 +69,7 @@ import java.util.ArrayList;
* {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle) * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
* Context.startActivity(Intent, Bundle)} and related methods. * Context.startActivity(Intent, Bundle)} and related methods.
*/ */
public class ActivityOptions { public class ActivityOptions extends ComponentOptions {
private static final String TAG = "ActivityOptions"; private static final String TAG = "ActivityOptions";
/** /**
@@ -1081,13 +1081,12 @@ public class ActivityOptions {
} }
private ActivityOptions() { private ActivityOptions() {
super();
} }
/** @hide */ /** @hide */
public ActivityOptions(Bundle opts) { public ActivityOptions(Bundle opts) {
// If the remote side sent us bad parcelables, they won't get the super(opts);
// results they want, which is their loss.
opts.setDefusable(true);
mPackageName = opts.getString(KEY_PACKAGE_NAME); mPackageName = opts.getString(KEY_PACKAGE_NAME);
try { try {
@@ -1832,8 +1831,9 @@ public class ActivityOptions {
* object; you must not modify it, but can supply it to the startActivity * object; you must not modify it, but can supply it to the startActivity
* methods that take an options Bundle. * methods that take an options Bundle.
*/ */
@Override
public Bundle toBundle() { public Bundle toBundle() {
Bundle b = new Bundle(); Bundle b = super.toBundle();
if (mPackageName != null) { if (mPackageName != null) {
b.putString(KEY_PACKAGE_NAME, mPackageName); b.putString(KEY_PACKAGE_NAME, mPackageName);
} }

View File

@@ -34,7 +34,7 @@ import android.os.PowerExemptionManager.TempAllowListType;
* {@hide} * {@hide}
*/ */
@SystemApi @SystemApi
public class BroadcastOptions { public class BroadcastOptions extends ComponentOptions {
private long mTemporaryAppAllowlistDuration; private long mTemporaryAppAllowlistDuration;
private @TempAllowListType int mTemporaryAppAllowlistType; private @TempAllowListType int mTemporaryAppAllowlistType;
private @ReasonCode int mTemporaryAppAllowlistReasonCode; private @ReasonCode int mTemporaryAppAllowlistReasonCode;
@@ -108,12 +108,14 @@ public class BroadcastOptions {
} }
private BroadcastOptions() { private BroadcastOptions() {
super();
resetTemporaryAppAllowlist(); resetTemporaryAppAllowlist();
} }
/** @hide */ /** @hide */
@TestApi @TestApi
public BroadcastOptions(@NonNull Bundle opts) { public BroadcastOptions(@NonNull Bundle opts) {
super(opts);
// Match the logic in toBundle(). // Match the logic in toBundle().
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);
@@ -190,6 +192,24 @@ public class BroadcastOptions {
mTemporaryAppAllowlistReason = null; mTemporaryAppAllowlistReason = null;
} }
/**
* Set PendingIntent activity is allowed to be started in the background if the caller
* can start background activities.
* @hide
*/
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
*/
public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
return super.isPendingIntentBackgroundActivityLaunchAllowed();
}
/** /**
* Return {@link #setTemporaryAppAllowlist}. * Return {@link #setTemporaryAppAllowlist}.
* @hide * @hide
@@ -308,8 +328,9 @@ public class 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.
*/ */
@Override
public Bundle toBundle() { public Bundle toBundle() {
Bundle b = new Bundle(); Bundle b = super.toBundle();
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);

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app;
import android.os.Bundle;
/**
* @hide
*/
public class ComponentOptions {
/**
* Default value for KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED.
* @hide
**/
public static final boolean PENDING_INTENT_BAL_ALLOWED_DEFAULT = true;
/**
* 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";
private boolean mPendingIntentBalAllowed = PENDING_INTENT_BAL_ALLOWED_DEFAULT;
ComponentOptions() {
}
ComponentOptions(Bundle opts) {
// If the remote side sent us bad parcelables, they won't get the
// results they want, which is their loss.
opts.setDefusable(true);
setPendingIntentBackgroundActivityLaunchAllowed(
opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
PENDING_INTENT_BAL_ALLOWED_DEFAULT));
}
/**
* 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;
}
/**
* @hide
*/
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
return bundle;
}
}

View File

@@ -310,6 +310,25 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
requiredPermission, null, null, 0, 0, 0, options); requiredPermission, null, null, 0, 0, 0, options);
} }
/**
* Return true if the activity options allows PendingIntent to use caller's BAL permission.
*/
public static boolean isPendingIntentBalAllowedByCaller(
@Nullable ActivityOptions activityOptions) {
if (activityOptions == null) {
return ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT;
}
return isPendingIntentBalAllowedByCaller(activityOptions.toBundle());
}
private static boolean isPendingIntentBalAllowedByCaller(@Nullable Bundle options) {
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);
}
public int sendInner(int code, Intent intent, String resolvedType, IBinder allowlistToken, public int sendInner(int code, Intent intent, String resolvedType, IBinder allowlistToken,
IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo, IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo,
String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle options) { String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle options) {
@@ -436,7 +455,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
// temporarily allow receivers and services to open activities from background if the // temporarily allow receivers and services to open activities from background if the
// PendingIntent.send() caller was foreground at the time of sendInner() call // PendingIntent.send() caller was foreground at the time of sendInner() call
final boolean allowTrampoline = uid != callingUid final boolean allowTrampoline = uid != callingUid
&& controller.mAtmInternal.isUidForeground(callingUid); && controller.mAtmInternal.isUidForeground(callingUid)
&& isPendingIntentBalAllowedByCaller(options);
// note: we on purpose don't pass in the information about the PendingIntent's creator, // note: we on purpose don't pass in the information about the PendingIntent's creator,
// like pid or ProcessRecord, to the ActivityTaskManagerInternal calls below, because // like pid or ProcessRecord, to the ActivityTaskManagerInternal calls below, because

View File

@@ -989,6 +989,10 @@ class ActivityStarter {
abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid, abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid,
callingPackage); callingPackage);
// Merge the two options bundles, while realCallerOptions takes precedence.
ActivityOptions checkedOptions = options != null
? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null;
boolean restrictedBgActivity = false; boolean restrictedBgActivity = false;
if (!abort) { if (!abort) {
try { try {
@@ -997,15 +1001,12 @@ class ActivityStarter {
restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid, restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, callerApp, callingPid, callingPackage, realCallingUid, realCallingPid, callerApp,
request.originatingPendingIntent, request.allowBackgroundActivityStart, request.originatingPendingIntent, request.allowBackgroundActivityStart,
intent); intent, checkedOptions);
} finally { } finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); 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) { if (request.allowPendingRemoteAnimationRegistryLookup) {
checkedOptions = mService.getActivityStartController() checkedOptions = mService.getActivityStartController()
.getPendingRemoteAnimationRegistry() .getPendingRemoteAnimationRegistry()
@@ -1247,7 +1248,7 @@ class ActivityStarter {
boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid, boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid,
final String callingPackage, int realCallingUid, int realCallingPid, final String callingPackage, int realCallingUid, int realCallingPid,
WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent, WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent,
boolean allowBackgroundActivityStart, Intent intent) { boolean allowBackgroundActivityStart, Intent intent, ActivityOptions checkedOptions) {
// don't abort for the most important UIDs // don't abort for the most important UIDs
final int callingAppId = UserHandle.getAppId(callingUid); final int callingAppId = UserHandle.getAppId(callingUid);
if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID
@@ -1318,9 +1319,12 @@ class ActivityStarter {
? isCallingUidPersistentSystemProcess ? isCallingUidPersistentSystemProcess
: (realCallingAppId == Process.SYSTEM_UID) : (realCallingAppId == Process.SYSTEM_UID)
|| realCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI; || realCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
if (realCallingUid != callingUid) {
// don't abort if the realCallingUid has a visible window // Legacy behavior allows to use caller foreground state to bypass BAL restriction.
// TODO(b/171459802): We should check appSwitchAllowed also final boolean balAllowedByPiSender =
PendingIntentRecord.isPendingIntentBalAllowedByCaller(checkedOptions);
if (balAllowedByPiSender && realCallingUid != callingUid) {
if (realCallingUidHasAnyVisibleWindow) { if (realCallingUidHasAnyVisibleWindow) {
if (DEBUG_ACTIVITY_STARTS) { if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG, "Activity start allowed: realCallingUid (" + realCallingUid Slog.d(TAG, "Activity start allowed: realCallingUid (" + realCallingUid
@@ -1393,9 +1397,9 @@ class ActivityStarter {
// If we don't have callerApp at this point, no caller was provided to startActivity(). // 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 // 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() // 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; int callerAppUid = callingUid;
if (callerApp == null) { if (callerApp == null && balAllowedByPiSender) {
callerApp = mService.getProcessController(realCallingPid, realCallingUid); callerApp = mService.getProcessController(realCallingPid, realCallingUid);
callerAppUid = realCallingUid; callerAppUid = realCallingUid;
} }

View File

@@ -2105,7 +2105,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
final ActivityStarter starter = getActivityStartController().obtainStarter( final ActivityStarter starter = getActivityStartController().obtainStarter(
null /* intent */, "moveTaskToFront"); null /* intent */, "moveTaskToFront");
if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage, -1, if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage, -1,
-1, callerApp, null, false, null)) { -1, callerApp, null, false, null, null)) {
if (!isBackgroundActivityStartsEnabled()) { if (!isBackgroundActivityStartsEnabled()) {
return; return;
} }

View File

@@ -108,7 +108,7 @@ class AppTaskImpl extends IAppTask.Stub {
final ActivityStarter starter = mService.getActivityStartController().obtainStarter( final ActivityStarter starter = mService.getActivityStartController().obtainStarter(
null /* intent */, "moveToFront"); null /* intent */, "moveToFront");
if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid, if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid,
callingPackage, -1, -1, callerApp, null, false, null)) { callingPackage, -1, -1, callerApp, null, false, null, null)) {
if (!mService.isBackgroundActivityStartsEnabled()) { if (!mService.isBackgroundActivityStartsEnabled()) {
return; return;
} }

View File

@@ -109,6 +109,7 @@ import static org.mockito.Mockito.times;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.ActivityManagerInternal; import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.AppOpsManager; import android.app.AppOpsManager;
import android.app.BroadcastOptions; import android.app.BroadcastOptions;
@@ -552,13 +553,23 @@ public class AlarmManagerServiceTest {
private PendingIntent getNewMockPendingIntent() { 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) { 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); final PendingIntent mockPi = mock(PendingIntent.class, Answers.RETURNS_DEEP_STUBS);
when(mockPi.getCreatorUid()).thenReturn(creatorUid); when(mockPi.getCreatorUid()).thenReturn(creatorUid);
when(mockPi.getCreatorPackage()).thenReturn(creatorPackage); when(mockPi.getCreatorPackage()).thenReturn(creatorPackage);
when(mockPi.isActivity()).thenReturn(isActivity);
return mockPi; return mockPi;
} }
@@ -2801,21 +2812,53 @@ public class AlarmManagerServiceTest {
anyString())); anyString()));
} }
@Test private void optionsSentOnExpiration(boolean isActivity, Bundle idleOptions)
public void idleOptionsSentOnExpiration() throws Exception { throws Exception {
final long triggerTime = mNowElapsedTest + 5000; final long triggerTime = mNowElapsedTest + 5000;
final PendingIntent alarmPi = getNewMockPendingIntent(); final PendingIntent alarmPi = getNewMockPendingIntent(isActivity);
final Bundle idleOptions = new Bundle();
idleOptions.putChar("TEST_CHAR_KEY", 'x');
idleOptions.putInt("TEST_INT_KEY", 53);
setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, 0, alarmPi, 0, 0, TEST_CALLING_UID, setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, 0, alarmPi, 0, 0, TEST_CALLING_UID,
idleOptions); idleOptions);
mNowElapsedTest = mTestTimer.getElapsed(); mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire(); mTestTimer.expire();
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.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 {
assertFalse("BAL flag needs to be false in alarm manager",
bundleCaptor.getValue().getBoolean(
ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
true));
}
}
@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 @Test