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
+    }
+}