Merge "Handle corner cases in "Alarms and Reminders" page" into udc-dev

This commit is contained in:
Treehugger Robot
2023-04-13 20:10:49 +00:00
committed by Android (Google) Code Review
2 changed files with 217 additions and 78 deletions

View File

@@ -18,11 +18,11 @@ package com.android.settings.applications;
import android.Manifest; import android.Manifest;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.compat.CompatChanges; import android.app.compat.CompatChanges;
import android.content.Context; import android.content.Context;
import android.content.pm.IPackageManager; import android.content.pm.PackageInfo;
import android.os.RemoteException; import android.content.pm.PackageManager;
import android.os.PowerExemptionManager;
import android.os.UserHandle; import android.os.UserHandle;
import android.util.Log; import android.util.Log;
@@ -32,8 +32,6 @@ import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry; import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.ApplicationsState.AppFilter; import com.android.settingslib.applications.ApplicationsState.AppFilter;
import libcore.util.EmptyArray;
import java.util.List; import java.util.List;
/** /**
@@ -42,26 +40,24 @@ import java.util.List;
* Also provides app filters that can use the info. * Also provides app filters that can use the info.
*/ */
public class AppStateAlarmsAndRemindersBridge extends AppStateBaseBridge { public class AppStateAlarmsAndRemindersBridge extends AppStateBaseBridge {
private static final String PERMISSION = Manifest.permission.SCHEDULE_EXACT_ALARM; private static final String SEA_PERMISSION = Manifest.permission.SCHEDULE_EXACT_ALARM;
private static final String UEA_PERMISSION = Manifest.permission.USE_EXACT_ALARM;
private static final String TAG = "AlarmsAndRemindersBridge"; private static final String TAG = "AlarmsAndRemindersBridge";
@VisibleForTesting @VisibleForTesting
AlarmManager mAlarmManager; AlarmManager mAlarmManager;
@VisibleForTesting @VisibleForTesting
String[] mRequesterPackages; PowerExemptionManager mPowerExemptionManager;
@VisibleForTesting
PackageManager mPackageManager;
public AppStateAlarmsAndRemindersBridge(Context context, ApplicationsState appState, public AppStateAlarmsAndRemindersBridge(Context context, ApplicationsState appState,
Callback callback) { Callback callback) {
super(appState, callback); super(appState, callback);
mPowerExemptionManager = context.getSystemService(PowerExemptionManager.class);
mAlarmManager = context.getSystemService(AlarmManager.class); mAlarmManager = context.getSystemService(AlarmManager.class);
final IPackageManager iPm = AppGlobals.getPackageManager(); mPackageManager = context.getPackageManager();
try {
mRequesterPackages = iPm.getAppOpPermissionPackages(PERMISSION, context.getUserId());
} catch (RemoteException re) {
Log.e(TAG, "Cannot reach package manager", re);
mRequesterPackages = EmptyArray.STRING;
}
} }
private boolean isChangeEnabled(String packageName, int userId) { private boolean isChangeEnabled(String packageName, int userId) {
@@ -69,6 +65,22 @@ public class AppStateAlarmsAndRemindersBridge extends AppStateBaseBridge {
packageName, UserHandle.of(userId)); packageName, UserHandle.of(userId));
} }
private boolean isUeaChangeEnabled(String packageName, int userId) {
return CompatChanges.isChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, packageName,
UserHandle.of(userId));
}
private String[] getRequestedPermissions(String packageName, int userId) {
try {
final PackageInfo info = mPackageManager.getPackageInfoAsUser(packageName,
PackageManager.GET_PERMISSIONS, userId);
return info.requestedPermissions;
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Could not find package " + packageName, e);
}
return null;
}
/** /**
* Returns information regarding {@link Manifest.permission#SCHEDULE_EXACT_ALARM} for the given * Returns information regarding {@link Manifest.permission#SCHEDULE_EXACT_ALARM} for the given
* package and uid. * package and uid.
@@ -76,10 +88,17 @@ public class AppStateAlarmsAndRemindersBridge extends AppStateBaseBridge {
public AlarmsAndRemindersState createPermissionState(String packageName, int uid) { public AlarmsAndRemindersState createPermissionState(String packageName, int uid) {
final int userId = UserHandle.getUserId(uid); final int userId = UserHandle.getUserId(uid);
final boolean permissionRequested = ArrayUtils.contains(mRequesterPackages, packageName) final String[] requestedPermissions = getRequestedPermissions(packageName, userId);
final boolean seaRequested = ArrayUtils.contains(requestedPermissions, SEA_PERMISSION)
&& isChangeEnabled(packageName, userId); && isChangeEnabled(packageName, userId);
final boolean permissionGranted = mAlarmManager.hasScheduleExactAlarm(packageName, userId); final boolean ueaRequested = ArrayUtils.contains(requestedPermissions, UEA_PERMISSION)
return new AlarmsAndRemindersState(permissionRequested, permissionGranted); && isUeaChangeEnabled(packageName, userId);
final boolean seaGranted = mAlarmManager.hasScheduleExactAlarm(packageName, userId);
final boolean allowListed = mPowerExemptionManager.isAllowListed(packageName, true);
return new AlarmsAndRemindersState(seaRequested, ueaRequested, seaGranted, allowListed);
} }
@Override @Override
@@ -113,26 +132,32 @@ public class AppStateAlarmsAndRemindersBridge extends AppStateBaseBridge {
}; };
/** /**
* Class to denote the state of an app regarding * Class to denote the state of an app regarding "Alarms and Reminders" permission.
* {@link Manifest.permission#SCHEDULE_EXACT_ALARM}. * This permission state is a combination of {@link Manifest.permission#SCHEDULE_EXACT_ALARM},
* {@link Manifest.permission#USE_EXACT_ALARM} and the power allowlist state.
*/ */
public static class AlarmsAndRemindersState { public static class AlarmsAndRemindersState {
private boolean mPermissionRequested; private boolean mSeaPermissionRequested;
private boolean mPermissionGranted; private boolean mUeaPermissionRequested;
private boolean mSeaPermissionGranted;
private boolean mAllowListed;
AlarmsAndRemindersState(boolean permissionRequested, boolean permissionGranted) { AlarmsAndRemindersState(boolean seaPermissionRequested, boolean ueaPermissionRequested,
mPermissionRequested = permissionRequested; boolean seaPermissionGranted, boolean allowListed) {
mPermissionGranted = permissionGranted; mSeaPermissionRequested = seaPermissionRequested;
mUeaPermissionRequested = ueaPermissionRequested;
mSeaPermissionGranted = seaPermissionGranted;
mAllowListed = allowListed;
} }
/** Should the app associated with this state appear on the Settings screen */ /** Should the app associated with this state appear on the Settings screen */
public boolean shouldBeVisible() { public boolean shouldBeVisible() {
return mPermissionRequested; return mSeaPermissionRequested && !mUeaPermissionRequested && !mAllowListed;
} }
/** Is the permission granted to the app associated with this state */ /** Is the permission granted to the app associated with this state */
public boolean isAllowed() { public boolean isAllowed() {
return mPermissionGranted; return mSeaPermissionGranted || mUeaPermissionRequested || mAllowListed;
} }
} }
} }

