Merge "Changes to 'Alarms & reminders' permission setting" into sc-dev
diff --git a/res/xml/app_info_settings.xml b/res/xml/app_info_settings.xml
index fef5243..2afaede 100644
--- a/res/xml/app_info_settings.xml
+++ b/res/xml/app_info_settings.xml
@@ -175,6 +175,12 @@
android:summary="@string/summary_placeholder"
settings:controller="com.android.settings.applications.specialaccess.interactacrossprofiles.InteractAcrossProfilesDetailsPreferenceController" />
+ <Preference
+ android:key="alarms_and_reminders"
+ android:title="@string/alarms_and_reminders_title"
+ android:summary="@string/summary_placeholder"
+ settings:controller="com.android.settings.applications.appinfo.AlarmsAndRemindersDetailPreferenceController" />
+
</PreferenceCategory>
<!-- App installer info -->
diff --git a/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridge.java b/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridge.java
index 4e9a96e..cf938a5 100644
--- a/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridge.java
+++ b/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridge.java
@@ -19,6 +19,7 @@
import android.Manifest;
import android.app.AlarmManager;
import android.app.AppGlobals;
+import android.app.compat.CompatChanges;
import android.content.Context;
import android.content.pm.IPackageManager;
import android.os.RemoteException;
@@ -63,14 +64,21 @@
}
}
+ private boolean isChangeEnabled(String packageName, int userId) {
+ return CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
+ packageName, UserHandle.of(userId));
+ }
+
/**
* Returns information regarding {@link Manifest.permission#SCHEDULE_EXACT_ALARM} for the given
* package and uid.
*/
public AlarmsAndRemindersState createPermissionState(String packageName, int uid) {
- final boolean permissionRequested = ArrayUtils.contains(mRequesterPackages, packageName);
- final boolean permissionGranted = mAlarmManager.hasScheduleExactAlarm(packageName,
- UserHandle.getUserId(uid));
+ final int userId = UserHandle.getUserId(uid);
+
+ final boolean permissionRequested = ArrayUtils.contains(mRequesterPackages, packageName)
+ && isChangeEnabled(packageName, userId);
+ final boolean permissionGranted = mAlarmManager.hasScheduleExactAlarm(packageName, userId);
return new AlarmsAndRemindersState(permissionRequested, permissionGranted);
}
diff --git a/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetailPreferenceController.java b/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetailPreferenceController.java
new file mode 100644
index 0000000..cfd4bf1
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetailPreferenceController.java
@@ -0,0 +1,75 @@
+/*
+ * 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 com.android.settings.applications.appinfo;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.applications.AppStateAlarmsAndRemindersBridge;
+
+/**
+ * Preference controller for
+ * {@link com.android.settings.applications.appinfo.AlarmsAndRemindersDetails} Settings fragment.
+ */
+public class AlarmsAndRemindersDetailPreferenceController extends AppInfoPreferenceControllerBase {
+
+ private String mPackageName;
+
+ public AlarmsAndRemindersDetailPreferenceController(Context context, String key) {
+ super(context, key);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return isCandidate() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ preference.setSummary(getPreferenceSummary());
+ }
+
+ @Override
+ protected Class<? extends SettingsPreferenceFragment> getDetailFragmentClass() {
+ return AlarmsAndRemindersDetails.class;
+ }
+
+ @VisibleForTesting
+ CharSequence getPreferenceSummary() {
+ return AlarmsAndRemindersDetails.getSummary(mContext, mParent.getAppEntry());
+ }
+
+ @VisibleForTesting
+ boolean isCandidate() {
+ final PackageInfo packageInfo = mParent.getPackageInfo();
+ if (packageInfo == null) {
+ return false;
+ }
+ final AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState appState =
+ new AppStateAlarmsAndRemindersBridge(mContext, null, null).createPermissionState(
+ mPackageName, packageInfo.applicationInfo.uid);
+ return appState.shouldBeVisible();
+ }
+
+ void setPackageName(String packageName) {
+ mPackageName = packageName;
+ }
+}
diff --git a/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetails.java b/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetails.java
index 3765dd9..648696b 100644
--- a/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetails.java
+++ b/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetails.java
@@ -18,7 +18,6 @@
import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_OK;
-import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
@@ -49,25 +48,20 @@
private AppOpsManager mAppOpsManager;
private RestrictedSwitchPreference mSwitchPref;
private AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState mPermissionState;
- private ActivityManager mActivityManager;
private volatile Boolean mUncommittedState;
/**
* Returns the string that states whether the app has access to
* {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM}.
*/
- public static int getSummary(Context context, AppEntry entry) {
- final AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState state;
- if (entry.extraInfo instanceof AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState) {
- state = (AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState) entry.extraInfo;
- } else {
- state = new AppStateAlarmsAndRemindersBridge(context, /*appState=*/null,
- /*callback=*/null).createPermissionState(entry.info.packageName,
- entry.info.uid);
- }
+ public static CharSequence getSummary(Context context, AppEntry entry) {
+ final AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState state =
+ new AppStateAlarmsAndRemindersBridge(context, /*appState=*/null,
+ /*callback=*/null).createPermissionState(entry.info.packageName,
+ entry.info.uid);
- return state.isAllowed() ? R.string.app_permission_summary_allowed
- : R.string.app_permission_summary_not_allowed;
+ return context.getString(state.isAllowed() ? R.string.app_permission_summary_allowed
+ : R.string.app_permission_summary_not_allowed);
}
@Override
@@ -77,7 +71,6 @@
final Context context = getActivity();
mAppBridge = new AppStateAlarmsAndRemindersBridge(context, mState, /*callback=*/null);
mAppOpsManager = context.getSystemService(AppOpsManager.class);
- mActivityManager = context.getSystemService(ActivityManager.class);
if (savedInstanceState != null) {
mUncommittedState = (Boolean) savedInstanceState.get(UNCOMMITTED_STATE_KEY);
@@ -115,10 +108,6 @@
final int uid = mPackageInfo.applicationInfo.uid;
mAppOpsManager.setUidMode(AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, uid,
newState ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED);
- if (!newState) {
- mActivityManager.killUid(uid,
- AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM + " no longer allowed.");
- }
}
private void logPermissionChange(boolean newState, String packageName) {
diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
index cb0ed07..9cc3836 100755
--- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
+++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
@@ -197,8 +197,14 @@
acrossProfiles.setPackageName(packageName);
acrossProfiles.setParentFragment(this);
+ final AlarmsAndRemindersDetailPreferenceController alarmsAndReminders =
+ use(AlarmsAndRemindersDetailPreferenceController.class);
+ alarmsAndReminders.setPackageName(packageName);
+ alarmsAndReminders.setParentFragment(this);
+
use(AdvancedAppInfoPreferenceCategoryController.class).setChildren(Arrays.asList(
- writeSystemSettings, drawOverlay, pip, externalSource, acrossProfiles));
+ writeSystemSettings, drawOverlay, pip, externalSource, acrossProfiles,
+ alarmsAndReminders));
}
@Override
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetailPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetailPreferenceControllerTest.java
new file mode 100644
index 0000000..58b894e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetailPreferenceControllerTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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 com.android.settings.applications.appinfo;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.settings.core.BasePreferenceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class AlarmsAndRemindersDetailPreferenceControllerTest {
+
+ @Mock
+ private AppInfoDashboardFragment mFragment;
+ @Mock
+ private Preference mPreference;
+
+ private Context mContext;
+ private AlarmsAndRemindersDetailPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ mController = spy(new AlarmsAndRemindersDetailPreferenceController(mContext, "test_key"));
+ mController.setPackageName("Package1");
+ mController.setParentFragment(mFragment);
+ final String key = mController.getPreferenceKey();
+ when(mPreference.getKey()).thenReturn(key);
+ }
+
+ @Test
+ public void getAvailabilityStatus_notCandidate_shouldReturnUnavailable() {
+ doReturn(false).when(mController).isCandidate();
+
+ assertThat(mController.getAvailabilityStatus())
+ .isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_isCandidate_shouldReturnAvailable() {
+ doReturn(true).when(mController).isCandidate();
+
+ assertThat(mController.getAvailabilityStatus())
+ .isEqualTo(BasePreferenceController.AVAILABLE);
+ }
+
+ @Test
+ public void getDetailFragmentClass_shouldReturnAlarmsAndRemindersDetails() {
+ assertThat(mController.getDetailFragmentClass()).isEqualTo(AlarmsAndRemindersDetails.class);
+ }
+
+ @Test
+ public void updateState_shouldSetSummary() {
+ final String summary = "test summary";
+ doReturn(summary).when(mController).getPreferenceSummary();
+
+ mController.updateState(mPreference);
+
+ verify(mPreference).setSummary(summary);
+ }
+
+ @Test
+ public void isCandidate_nullPackageInfo_shouldNotCrash() {
+ mController.isCandidate();
+ // no crash
+ }
+}