Send a broadcast when an app is granted SCHEDULE_EXACT_ALARM

Bug: 187206399
Test: atest AlarmManagerServiceTest
Test: Manual test with a test app.
- Grant the permission to test app and make sure it can start FGS.
Log:
```
05-07 10:12:08.671  1000  1579  9475 I ActivityManager: Background started FGS: Allowed [callingPackage: com.google.omakoto.testapp; callingUid: 10294; uidState: RCVR; intent: Intent { act=fgs cmp=com.google.omakoto.testapp/.MyFgs }; code:(unknown:207); tempAllowListReason:<broadcast:1000:android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,reason:,reasonCode:(unknown:207),duration:10000,callingUid:1000>; targetSdkVersion:30; callerTargetSdkVersion:30; startForegroundCount:0]
```
- Grant the permission to a different app and make sure the test app
  won't receive the broadcast.

Test: CTS -- Incoming

Change-Id: Iefe3e12dcf51318d8433532ba3048caa69b1edcd
This commit is contained in:
Makoto Onuki
2021-05-06 16:27:01 -07:00
parent 284edfdcc6
commit 306ed287f4
6 changed files with 101 additions and 0 deletions

View File

@@ -22,6 +22,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -138,6 +139,36 @@ public class AlarmManager {
public static final String ACTION_NEXT_ALARM_CLOCK_CHANGED =
"android.app.action.NEXT_ALARM_CLOCK_CHANGED";
/**
* Broadcast Action: An app is granted the
* {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM} permission.
*
* <p>When the user revokes the {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM}
* permission, all alarms scheduled with
* {@link #setExact}, {@link #setExactAndAllowWhileIdle} and
* {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} will be deleted.
*
* <p>When the user grants the {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM},
* this broadcast will be sent. Applications can reschedule all the necessary alarms when
* receiving it.
*
* <p><em>Note:</em>
* Applications are still required to check {@link #canScheduleExactAlarms()}
* before using the above APIs after receiving this broadcast,
* because it's possible that the permission is already revoked again by the time
* applications receive this broadcast.
*
* <p>This broadcast will be sent to both runtime receivers and manifest receivers.
*
* <p>This broadcast is sent as a foreground broadcast.
* See {@link android.content.Intent#FLAG_RECEIVER_FOREGROUND}.
*
* <p>When an application receives this broadcast, it's allowed to start a foreground service.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED =
"android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED";
/** @hide */
@UnsupportedAppUsage
public static final long WINDOW_EXACT = 0;

View File

@@ -251,6 +251,12 @@ public class PowerExemptionManager {
* @hide
*/
public static final int REASON_LOCALE_CHANGED = 206;
/**
* Broadcast
* {@link android.app.AlarmManager#ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED}
* @hide
*/
public static final int REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED = 207;
/* Reason code range 300-399 are reserved for other internal reasons */
/**
* Device idle system allow list, including EXCEPT-IDLE
@@ -386,6 +392,7 @@ public class PowerExemptionManager {
REASON_TIMEZONE_CHANGED,
REASON_TIME_CHANGED,
REASON_LOCALE_CHANGED,
REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
REASON_SYSTEM_ALLOW_LISTED,
REASON_ALARM_MANAGER_ALARM_CLOCK,
REASON_ALARM_MANAGER_WHILE_IDLE,
@@ -664,6 +671,8 @@ public class PowerExemptionManager {
return "TIME_CHANGED";
case REASON_LOCALE_CHANGED:
return "LOCALE_CHANGED";
case REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED:
return "REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED";
case REASON_SYSTEM_ALLOW_LISTED:
return "SYSTEM_ALLOW_LISTED";
case REASON_ALARM_MANAGER_ALARM_CLOCK:

View File

@@ -30,6 +30,7 @@ import static android.app.AlarmManager.INTERVAL_HOUR;
import static android.app.AlarmManager.RTC;
import static android.app.AlarmManager.RTC_WAKEUP;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.os.PowerExemptionManager.REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.PowerWhitelistManager.REASON_ALARM_MANAGER_WHILE_IDLE;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
@@ -1704,6 +1705,11 @@ public class AlarmManagerService extends SystemService {
if (!hasScheduleExactAlarmInternal(packageName, uid)) {
mHandler.obtainMessage(AlarmHandler.REMOVE_EXACT_ALARMS,
uid, 0, packageName).sendToTarget();
} else {
// TODO(b/187206399) Make sure this won't be sent, if the app
// already had the appop previously.
sendScheduleExactAlarmPermissionStateChangedBroadcast(
packageName, UserHandle.getUserId(uid));
}
}
});
@@ -4816,6 +4822,30 @@ public class AlarmManagerService extends SystemService {
}
}
/**
* Send {@link AlarmManager#ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED} to
* the app that is just granted the permission.
*/
private void sendScheduleExactAlarmPermissionStateChangedBroadcast(
String packageName, int userId) {
final Intent i = new Intent(
AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED);
i.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
| Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_FOREGROUND);
i.setPackage(packageName);
// We need to allow the app to start a foreground service.
// This broadcast is very rare, so we do not cache the BroadcastOptions.
final BroadcastOptions opts = BroadcastOptions.makeBasic();
opts.setTemporaryAppAllowlist(
mActivityManagerInternal.getBootTimeTempAllowListDuration(),
TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED, "");
getContext().sendBroadcastAsUser(i, UserHandle.of(userId), /*permission*/ null,
opts.toBundle());
}
private void decrementAlarmCount(int uid, int decrement) {
int oldCount = 0;
final int uidIndex = mAlarmsPerUid.indexOfKey(uid);

View File

@@ -4404,6 +4404,7 @@ package android.app {
method public void setWindow(int, long, long, android.app.PendingIntent);
method public void setWindow(int, long, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
field public static final String ACTION_NEXT_ALARM_CLOCK_CHANGED = "android.app.action.NEXT_ALARM_CLOCK_CHANGED";
field public static final String ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED = "android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED";
field public static final int ELAPSED_REALTIME = 3; // 0x3
field public static final int ELAPSED_REALTIME_WAKEUP = 2; // 0x2
field public static final long INTERVAL_DAY = 86400000L; // 0x5265c00L

View File

@@ -696,6 +696,8 @@
<protected-broadcast android:name="android.scheduling.action.REBOOT_READY" />
<protected-broadcast android:name="android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED" />
<protected-broadcast android:name="android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
<!-- ====================================================================== -->

View File

@@ -2402,6 +2402,34 @@ public class AlarmManagerServiceTest {
TEST_CALLING_PACKAGE);
}
@Test
public void opScheduleExactAlarmGranted() throws Exception {
final long durationMs = 20000L;
when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs);
mockExactAlarmPermissionGrant(true, false, MODE_ALLOWED);
mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.SYSTEM),
isNull(), bundleCaptor.capture());
// Validate the intent.
assertEquals(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
intentCaptor.getValue().getAction());
assertEquals(TEST_CALLING_PACKAGE, intentCaptor.getValue().getPackage());
// Validate the options.
final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue());
assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
bOptions.getTemporaryAppAllowlistType());
assertEquals(durationMs, bOptions.getTemporaryAppAllowlistDuration());
assertEquals(PowerExemptionManager.REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
bOptions.getTemporaryAppAllowlistReasonCode());
}
@Test
public void removeExactAlarmsOnPermissionRevoked() {
doReturn(true).when(