View File

@@ -16,15 +16,24 @@
package com.android.settings.applications; package com.android.settings.applications;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import android.Manifest;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.PowerExemptionManager;
import android.os.UserHandle; import android.os.UserHandle;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import libcore.util.EmptyArray;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -34,13 +43,15 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class AppStateAlarmsAndRemindersBridgeTest { public class AppStateAlarmsAndRemindersBridgeTest {
private static final String TEST_PACKAGE_1 = "com.example.test.1"; private static final String TEST_PACKAGE = "com.example.test.1";
private static final String TEST_PACKAGE_2 = "com.example.test.2"; private static final int TEST_UID = 12345;
private static final int UID_1 = 12345;
private static final int UID_2 = 7654321;
@Mock @Mock
private AlarmManager mAlarmManager; private AlarmManager mAlarmManager;
@Mock
private PowerExemptionManager mPowerExemptionManager;
@Mock
private PackageManager mPackageManager;
@Mock(answer = Answers.RETURNS_DEEP_STUBS) @Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext; private Context mContext;
@@ -50,65 +61,168 @@ public class AppStateAlarmsAndRemindersBridgeTest {
} }
@Test @Test
public void shouldBeVisible_permissionRequestedIsTrue_isTrue() { public void alarmsAndRemindersState_shouldBeVisible() {
assertThat(new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState( boolean seaPermissionRequested;
true /* permissionRequested */, boolean ueaPermissionRequested;
true /* permissionGranted */) boolean seaPermissionGranted;
.shouldBeVisible()).isTrue(); boolean allowListed;
assertThat(new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState(
true /* permissionRequested */, for (int i = 0; i < (1 << 4); i++) {
false /* permissionGranted */) seaPermissionRequested = (i & 1) != 0;
.shouldBeVisible()).isTrue(); ueaPermissionRequested = (i & (1 << 1)) != 0;
assertThat(new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState( seaPermissionGranted = (i & (1 << 2)) != 0;
false /* permissionRequested */, allowListed = (i & (1 << 3)) != 0;
true /* permissionGranted */)
.shouldBeVisible()).isFalse(); final boolean visible = new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState(
assertThat(new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState( seaPermissionRequested,
false /* permissionRequested */, ueaPermissionRequested,
false /* permissionGranted */) seaPermissionGranted,
.shouldBeVisible()).isFalse(); allowListed).shouldBeVisible();
assertWithMessage("Wrong return value " + visible
+ " for {seaPermissionRequested = " + seaPermissionRequested
+ ", ueaPermissionRequested = " + ueaPermissionRequested
+ ", seaPermissionGranted = " + seaPermissionGranted
+ ", allowListed = " + allowListed + "}")
.that(visible)
.isEqualTo(seaPermissionRequested && !ueaPermissionRequested && !allowListed);
}
} }
@Test @Test
public void isAllowed_permissionGrantedIsTrue_isTrue() { public void alarmsAndRemindersState_isAllowed() {
assertThat(new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState( boolean seaPermissionRequested;
true /* permissionRequested */, boolean ueaPermissionRequested;
true /* permissionGranted */) boolean seaPermissionGranted;
.isAllowed()).isTrue(); boolean allowListed;
assertThat(new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState(
true /* permissionRequested */, for (int i = 0; i < (1 << 4); i++) {
false /* permissionGranted */) seaPermissionRequested = (i & 1) != 0;
.isAllowed()).isFalse(); ueaPermissionRequested = (i & (1 << 1)) != 0;
assertThat(new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState( seaPermissionGranted = (i & (1 << 2)) != 0;
false /* permissionRequested */, allowListed = (i & (1 << 3)) != 0;
true /* permissionGranted */)
.isAllowed()).isTrue(); final boolean allowed = new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState(
assertThat(new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState( seaPermissionRequested,
false /* permissionRequested */, ueaPermissionRequested,
false /* permissionGranted */) seaPermissionGranted,
.isAllowed()).isFalse(); allowListed).isAllowed();
assertWithMessage("Wrong return value " + allowed
+ " for {seaPermissionRequested = " + seaPermissionRequested
+ ", ueaPermissionRequested = " + ueaPermissionRequested
+ ", seaPermissionGranted = " + seaPermissionGranted
+ ", allowListed = " + allowListed + "}")
.that(allowed)
.isEqualTo(seaPermissionGranted || ueaPermissionRequested || allowListed);
}
}
private PackageInfo createPackageInfoWithPermissions(String... requestedPermissions) {
final PackageInfo info = new PackageInfo();
info.requestedPermissions = requestedPermissions;
return info;
} }
@Test @Test
public void createPermissionState() { public void createPermissionState_SeaGrantedNoUeaNoAllowlist() throws Exception {
AppStateAlarmsAndRemindersBridge bridge = new AppStateAlarmsAndRemindersBridge(mContext, AppStateAlarmsAndRemindersBridge bridge = new AppStateAlarmsAndRemindersBridge(mContext,
null, null); null, null);
bridge.mAlarmManager = mAlarmManager; bridge.mAlarmManager = mAlarmManager;
bridge.mRequesterPackages = new String[]{TEST_PACKAGE_1, "some.other.package"}; bridge.mPackageManager = mPackageManager;
bridge.mPowerExemptionManager = mPowerExemptionManager;
doReturn(false).when(mAlarmManager).hasScheduleExactAlarm(TEST_PACKAGE_1, doReturn(true).when(mAlarmManager).hasScheduleExactAlarm(TEST_PACKAGE,
UserHandle.getUserId(UID_1)); UserHandle.getUserId(TEST_UID));
doReturn(true).when(mAlarmManager).hasScheduleExactAlarm(TEST_PACKAGE_2, doReturn(createPackageInfoWithPermissions(Manifest.permission.SCHEDULE_EXACT_ALARM))
UserHandle.getUserId(UID_2)); .when(mPackageManager).getPackageInfoAsUser(eq(TEST_PACKAGE), anyInt(), anyInt());
doReturn(false).when(mPowerExemptionManager).isAllowListed(TEST_PACKAGE, true);
AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState state1 = AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState state =
bridge.createPermissionState(TEST_PACKAGE_1, UID_1); bridge.createPermissionState(TEST_PACKAGE, TEST_UID);
assertThat(state1.shouldBeVisible()).isTrue(); assertThat(state.shouldBeVisible()).isTrue();
assertThat(state1.isAllowed()).isFalse(); assertThat(state.isAllowed()).isTrue();
}
AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState state2 = @Test
bridge.createPermissionState(TEST_PACKAGE_2, UID_2); public void createPermissionState_requestsBothSeaDeniedNoAllowlist() throws Exception {
assertThat(state2.shouldBeVisible()).isFalse(); AppStateAlarmsAndRemindersBridge bridge = new AppStateAlarmsAndRemindersBridge(mContext,
assertThat(state2.isAllowed()).isTrue(); null, null);
bridge.mAlarmManager = mAlarmManager;
bridge.mPackageManager = mPackageManager;
bridge.mPowerExemptionManager = mPowerExemptionManager;
doReturn(false).when(mAlarmManager).hasScheduleExactAlarm(TEST_PACKAGE,
UserHandle.getUserId(TEST_UID));
doReturn(createPackageInfoWithPermissions(
Manifest.permission.SCHEDULE_EXACT_ALARM,
Manifest.permission.USE_EXACT_ALARM))
.when(mPackageManager).getPackageInfoAsUser(eq(TEST_PACKAGE), anyInt(), anyInt());
doReturn(false).when(mPowerExemptionManager).isAllowListed(TEST_PACKAGE, true);
AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState state =
bridge.createPermissionState(TEST_PACKAGE, TEST_UID);
assertThat(state.shouldBeVisible()).isFalse();
assertThat(state.isAllowed()).isTrue();
}
@Test
public void createPermissionState_requestsNoneNoAllowlist() throws Exception {
AppStateAlarmsAndRemindersBridge bridge = new AppStateAlarmsAndRemindersBridge(mContext,
null, null);
bridge.mAlarmManager = mAlarmManager;
bridge.mPackageManager = mPackageManager;
bridge.mPowerExemptionManager = mPowerExemptionManager;
doReturn(false).when(mAlarmManager).hasScheduleExactAlarm(TEST_PACKAGE,
UserHandle.getUserId(TEST_UID));
doReturn(createPackageInfoWithPermissions(EmptyArray.STRING))
.when(mPackageManager).getPackageInfoAsUser(eq(TEST_PACKAGE), anyInt(), anyInt());
doReturn(false).when(mPowerExemptionManager).isAllowListed(TEST_PACKAGE, true);
AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState state =
bridge.createPermissionState(TEST_PACKAGE, TEST_UID);
assertThat(state.shouldBeVisible()).isFalse();
assertThat(state.isAllowed()).isFalse();
}
@Test
public void createPermissionState_requestsOnlyUeaNoAllowlist() throws Exception {
AppStateAlarmsAndRemindersBridge bridge = new AppStateAlarmsAndRemindersBridge(mContext,
null, null);
bridge.mAlarmManager = mAlarmManager;
bridge.mPackageManager = mPackageManager;
bridge.mPowerExemptionManager = mPowerExemptionManager;
doReturn(false).when(mAlarmManager).hasScheduleExactAlarm(TEST_PACKAGE,
UserHandle.getUserId(TEST_UID));
doReturn(createPackageInfoWithPermissions(Manifest.permission.USE_EXACT_ALARM))
.when(mPackageManager).getPackageInfoAsUser(eq(TEST_PACKAGE), anyInt(), anyInt());
doReturn(false).when(mPowerExemptionManager).isAllowListed(TEST_PACKAGE, true);
AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState state =
bridge.createPermissionState(TEST_PACKAGE, TEST_UID);
assertThat(state.shouldBeVisible()).isFalse();
assertThat(state.isAllowed()).isTrue();
}
@Test
public void createPermissionState_requestsNoneButAllowlisted() throws Exception {
AppStateAlarmsAndRemindersBridge bridge = new AppStateAlarmsAndRemindersBridge(mContext,
null, null);
bridge.mAlarmManager = mAlarmManager;
bridge.mPackageManager = mPackageManager;
bridge.mPowerExemptionManager = mPowerExemptionManager;
doReturn(false).when(mAlarmManager).hasScheduleExactAlarm(TEST_PACKAGE,
UserHandle.getUserId(TEST_UID));
doReturn(createPackageInfoWithPermissions(EmptyArray.STRING))
.when(mPackageManager).getPackageInfoAsUser(eq(TEST_PACKAGE), anyInt(), anyInt());
doReturn(true).when(mPowerExemptionManager).isAllowListed(TEST_PACKAGE, true);
AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState state =
bridge.createPermissionState(TEST_PACKAGE, TEST_UID);
assertThat(state.shouldBeVisible()).isFalse();
assertThat(state.isAllowed()).isTrue();
} }
} }