Merge "Changes to "Alarms and Reminders" permission" into sc-dev am: 39086c24fd

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/14095813

Change-Id: I644442b4d41b343bfc1aebcc2c42ac2f9dc7ce82
diff --git a/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridge.java b/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridge.java
index ccfc87e..4e9a96e 100644
--- a/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridge.java
+++ b/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridge.java
@@ -17,13 +17,22 @@
 package com.android.settings.applications;
 
 import android.Manifest;
-import android.app.AppOpsManager;
+import android.app.AlarmManager;
+import android.app.AppGlobals;
 import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.settingslib.applications.ApplicationsState;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 import com.android.settingslib.applications.ApplicationsState.AppFilter;
 
+import libcore.util.EmptyArray;
+
 import java.util.List;
 
 /**
@@ -31,28 +40,38 @@
  * to the semantics of {@link Manifest.permission#SCHEDULE_EXACT_ALARM}.
  * Also provides app filters that can use the info.
  */
-public class AppStateAlarmsAndRemindersBridge extends AppStateAppOpsBridge {
+public class AppStateAlarmsAndRemindersBridge extends AppStateBaseBridge {
+    private static final String PERMISSION = Manifest.permission.SCHEDULE_EXACT_ALARM;
+    private static final String TAG = "AlarmsAndRemindersBridge";
 
-    private AppOpsManager mAppOpsManager;
+    @VisibleForTesting
+    AlarmManager mAlarmManager;
+    @VisibleForTesting
+    String[] mRequesterPackages;
 
     public AppStateAlarmsAndRemindersBridge(Context context, ApplicationsState appState,
             Callback callback) {
-        super(context, appState, callback,
-                AppOpsManager.strOpToOp(AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM),
-                new String[]{Manifest.permission.SCHEDULE_EXACT_ALARM});
+        super(appState, callback);
 
-        mAppOpsManager = context.getSystemService(AppOpsManager.class);
+        mAlarmManager = context.getSystemService(AlarmManager.class);
+        final IPackageManager iPm = AppGlobals.getPackageManager();
+        try {
+            mRequesterPackages = iPm.getAppOpPermissionPackages(PERMISSION);
+        } catch (RemoteException re) {
+            Log.e(TAG, "Cannot reach package manager", re);
+            mRequesterPackages = EmptyArray.STRING;
+        }
     }
 
     /**
      * Returns information regarding {@link Manifest.permission#SCHEDULE_EXACT_ALARM} for the given
      * package and uid.
      */
-    public PermissionState createPermissionState(String packageName, int uid) {
-        final PermissionState permState = getPermissionInfo(packageName, uid);
-        permState.appOpMode = mAppOpsManager.unsafeCheckOpRawNoThrow(
-                AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, uid, packageName);
-        return permState;
+    public AlarmsAndRemindersState createPermissionState(String packageName, int uid) {
+        final boolean permissionRequested = ArrayUtils.contains(mRequesterPackages, packageName);
+        final boolean permissionGranted = mAlarmManager.hasScheduleExactAlarm(packageName,
+                UserHandle.getUserId(uid));
+        return new AlarmsAndRemindersState(permissionRequested, permissionGranted);
     }
 
     @Override
@@ -77,12 +96,35 @@
 
         @Override
         public boolean filterApp(AppEntry info) {
-            if (info.extraInfo instanceof PermissionState) {
-                final PermissionState permissionState = (PermissionState) info.extraInfo;
-                return permissionState.permissionDeclared;
+            if (info.extraInfo instanceof AlarmsAndRemindersState) {
+                final AlarmsAndRemindersState state = (AlarmsAndRemindersState) info.extraInfo;
+                return state.shouldBeVisible();
             }
             return false;
         }
     };
 
+    /**
+     * Class to denote the state of an app regarding
+     * {@link Manifest.permission#SCHEDULE_EXACT_ALARM}.
+     */
+    public static class AlarmsAndRemindersState {
+        private boolean mPermissionRequested;
+        private boolean mPermissionGranted;
+
+        AlarmsAndRemindersState(boolean permissionRequested, boolean permissionGranted) {
+            mPermissionRequested = permissionRequested;
+            mPermissionGranted = permissionGranted;
+        }
+
+        /** Should the app associated with this state appear on the Settings screen */
+        public boolean shouldBeVisible() {
+            return mPermissionRequested;
+        }
+
+        /** Is the permission granted to the app associated with this state */
+        public boolean isAllowed() {
+            return mPermissionGranted;
+        }
+    }
 }
diff --git a/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetails.java b/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetails.java
index 95835cb..7ef9e89 100644
--- a/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetails.java
+++ b/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetails.java
@@ -18,6 +18,7 @@
 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;
@@ -31,7 +32,6 @@
 import com.android.settings.Settings;
 import com.android.settings.applications.AppInfoWithHeader;
 import com.android.settings.applications.AppStateAlarmsAndRemindersBridge;
-import com.android.settings.applications.AppStateAppOpsBridge;
 import com.android.settingslib.RestrictedSwitchPreference;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 
@@ -47,23 +47,24 @@
     private AppStateAlarmsAndRemindersBridge mAppBridge;
     private AppOpsManager mAppOpsManager;
     private RestrictedSwitchPreference mSwitchPref;
-    private AppStateAppOpsBridge.PermissionState mPermissionState;
+    private AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState mPermissionState;
+    private ActivityManager mActivityManager;
 
     /**
      * 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 AppStateAppOpsBridge.PermissionState state;
-        if (entry.extraInfo instanceof AppStateAppOpsBridge.PermissionState) {
-            state = (AppStateAppOpsBridge.PermissionState) entry.extraInfo;
+        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);
         }
 
-        return state.isPermissible() ? R.string.app_permission_summary_allowed
+        return state.isAllowed() ? R.string.app_permission_summary_allowed
                 : R.string.app_permission_summary_not_allowed;
     }
 
@@ -74,6 +75,7 @@
         final Context context = getActivity();
         mAppBridge = new AppStateAlarmsAndRemindersBridge(context, mState, /*callback=*/null);
         mAppOpsManager = context.getSystemService(AppOpsManager.class);
+        mActivityManager = context.getSystemService(ActivityManager.class);
 
         addPreferencesFromResource(R.xml.alarms_and_reminders);
         mSwitchPref = findPreference(KEY_SWITCH);
@@ -84,7 +86,7 @@
     public boolean onPreferenceChange(Preference preference, Object newValue) {
         final boolean checked = (Boolean) newValue;
         if (preference == mSwitchPref) {
-            if (mPermissionState != null && checked != mPermissionState.isPermissible()) {
+            if (mPermissionState != null && checked != mPermissionState.isAllowed()) {
                 if (Settings.AlarmsAndRemindersAppActivity.class.getName().equals(
                         getIntent().getComponent().getClassName())) {
                     setResult(checked ? RESULT_OK : RESULT_CANCELED);
@@ -99,9 +101,13 @@
     }
 
     private void setCanScheduleAlarms(boolean newState) {
-        mAppOpsManager.setUidMode(AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM,
-                mPackageInfo.applicationInfo.uid,
+        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) {
@@ -120,8 +126,8 @@
         }
         mPermissionState = mAppBridge.createPermissionState(mPackageName,
                 mPackageInfo.applicationInfo.uid);
-        mSwitchPref.setEnabled(mPermissionState.permissionDeclared);
-        mSwitchPref.setChecked(mPermissionState.isPermissible());
+        mSwitchPref.setEnabled(mPermissionState.shouldBeVisible());
+        mSwitchPref.setChecked(mPermissionState.isAllowed());
         return true;
     }
 
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetailsTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetailsTest.java
index c3bcead..313ec11 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetailsTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetailsTest.java
@@ -27,7 +27,6 @@
 import android.content.pm.PackageInfo;
 
 import com.android.settings.applications.AppStateAlarmsAndRemindersBridge;
-import com.android.settings.applications.AppStateAppOpsBridge;
 import com.android.settingslib.RestrictedSwitchPreference;
 
 import org.junit.Before;
@@ -48,7 +47,7 @@
     @Mock
     private AppStateAlarmsAndRemindersBridge mAppStateBridge;
     @Mock
-    private AppStateAppOpsBridge.PermissionState mPermissionState;
+    private AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState mPermissionState;
 
     private AlarmsAndRemindersDetails mFragment = new AlarmsAndRemindersDetails();
 
@@ -96,12 +95,12 @@
         mPackageInfo.applicationInfo = new ApplicationInfo();
         when(mAppStateBridge.createPermissionState(nullable(String.class), anyInt()))
                 .thenReturn(mPermissionState);
-        mPermissionState.permissionDeclared = false;
+        when(mPermissionState.shouldBeVisible()).thenReturn(false);
 
         mFragment.refreshUi();
         verify(mSwitchPref).setEnabled(false);
 
-        mPermissionState.permissionDeclared = true;
+        when(mPermissionState.shouldBeVisible()).thenReturn(true);
 
         mFragment.refreshUi();
         verify(mSwitchPref).setEnabled(true);
@@ -114,11 +113,11 @@
         when(mAppStateBridge.createPermissionState(nullable(String.class), anyInt()))
                 .thenReturn(mPermissionState);
 
-        when(mPermissionState.isPermissible()).thenReturn(true);
+        when(mPermissionState.isAllowed()).thenReturn(true);
         mFragment.refreshUi();
         verify(mSwitchPref).setChecked(true);
 
-        when(mPermissionState.isPermissible()).thenReturn(false);
+        when(mPermissionState.isAllowed()).thenReturn(false);
         mFragment.refreshUi();
         verify(mSwitchPref).setChecked(false);
     }
diff --git a/tests/unit/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridgeTest.java b/tests/unit/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridgeTest.java
new file mode 100644
index 0000000..f56da05
--- /dev/null
+++ b/tests/unit/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridgeTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class AppStateAlarmsAndRemindersBridgeTest {
+    private static final String TEST_PACKAGE_1 = "com.example.test.1";
+    private static final String TEST_PACKAGE_2 = "com.example.test.2";
+    private static final int UID_1 = 12345;
+    private static final int UID_2 = 7654321;
+
+    @Mock
+    private AlarmManager mAlarmManager;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+
+    @Before
+    public void initMocks() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void shouldBeVisible_permissionRequestedIsTrue_isTrue() {
+        assertThat(new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState(
+                true /* permissionRequested */,
+                true /* permissionGranted */)
+                .shouldBeVisible()).isTrue();
+        assertThat(new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState(
+                true /* permissionRequested */,
+                false /* permissionGranted */)
+                .shouldBeVisible()).isTrue();
+        assertThat(new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState(
+                false /* permissionRequested */,
+                true /* permissionGranted */)
+                .shouldBeVisible()).isFalse();
+        assertThat(new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState(
+                false /* permissionRequested */,
+                false /* permissionGranted */)
+                .shouldBeVisible()).isFalse();
+    }
+
+    @Test
+    public void isAllowed_permissionGrantedIsTrue_isTrue() {
+        assertThat(new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState(
+                true /* permissionRequested */,
+                true /* permissionGranted */)
+                .isAllowed()).isTrue();
+        assertThat(new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState(
+                true /* permissionRequested */,
+                false /* permissionGranted */)
+                .isAllowed()).isFalse();
+        assertThat(new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState(
+                false /* permissionRequested */,
+                true /* permissionGranted */)
+                .isAllowed()).isTrue();
+        assertThat(new AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState(
+                false /* permissionRequested */,
+                false /* permissionGranted */)
+                .isAllowed()).isFalse();
+    }
+
+    @Test
+    public void createPermissionState() {
+        AppStateAlarmsAndRemindersBridge bridge = new AppStateAlarmsAndRemindersBridge(mContext,
+                null, null);
+        bridge.mAlarmManager = mAlarmManager;
+        bridge.mRequesterPackages = new String[]{TEST_PACKAGE_1, "some.other.package"};
+
+        doReturn(false).when(mAlarmManager).hasScheduleExactAlarm(TEST_PACKAGE_1,
+                UserHandle.getUserId(UID_1));
+        doReturn(true).when(mAlarmManager).hasScheduleExactAlarm(TEST_PACKAGE_2,
+                UserHandle.getUserId(UID_2));
+
+        AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState state1 =
+                bridge.createPermissionState(TEST_PACKAGE_1, UID_1);
+        assertThat(state1.shouldBeVisible()).isTrue();
+        assertThat(state1.isAllowed()).isFalse();
+
+        AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState state2 =
+                bridge.createPermissionState(TEST_PACKAGE_2, UID_2);
+        assertThat(state2.shouldBeVisible()).isFalse();
+        assertThat(state2.isAllowed()).isTrue();
+    }
+}