diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/ManageExternalStorageDetailsTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/ManageExternalStorageDetailsTest.java new file mode 100644 index 00000000000..ed85c013698 --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appinfo/ManageExternalStorageDetailsTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2020 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.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.AppOpsManager; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; + +import androidx.preference.SwitchPreference; + +import com.android.settings.applications.AppStateAppOpsBridge.PermissionState; +import com.android.settings.applications.AppStateManageExternalStorageBridge; +import com.android.settings.testutils.shadow.ShadowUserManager; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +import java.util.HashMap; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowUserManager.class}) +public class ManageExternalStorageDetailsTest { + + @Mock + private AppOpsManager mAppOpsManager; + @Mock + private SwitchPreference mSwitchPref; + @Mock + private MetricsFeatureProvider mMetricsFeatureProvider; + @Mock + private AppStateManageExternalStorageBridge mBridge; + + private ManageExternalStorageDetails mFragment; + + private final HashMap mPkgToOpModeMap = new HashMap<>(); + private final HashMap mUidToOpModeMap = new HashMap<>(); + + @Before + public void setUp() { + // Reset the global trackers + mPkgToOpModeMap.clear(); + mUidToOpModeMap.clear(); + + //Start the mockin' + MockitoAnnotations.initMocks(this); + + mFragment = new ManageExternalStorageDetails(); + ReflectionHelpers.setField(mFragment, "mAppOpsManager", mAppOpsManager); + ReflectionHelpers.setField(mFragment, "mSwitchPref", mSwitchPref); + ReflectionHelpers.setField(mFragment, "mBridge", mBridge); + ReflectionHelpers.setField(mFragment, "mMetricsFeatureProvider", + mMetricsFeatureProvider); + + mockAppOpsOperations(); + } + + @Test + public void onPreferenceChange_enableManageExternalStorage_shouldTriggerAppOpsManager() { + // Inject mock package details + final int mockUid = 23333; + final String mockPkgName = "com.mock.pkg.1"; + PackageInfo pkgInfo = mock(PackageInfo.class); + pkgInfo.applicationInfo = new ApplicationInfo(); + pkgInfo.applicationInfo.uid = mockUid; + + ReflectionHelpers.setField(mFragment, "mPackageInfo", pkgInfo); + ReflectionHelpers.setField(mFragment, "mPackageName", mockPkgName); + + // Set the initial state to be disabled + injectPermissionState(false); + + // Simulate a preference change + mFragment.onPreferenceChange(mSwitchPref, /* newValue */ true); + + // Verify that mAppOpsManager was called to allow the app-op + verify(mAppOpsManager, times(1)) + .setMode(anyInt(), anyInt(), nullable(String.class), anyInt()); + assertThat(mPkgToOpModeMap).containsExactly(mockPkgName, AppOpsManager.MODE_ALLOWED); + assertThat(mUidToOpModeMap).containsExactly(mockUid, AppOpsManager.MODE_ALLOWED); + + // Verify the mSwitchPref was enabled + ArgumentCaptor acSetEnabled = ArgumentCaptor.forClass(Boolean.class); + verify(mSwitchPref, times(1)).setEnabled(acSetEnabled.capture()); + assertThat(acSetEnabled.getAllValues()).containsExactly(true); + + // Verify that mSwitchPref was toggled to on + ArgumentCaptor acSetChecked = ArgumentCaptor.forClass(Boolean.class); + verify(mSwitchPref, times(1)).setChecked(acSetChecked.capture()); + assertThat(acSetChecked.getAllValues()).containsExactly(true); + } + + @Test + public void onPreferenceChange_disableManageExternalStorage_shouldTriggerAppOpsManager() { + // Inject mock package details + final int mockUid = 24444; + final String mockPkgName = "com.mock.pkg.2"; + PackageInfo pkgInfo = mock(PackageInfo.class); + pkgInfo.applicationInfo = new ApplicationInfo(); + pkgInfo.applicationInfo.uid = mockUid; + + ReflectionHelpers.setField(mFragment, "mPackageInfo", pkgInfo); + ReflectionHelpers.setField(mFragment, "mPackageName", mockPkgName); + + // Set the initial state to be enabled + injectPermissionState(true); + + // Simulate a preference change + mFragment.onPreferenceChange(mSwitchPref, /* newValue */ false); + + // Verify that mAppOpsManager was called to deny the app-op + verify(mAppOpsManager, times(1)) + .setMode(anyInt(), anyInt(), nullable(String.class), anyInt()); + assertThat(mPkgToOpModeMap).containsExactly(mockPkgName, AppOpsManager.MODE_ERRORED); + assertThat(mUidToOpModeMap).containsExactly(mockUid, AppOpsManager.MODE_ERRORED); + + // Verify the mSwitchPref was enabled + ArgumentCaptor acSetEnabled = ArgumentCaptor.forClass(Boolean.class); + verify(mSwitchPref, times(1)).setEnabled(acSetEnabled.capture()); + assertThat(acSetEnabled.getAllValues()).containsExactly(true); + + // Verify that mSwitchPref was toggled to off + ArgumentCaptor acSetChecked = ArgumentCaptor.forClass(Boolean.class); + verify(mSwitchPref, times(1)).setChecked(acSetChecked.capture()); + assertThat(acSetChecked.getAllValues()).containsExactly(false); + } + + private void injectPermissionState(boolean enabled) { + PermissionState state = new PermissionState(null, null); + state.permissionDeclared = true; + state.appOpMode = enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED; + ReflectionHelpers.setField(mFragment, "mPermissionState", state); + } + + private void mockAppOpsOperations() { + Answer answerSetMode = invocation -> { + int code = invocation.getArgument(0); + int uid = invocation.getArgument(1); + String packageName = invocation.getArgument(2); + int mode = invocation.getArgument(3); + + if (code != AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE) { + return null; + } + + mPkgToOpModeMap.put(packageName, mode); + mUidToOpModeMap.put(uid, mode); + + return null; + }; + + doAnswer(answerSetMode).when(mAppOpsManager) + .setMode(anyInt(), anyInt(), nullable(String.class), anyInt()); + + Answer answerPermState = invocation -> { + String packageName = invocation.getArgument(0); + PermissionState res = new PermissionState(packageName, null); + res.permissionDeclared = false; + + if (mPkgToOpModeMap.containsKey(packageName)) { + res.permissionDeclared = true; + res.appOpMode = mPkgToOpModeMap.get(packageName); + } + return res; + }; + + doAnswer(answerPermState).when(mBridge) + .getManageExternalStoragePermState(nullable(String.class), anyInt()); + } +}