1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 package com.android.settings.datausage; 17 18 import static com.google.common.truth.Truth.assertThat; 19 20 import static org.mockito.ArgumentMatchers.any; 21 import static org.mockito.ArgumentMatchers.anyBoolean; 22 import static org.mockito.ArgumentMatchers.eq; 23 import static org.mockito.ArgumentMatchers.nullable; 24 import static org.mockito.Mockito.doAnswer; 25 import static org.mockito.Mockito.doNothing; 26 import static org.mockito.Mockito.doReturn; 27 import static org.mockito.Mockito.mock; 28 import static org.mockito.Mockito.never; 29 import static org.mockito.Mockito.spy; 30 import static org.mockito.Mockito.verify; 31 import static org.mockito.Mockito.when; 32 33 import android.content.Context; 34 import android.content.pm.ApplicationInfo; 35 import android.os.Process; 36 37 import androidx.preference.PreferenceManager; 38 import androidx.preference.PreferenceScreen; 39 40 import com.android.internal.logging.nano.MetricsProto; 41 import com.android.settings.applications.AppStateBaseBridge; 42 import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState; 43 import com.android.settings.testutils.FakeFeatureFactory; 44 import com.android.settings.testutils.shadow.ShadowRestrictedLockUtils; 45 import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal; 46 import com.android.settingslib.applications.ApplicationsState; 47 import com.android.settingslib.applications.ApplicationsState.AppEntry; 48 49 import org.junit.Before; 50 import org.junit.Test; 51 import org.junit.runner.RunWith; 52 import org.mockito.Mock; 53 import org.mockito.MockitoAnnotations; 54 import org.robolectric.RobolectricTestRunner; 55 import org.robolectric.RuntimeEnvironment; 56 import org.robolectric.annotation.Config; 57 import org.robolectric.annotation.Implements; 58 import org.robolectric.util.ReflectionHelpers; 59 60 import java.util.ArrayList; 61 62 @RunWith(RobolectricTestRunner.class) 63 @Config(shadows = { 64 ShadowRestrictedLockUtils.class, 65 ShadowRestrictedLockUtilsInternal.class, 66 UnrestrictedDataAccessPreferenceControllerTest.ShadowAppStateBaseBridge.class 67 }) 68 public class UnrestrictedDataAccessPreferenceControllerTest { 69 @Mock 70 private ApplicationsState mState; 71 @Mock 72 private ApplicationsState.Session mSession; 73 74 private Context mContext; 75 private FakeFeatureFactory mFeatureFactory; 76 private PreferenceManager mPreferenceManager; 77 private PreferenceScreen mPreferenceScreen; 78 private UnrestrictedDataAccess mFragment; 79 private UnrestrictedDataAccessPreferenceController mController; 80 81 @Before setUp()82 public void setUp() { 83 MockitoAnnotations.initMocks(this); 84 mContext = RuntimeEnvironment.application; 85 mFeatureFactory = FakeFeatureFactory.setupForTest(); 86 ReflectionHelpers.setStaticField(ApplicationsState.class, "sInstance", mState); 87 when(mState.newSession(any())).thenReturn(mSession); 88 mController = spy(new UnrestrictedDataAccessPreferenceController(mContext, "pref_key")); 89 } 90 91 @Test shouldAddPreference_forApps_shouldBeTrue()92 public void shouldAddPreference_forApps_shouldBeTrue() { 93 final int uid = Process.FIRST_APPLICATION_UID + 10; 94 final AppEntry entry = createAppEntry(uid); 95 assertThat(UnrestrictedDataAccessPreferenceController.shouldAddPreference(entry)).isTrue(); 96 } 97 98 @Test shouldAddPreference_forNonApps_shouldBeFalse()99 public void shouldAddPreference_forNonApps_shouldBeFalse() { 100 final int uid = Process.FIRST_APPLICATION_UID - 10; 101 final AppEntry entry = createAppEntry(uid); 102 assertThat(UnrestrictedDataAccessPreferenceController.shouldAddPreference(entry)).isFalse(); 103 } 104 105 @Test logSpecialPermissionChange()106 public void logSpecialPermissionChange() { 107 mController.logSpecialPermissionChange(true, "app"); 108 verify(mFeatureFactory.metricsFeatureProvider).action(nullable(Context.class), 109 eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_ALLOW), eq("app")); 110 111 mController.logSpecialPermissionChange(false, "app"); 112 verify(mFeatureFactory.metricsFeatureProvider).action(nullable(Context.class), 113 eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_DENY), eq("app")); 114 } 115 116 @Test onRebuildComplete_restricted_shouldBeDisabled()117 public void onRebuildComplete_restricted_shouldBeDisabled() { 118 mFragment = spy(new UnrestrictedDataAccess()); 119 doNothing().when(mFragment).setLoading(anyBoolean(), anyBoolean()); 120 mController.setParentFragment(mFragment); 121 mPreferenceManager = new PreferenceManager(mContext); 122 mPreferenceScreen = spy(mPreferenceManager.createPreferenceScreen(mContext)); 123 doReturn(mPreferenceManager).when(mFragment).getPreferenceManager(); 124 doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); 125 doReturn(0).when(mPreferenceScreen).getPreferenceCount(); 126 final DataSaverBackend dataSaverBackend = mock(DataSaverBackend.class); 127 ReflectionHelpers.setField(mController, "mDataSaverBackend", dataSaverBackend); 128 ReflectionHelpers.setField(mController, "mScreen", mPreferenceScreen); 129 130 final String testPkg1 = "com.example.one"; 131 final String testPkg2 = "com.example.two"; 132 ShadowRestrictedLockUtilsInternal.setRestrictedPkgs(testPkg2); 133 134 doAnswer((invocation) -> { 135 final UnrestrictedDataAccessPreference preference = invocation.getArgument(0); 136 final AppEntry entry = preference.getEntry(); 137 // Verify preference is disabled by admin and the summary is changed accordingly. 138 if (testPkg1.equals(entry.info.packageName)) { 139 assertThat(preference.isDisabledByAdmin()).isFalse(); 140 assertThat(preference.getSummary()).isEqualTo(""); 141 } else if (testPkg2.equals(entry.info.packageName)) { 142 assertThat(preference.isDisabledByAdmin()).isTrue(); 143 assertThat(preference.getSummary()).isEqualTo( 144 mContext.getString( 145 com.android.settingslib.widget.restricted.R.string.disabled_by_admin)); 146 } 147 assertThat(preference.isChecked()).isFalse(); 148 preference.performClick(); 149 // Verify that when the preference is clicked, support details intent is launched 150 // if the preference is disabled by admin, otherwise the switch is toggled. 151 if (testPkg1.equals(entry.info.packageName)) { 152 assertThat(preference.isChecked()).isTrue(); 153 assertThat(ShadowRestrictedLockUtils.hasAdminSupportDetailsIntentLaunched()) 154 .isFalse(); 155 } else if (testPkg2.equals(entry.info.packageName)) { 156 assertThat(preference.isChecked()).isFalse(); 157 assertThat(ShadowRestrictedLockUtils.hasAdminSupportDetailsIntentLaunched()) 158 .isTrue(); 159 } 160 ShadowRestrictedLockUtils.clearAdminSupportDetailsIntentLaunch(); 161 return null; 162 }).when(mPreferenceScreen).addPreference(any(UnrestrictedDataAccessPreference.class)); 163 164 mController.onRebuildComplete(createAppEntries(testPkg1, testPkg2)); 165 } 166 167 @Test onRebuildComplete_ecmRestricted_shouldBeDisabled()168 public void onRebuildComplete_ecmRestricted_shouldBeDisabled() { 169 mFragment = spy(new UnrestrictedDataAccess()); 170 mContext = spy(mContext); 171 doNothing().when(mFragment).setLoading(anyBoolean(), anyBoolean()); 172 mController.setParentFragment(mFragment); 173 mPreferenceManager = new PreferenceManager(mContext); 174 mPreferenceScreen = spy(mPreferenceManager.createPreferenceScreen(mContext)); 175 doReturn(mPreferenceManager).when(mFragment).getPreferenceManager(); 176 doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); 177 doReturn(0).when(mPreferenceScreen).getPreferenceCount(); 178 final DataSaverBackend dataSaverBackend = mock(DataSaverBackend.class); 179 ReflectionHelpers.setField(mController, "mDataSaverBackend", dataSaverBackend); 180 ReflectionHelpers.setField(mController, "mScreen", mPreferenceScreen); 181 182 final String testPkg = "com.example.disabled"; 183 doNothing().when(mContext).startActivity(any()); 184 ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs(testPkg); 185 186 doAnswer((invocation) -> { 187 final UnrestrictedDataAccessPreference preference = invocation.getArgument(0); 188 // Verify preference is disabled by ecm and the summary is changed accordingly. 189 assertThat(preference.isDisabledByEcm()).isTrue(); 190 assertThat(preference.getSummary().toString()).isEqualTo( 191 mContext.getString( 192 com.android.settingslib.R.string.disabled_by_app_ops_text)); 193 assertThat(preference.isChecked()).isFalse(); 194 preference.performClick(); 195 // Verify that when the preference is clicked, ecm details intent is launched 196 assertThat(preference.isChecked()).isFalse(); 197 verify(mContext).startActivity(any()); 198 199 return null; 200 }).when(mPreferenceScreen).addPreference(any(UnrestrictedDataAccessPreference.class)); 201 202 mController.onRebuildComplete(createAppEntries(testPkg)); 203 verify(mPreferenceScreen).addPreference(any(UnrestrictedDataAccessPreference.class)); 204 } 205 206 @Test onRebuildComplete_ecmNotRestricted_notDisabled()207 public void onRebuildComplete_ecmNotRestricted_notDisabled() { 208 mFragment = spy(new UnrestrictedDataAccess()); 209 mContext = spy(mContext); 210 doNothing().when(mFragment).setLoading(anyBoolean(), anyBoolean()); 211 mController.setParentFragment(mFragment); 212 mPreferenceManager = new PreferenceManager(mContext); 213 mPreferenceScreen = spy(mPreferenceManager.createPreferenceScreen(mContext)); 214 doReturn(mPreferenceManager).when(mFragment).getPreferenceManager(); 215 doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); 216 doReturn(0).when(mPreferenceScreen).getPreferenceCount(); 217 final DataSaverBackend dataSaverBackend = mock(DataSaverBackend.class); 218 ReflectionHelpers.setField(mController, "mDataSaverBackend", dataSaverBackend); 219 ReflectionHelpers.setField(mController, "mScreen", mPreferenceScreen); 220 221 final String testPkg = "com.example.enabled"; 222 doNothing().when(mContext).startActivity(any()); 223 ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs(); 224 225 doAnswer((invocation) -> { 226 final UnrestrictedDataAccessPreference preference = invocation.getArgument(0); 227 assertThat(preference.isDisabledByEcm()).isFalse(); 228 assertThat(preference.getSummary()).isEqualTo(""); 229 assertThat(preference.isChecked()).isFalse(); 230 preference.performClick(); 231 // Verify that when the preference is clicked, ecm details intent is not launched 232 assertThat(preference.isChecked()).isTrue(); 233 verify(mContext, never()).startActivity(any()); 234 235 return null; 236 }).when(mPreferenceScreen).addPreference(any(UnrestrictedDataAccessPreference.class)); 237 238 mController.onRebuildComplete(createAppEntries(testPkg)); 239 verify(mPreferenceScreen).addPreference(any(UnrestrictedDataAccessPreference.class)); 240 } 241 createAppEntries(String... packageNames)242 private ArrayList<AppEntry> createAppEntries(String... packageNames) { 243 final ArrayList<AppEntry> appEntries = new ArrayList<>(); 244 for (int i = 0; i < packageNames.length; ++i) { 245 final ApplicationInfo info = new ApplicationInfo(); 246 info.packageName = packageNames[i]; 247 info.uid = Process.FIRST_APPLICATION_UID + i; 248 info.sourceDir = info.packageName; 249 final AppEntry appEntry = spy(new AppEntry(mContext, info, i)); 250 appEntry.extraInfo = new DataUsageState(false, false); 251 doNothing().when(appEntry).ensureLabel(any(Context.class)); 252 ReflectionHelpers.setField(appEntry, "info", info); 253 appEntries.add(appEntry); 254 } 255 return appEntries; 256 } 257 createAppEntry(int uid)258 private AppEntry createAppEntry(int uid) { 259 final ApplicationInfo info = new ApplicationInfo(); 260 info.packageName = "com.example.three"; 261 info.uid = uid; 262 info.sourceDir = info.packageName; 263 return new AppEntry(mContext, info, uid); 264 } 265 266 @Implements(AppStateBaseBridge.class) 267 public static class ShadowAppStateBaseBridge { 268 __constructor__(ApplicationsState appState, AppStateBaseBridge.Callback callback)269 public void __constructor__(ApplicationsState appState, 270 AppStateBaseBridge.Callback callback) { 271 } 272 } 273 } 274