1 /* 2 * Copyright (C) 2022 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 17 package com.android.car.settings.applications.performance; 18 19 import static android.car.settings.CarSettings.Secure.KEY_PACKAGES_DISABLED_ON_RESOURCE_OVERUSE; 20 import static android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS; 21 22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; 23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 24 25 import static com.google.common.truth.Truth.assertThat; 26 import static com.google.common.truth.Truth.assertWithMessage; 27 28 import static org.mockito.ArgumentMatchers.any; 29 import static org.mockito.ArgumentMatchers.anyInt; 30 import static org.mockito.ArgumentMatchers.anyLong; 31 import static org.mockito.ArgumentMatchers.anyString; 32 import static org.mockito.ArgumentMatchers.eq; 33 import static org.mockito.Mockito.doNothing; 34 import static org.mockito.Mockito.never; 35 import static org.mockito.Mockito.spy; 36 import static org.mockito.Mockito.verifyNoMoreInteractions; 37 import static org.mockito.Mockito.when; 38 39 import android.car.Car; 40 import android.car.drivingstate.CarUxRestrictions; 41 import android.car.watchdog.CarWatchdogManager; 42 import android.car.watchdog.PackageKillableState; 43 import android.content.Context; 44 import android.content.DialogInterface; 45 import android.content.Intent; 46 import android.content.pm.ActivityInfo; 47 import android.content.pm.ApplicationInfo; 48 import android.content.pm.PackageManager; 49 import android.content.pm.ResolveInfo; 50 import android.net.Uri; 51 import android.os.UserHandle; 52 import android.provider.Settings; 53 54 import androidx.lifecycle.LifecycleOwner; 55 import androidx.preference.Preference; 56 import androidx.preference.PreferenceManager; 57 import androidx.preference.PreferenceScreen; 58 import androidx.test.annotation.UiThreadTest; 59 import androidx.test.core.app.ApplicationProvider; 60 import androidx.test.ext.junit.runners.AndroidJUnit4; 61 62 import com.android.car.settings.common.ConfirmationDialogFragment; 63 import com.android.car.settings.common.FragmentController; 64 import com.android.car.settings.common.LogicalPreferenceGroup; 65 import com.android.car.settings.common.PreferenceControllerTestUtil; 66 import com.android.car.settings.testutils.TestLifecycleOwner; 67 import com.android.car.ui.preference.CarUiTwoActionTextPreference; 68 69 import org.junit.After; 70 import org.junit.Before; 71 import org.junit.Test; 72 import org.junit.runner.RunWith; 73 import org.mockito.ArgumentCaptor; 74 import org.mockito.Captor; 75 import org.mockito.Mock; 76 import org.mockito.MockitoSession; 77 import org.mockito.quality.Strictness; 78 79 import java.io.File; 80 import java.util.List; 81 import java.util.stream.Collectors; 82 83 @RunWith(AndroidJUnit4.class) 84 public final class PerfImpactingAppsPreferenceControllerTest { 85 private static final int TEST_USER_ID = 100; 86 private static final int TEST_PKG_UID = 10000001; 87 private static final String TEST_PKG_NAME = "test.package.name"; 88 private static final int TEST_PRIVILEGE_PKG_UID = 10000002; 89 private static final String TEST_PRIVILEGE_PKG_NAME = "test.privilege.package.name"; 90 private static final String TEST_DISABLED_PACKAGES_SETTING_STRING = 91 TEST_PKG_NAME + ";" + TEST_PRIVILEGE_PKG_NAME; 92 93 private final Context mContext = spy(ApplicationProvider.getApplicationContext()); 94 95 private MockitoSession mMockingSession; 96 private LifecycleOwner mLifecycleOwner; 97 private PerfImpactingAppsPreferenceController mController; 98 private LogicalPreferenceGroup mPreferenceGroup; 99 100 @Captor 101 private ArgumentCaptor<Car.CarServiceLifecycleListener> mCarLifecycleCaptor; 102 @Captor 103 private ArgumentCaptor<ConfirmationDialogFragment> mDialogFragmentCaptor; 104 @Mock 105 private PackageManager mMockPackageManager; 106 @Mock 107 private FragmentController mMockFragmentController; 108 @Mock 109 private Car mMockCar; 110 @Mock 111 private CarWatchdogManager mMockCarWatchdogManager; 112 113 @Before 114 @UiThreadTest setUp()115 public void setUp() { 116 mMockingSession = mockitoSession() 117 .initMocks(this) 118 .mockStatic(Car.class) 119 .mockStatic(Settings.Secure.class) 120 .strictness(Strictness.LENIENT) 121 .startMocking(); 122 mLifecycleOwner = new TestLifecycleOwner(); 123 124 mDialogFragmentCaptor = ArgumentCaptor.forClass(ConfirmationDialogFragment.class); 125 126 when(mContext.getPackageManager()).thenReturn(mMockPackageManager); 127 when(Car.createCar(any(), any(), anyLong(), mCarLifecycleCaptor.capture())).then( 128 invocation -> { 129 Car.CarServiceLifecycleListener listener = mCarLifecycleCaptor.getValue(); 130 listener.onLifecycleChanged(mMockCar, true); 131 return mMockCar; 132 }); 133 when(mMockCar.getCarManager(Car.CAR_WATCHDOG_SERVICE)).thenReturn(mMockCarWatchdogManager); 134 when(mMockCarWatchdogManager.getPackageKillableStatesAsUser( 135 UserHandle.getUserHandleForUid(TEST_PKG_UID))) 136 .thenReturn(List.of( 137 new PackageKillableState(TEST_PKG_NAME, TEST_USER_ID, 138 PackageKillableState.KILLABLE_STATE_YES), 139 new PackageKillableState(TEST_PRIVILEGE_PKG_NAME, TEST_USER_ID, 140 PackageKillableState.KILLABLE_STATE_NEVER))); 141 142 CarUxRestrictions restrictions = new CarUxRestrictions.Builder(/* reqOpt= */ true, 143 CarUxRestrictions.UX_RESTRICTIONS_BASELINE, /* timestamp= */ 0).build(); 144 145 PreferenceManager preferenceManager = new PreferenceManager(mContext); 146 PreferenceScreen screen = preferenceManager.createPreferenceScreen(mContext); 147 mPreferenceGroup = new LogicalPreferenceGroup(mContext); 148 screen.addPreference(mPreferenceGroup); 149 150 mController = new PerfImpactingAppsPreferenceController(mContext, 151 /* preferenceKey= */ "key", mMockFragmentController, restrictions); 152 153 PreferenceControllerTestUtil.assignPreference(mController, mPreferenceGroup); 154 155 initController(); 156 } 157 158 @After 159 @UiThreadTest tearDown()160 public void tearDown() { 161 (new File(mContext.getDataDir(), TEST_PKG_NAME)).delete(); 162 (new File(mContext.getDataDir(), TEST_PRIVILEGE_PKG_NAME)).delete(); 163 164 mMockingSession.finishMocking(); 165 } 166 167 @Test 168 @UiThreadTest onPreferenceClick_primaryAppAction_sendAppDetailIntent()169 public void onPreferenceClick_primaryAppAction_sendAppDetailIntent() { 170 doNothing().when(mContext).startActivity(any()); 171 172 Preference actualPreference = mPreferenceGroup.getPreference(0); 173 174 Preference.OnPreferenceClickListener onPreferenceClickListener = 175 actualPreference.getOnPreferenceClickListener(); 176 177 onPreferenceClickListener.onPreferenceClick(actualPreference); 178 179 ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); 180 verify(mContext).startActivity(captor.capture()); 181 Intent intent = captor.getValue(); 182 assertThat(intent).isNotNull(); 183 assertWithMessage("Intent action").that(intent.getAction()) 184 .isEqualTo(ACTION_APPLICATION_DETAILS_SETTINGS); 185 assertWithMessage("Intent data").that(intent.getData()) 186 .isEqualTo(Uri.parse("package:" + TEST_PKG_NAME)); 187 } 188 189 @Test 190 @UiThreadTest onPreferenceClick_showConfirmationDialog_prioritizeApp()191 public void onPreferenceClick_showConfirmationDialog_prioritizeApp() { 192 CarUiTwoActionTextPreference actualPreference = 193 (CarUiTwoActionTextPreference) mPreferenceGroup.getPreference(0); 194 195 actualPreference.performSecondaryActionClick(); 196 197 verify(mMockFragmentController).showDialog(mDialogFragmentCaptor.capture(), anyString()); 198 ConfirmationDialogFragment dialogFragment = mDialogFragmentCaptor.getValue(); 199 200 assertThat(dialogFragment).isNotNull(); 201 202 dialogFragment.onClick(dialogFragment.getDialog(), DialogInterface.BUTTON_POSITIVE); 203 204 verify(mMockCarWatchdogManager).setKillablePackageAsUser(TEST_PKG_NAME, 205 UserHandle.getUserHandleForUid(TEST_PKG_UID), 206 /* isKillable= */ false); 207 } 208 209 @Test 210 @UiThreadTest onPreferenceClick_showConfirmationDialog_prioritizePrivilegedApp()211 public void onPreferenceClick_showConfirmationDialog_prioritizePrivilegedApp() { 212 CarUiTwoActionTextPreference actualPreference = 213 (CarUiTwoActionTextPreference) mPreferenceGroup.getPreference(1); 214 215 actualPreference.performSecondaryActionClick(); 216 217 verify(mMockFragmentController).showDialog(mDialogFragmentCaptor.capture(), anyString()); 218 ConfirmationDialogFragment dialogFragment = mDialogFragmentCaptor.getValue(); 219 220 assertThat(dialogFragment).isNotNull(); 221 222 dialogFragment.onClick(dialogFragment.getDialog(), DialogInterface.BUTTON_POSITIVE); 223 224 verify(mMockPackageManager).setApplicationEnabledSetting(TEST_PRIVILEGE_PKG_NAME, 225 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ 0); 226 } 227 228 @Test 229 @UiThreadTest onPreferenceClick_showConfirmationDialog_cancel()230 public void onPreferenceClick_showConfirmationDialog_cancel() { 231 CarUiTwoActionTextPreference actualPreference = 232 (CarUiTwoActionTextPreference) mPreferenceGroup.getPreference(1); 233 234 actualPreference.performSecondaryActionClick(); 235 236 verify(mMockFragmentController).showDialog(mDialogFragmentCaptor.capture(), anyString()); 237 ConfirmationDialogFragment dialogFragment = mDialogFragmentCaptor.getValue(); 238 239 assertThat(dialogFragment).isNotNull(); 240 241 dialogFragment.onClick(dialogFragment.getDialog(), DialogInterface.BUTTON_NEGATIVE); 242 243 verify(mMockPackageManager, never()) 244 .setApplicationEnabledSetting(anyString(), anyInt(), anyInt()); 245 verifyNoMoreInteractions(mMockCarWatchdogManager); 246 } 247 248 @Test 249 @UiThreadTest onCreate_perfImpactingApps_withNoPackages()250 public void onCreate_perfImpactingApps_withNoPackages() { 251 when(Settings.Secure.getString(any(), eq(KEY_PACKAGES_DISABLED_ON_RESOURCE_OVERUSE))) 252 .thenReturn(""); 253 254 mController.onDestroy(mLifecycleOwner); 255 256 mController.onCreate(mLifecycleOwner); 257 258 assertWithMessage("Preference group count") 259 .that(mPreferenceGroup.getPreferenceCount()).isEqualTo(0); 260 } 261 initController()262 private void initController() { 263 List<ResolveInfo> expectedResultInfos = getResultInfos(); 264 265 when(Settings.Secure.getString(any(), eq(KEY_PACKAGES_DISABLED_ON_RESOURCE_OVERUSE))) 266 .thenReturn(TEST_DISABLED_PACKAGES_SETTING_STRING); 267 when(mMockPackageManager.queryIntentActivities(any(), any())) 268 .thenReturn(expectedResultInfos); 269 270 mController.onCreate(mLifecycleOwner); 271 272 List<CarUiTwoActionTextPreference> expectedPreferences = 273 getPreferences(expectedResultInfos); 274 275 assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(expectedPreferences.size()); 276 277 for (int idx = 0; idx < expectedPreferences.size(); idx++) { 278 assertThatPreferenceAreEqual(idx, 279 (CarUiTwoActionTextPreference) mPreferenceGroup.getPreference(idx), 280 expectedPreferences.get(idx)); 281 } 282 } 283 getPreferences(List<ResolveInfo> resolveInfos)284 private List<CarUiTwoActionTextPreference> getPreferences(List<ResolveInfo> resolveInfos) { 285 return resolveInfos.stream().map( 286 resolveInfo -> 287 new PerfImpactingAppsPreferenceController.PerformanceImpactingAppPreference( 288 mContext, resolveInfo.activityInfo.applicationInfo)) 289 .collect(Collectors.toList()); 290 } 291 getResultInfos()292 private List<ResolveInfo> getResultInfos() { 293 // Non-privileged Package 294 ResolveInfo resolveInfo1 = new ResolveInfo(); 295 resolveInfo1.activityInfo = new ActivityInfo(); 296 resolveInfo1.activityInfo.applicationInfo = new ApplicationInfo(); 297 resolveInfo1.activityInfo.applicationInfo.uid = TEST_PKG_UID; 298 resolveInfo1.activityInfo.applicationInfo.packageName = TEST_PKG_NAME; 299 300 File appFile = new File(mContext.getDataDir(), TEST_PKG_NAME); 301 assertWithMessage("%s dir", TEST_PKG_NAME).that(appFile.mkdir()).isTrue(); 302 303 resolveInfo1.activityInfo.applicationInfo.sourceDir = appFile.getAbsolutePath(); 304 305 // Privileged Package 306 ResolveInfo resolveInfo2 = new ResolveInfo(); 307 resolveInfo2.activityInfo = new ActivityInfo(); 308 resolveInfo2.activityInfo.applicationInfo = new ApplicationInfo(); 309 resolveInfo2.activityInfo.applicationInfo.uid = TEST_PRIVILEGE_PKG_UID; 310 resolveInfo2.activityInfo.applicationInfo.packageName = TEST_PRIVILEGE_PKG_NAME; 311 312 appFile = new File(mContext.getDataDir(), TEST_PRIVILEGE_PKG_NAME); 313 assertWithMessage("%s dir", TEST_PRIVILEGE_PKG_NAME).that(appFile.mkdir()).isTrue(); 314 315 resolveInfo2.activityInfo.applicationInfo.sourceDir = appFile.getAbsolutePath(); 316 317 return List.of(resolveInfo1, resolveInfo2); 318 } 319 assertThatPreferenceAreEqual(int index, CarUiTwoActionTextPreference p1, CarUiTwoActionTextPreference p2)320 private static void assertThatPreferenceAreEqual(int index, CarUiTwoActionTextPreference p1, 321 CarUiTwoActionTextPreference p2) { 322 assertWithMessage("Preference %s key", index).that(p1.getKey()).isEqualTo(p2.getKey()); 323 assertWithMessage("Preference %s title", index).that(p1.getTitle().toString()) 324 .isEqualTo(p2.getTitle().toString()); 325 assertWithMessage("Preference %s secondary action text", index) 326 .that(p2.getSecondaryActionText().toString()) 327 .isEqualTo(p2.getSecondaryActionText().toString()); 328 } 329 } 330