diff --git a/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridge.java b/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridge.java index ccfc87e7bbf..4e9a96efb5f 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 @@ import java.util.List; * 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 @@ public class AppStateAlarmsAndRemindersBridge extends AppStateAppOpsBridge { @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 95835cbb326..7ef9e89bbb5 100644 --- a/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetails.java +++ b/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetails.java @@ -18,6 +18,7 @@ package com.android.settings.applications.appinfo; 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.R; 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 @@ public class AlarmsAndRemindersDetails extends AppInfoWithHeader 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 @@ public class AlarmsAndRemindersDetails extends AppInfoWithHeader 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 class AlarmsAndRemindersDetails extends AppInfoWithHeader 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 @@ public class AlarmsAndRemindersDetails extends AppInfoWithHeader } 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 @@ public class AlarmsAndRemindersDetails extends AppInfoWithHeader } 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 c3bceade2e5..313ec11f24d 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.ApplicationInfo; 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 @@ public class AlarmsAndRemindersDetailsTest { @Mock private AppStateAlarmsAndRemindersBridge mAppStateBridge; @Mock - private AppStateAppOpsBridge.PermissionState mPermissionState; + private AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState mPermissionState; private AlarmsAndRemindersDetails mFragment = new AlarmsAndRemindersDetails(); @@ -96,12 +95,12 @@ public class AlarmsAndRemindersDetailsTest { 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 @@ public class AlarmsAndRemindersDetailsTest { 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 00000000000..f56da05eefc --- /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(); + } +}