1 /* 2 * Copyright (C) 2017 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.settings.applications; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static org.mockito.Matchers.any; 21 import static org.mockito.Matchers.anyInt; 22 import static org.mockito.Matchers.anyLong; 23 import static org.mockito.Matchers.anyString; 24 import static org.mockito.Matchers.argThat; 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.times; 31 import static org.mockito.Mockito.verify; 32 import static org.mockito.Mockito.when; 33 34 import android.app.Application; 35 import android.app.usage.UsageStats; 36 import android.app.usage.UsageStatsManager; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.content.pm.ApplicationInfo; 40 import android.content.pm.PackageManager; 41 import android.content.pm.ResolveInfo; 42 import android.os.UserHandle; 43 import android.os.UserManager; 44 import android.support.v7.preference.Preference; 45 import android.support.v7.preference.PreferenceCategory; 46 import android.support.v7.preference.PreferenceScreen; 47 import android.text.TextUtils; 48 49 import com.android.settings.R; 50 import com.android.settings.testutils.SettingsRobolectricTestRunner; 51 import com.android.settingslib.applications.AppUtils; 52 import com.android.settingslib.applications.ApplicationsState; 53 import com.android.settingslib.applications.instantapps.InstantAppDataProvider; 54 55 import org.junit.Before; 56 import org.junit.Test; 57 import org.junit.runner.RunWith; 58 import org.mockito.ArgumentCaptor; 59 import org.mockito.ArgumentMatcher; 60 import org.mockito.Mock; 61 import org.mockito.MockitoAnnotations; 62 import org.robolectric.RuntimeEnvironment; 63 import org.robolectric.annotation.Config; 64 import org.robolectric.util.ReflectionHelpers; 65 66 import java.util.ArrayList; 67 import java.util.List; 68 69 @RunWith(SettingsRobolectricTestRunner.class) 70 public class RecentAppsPreferenceControllerTest { 71 72 @Mock 73 private PreferenceScreen mScreen; 74 @Mock 75 private PreferenceCategory mCategory; 76 @Mock 77 private Preference mSeeAllPref; 78 @Mock 79 private PreferenceCategory mDivider; 80 @Mock 81 private UsageStatsManager mUsageStatsManager; 82 @Mock 83 private UserManager mUserManager; 84 @Mock 85 private ApplicationsState mAppState; 86 @Mock 87 private PackageManager mPackageManager; 88 @Mock 89 private ApplicationsState.AppEntry mAppEntry; 90 @Mock 91 private ApplicationInfo mApplicationInfo; 92 93 private Context mContext; 94 private RecentAppsPreferenceController mController; 95 96 @Before 97 public void setUp() { 98 MockitoAnnotations.initMocks(this); 99 mContext = spy(RuntimeEnvironment.application); 100 doReturn(mUsageStatsManager).when(mContext).getSystemService(Context.USAGE_STATS_SERVICE); 101 doReturn(mUserManager).when(mContext).getSystemService(Context.USER_SERVICE); 102 doReturn(mPackageManager).when(mContext).getPackageManager(); 103 104 mController = new RecentAppsPreferenceController(mContext, mAppState, null); 105 when(mScreen.findPreference(anyString())).thenReturn(mCategory); 106 107 when(mScreen.findPreference(RecentAppsPreferenceController.KEY_SEE_ALL)) 108 .thenReturn(mSeeAllPref); 109 when(mScreen.findPreference(RecentAppsPreferenceController.KEY_DIVIDER)) 110 .thenReturn(mDivider); 111 when(mCategory.getContext()).thenReturn(mContext); 112 } 113 114 @Test 115 public void isAlwaysAvailable() { 116 assertThat(mController.isAvailable()).isTrue(); 117 } 118 119 @Test 120 public void doNotIndexCategory() { 121 final List<String> nonIndexable = new ArrayList<>(); 122 123 mController.updateNonIndexableKeys(nonIndexable); 124 125 assertThat(nonIndexable).containsAllOf(mController.getPreferenceKey(), 126 RecentAppsPreferenceController.KEY_DIVIDER); 127 } 128 129 @Test 130 public void onDisplayAndUpdateState_shouldRefreshUi() { 131 mController = spy(new RecentAppsPreferenceController(mContext, (Application) null, null)); 132 133 doNothing().when(mController).refreshUi(mContext); 134 135 mController.displayPreference(mScreen); 136 mController.updateState(mCategory); 137 138 verify(mController, times(2)).refreshUi(mContext); 139 } 140 141 @Test 142 @Config(qualifiers = "mcc999") 143 public void display_shouldNotShowRecents_showAppInfoPreference() { 144 mController.displayPreference(mScreen); 145 146 verify(mCategory, never()).addPreference(any(Preference.class)); 147 verify(mCategory).setTitle(null); 148 verify(mSeeAllPref).setTitle(R.string.applications_settings); 149 verify(mSeeAllPref).setIcon(null); 150 verify(mDivider).setVisible(false); 151 } 152 153 @Test 154 public void display_showRecents() { 155 final List<UsageStats> stats = new ArrayList<>(); 156 final UsageStats stat1 = new UsageStats(); 157 final UsageStats stat2 = new UsageStats(); 158 final UsageStats stat3 = new UsageStats(); 159 stat1.mLastTimeUsed = System.currentTimeMillis(); 160 stat1.mPackageName = "pkg.class"; 161 stats.add(stat1); 162 163 stat2.mLastTimeUsed = System.currentTimeMillis(); 164 stat2.mPackageName = "com.android.settings"; 165 stats.add(stat2); 166 167 stat3.mLastTimeUsed = System.currentTimeMillis(); 168 stat3.mPackageName = "pkg.class2"; 169 stats.add(stat3); 170 171 // stat1, stat2 are valid apps. stat3 is invalid. 172 when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) 173 .thenReturn(mAppEntry); 174 when(mAppState.getEntry(stat2.mPackageName, UserHandle.myUserId())) 175 .thenReturn(mAppEntry); 176 when(mAppState.getEntry(stat3.mPackageName, UserHandle.myUserId())) 177 .thenReturn(null); 178 when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) 179 .thenReturn(new ResolveInfo()); 180 when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) 181 .thenReturn(stats); 182 mAppEntry.info = mApplicationInfo; 183 184 mController.displayPreference(mScreen); 185 186 verify(mCategory).setTitle(R.string.recent_app_category_title); 187 // Only add stat1. stat2 is skipped because of the package name, stat3 skipped because 188 // it's invalid app. 189 verify(mCategory, times(1)).addPreference(any(Preference.class)); 190 191 verify(mSeeAllPref).setSummary(null); 192 verify(mSeeAllPref).setIcon(R.drawable.ic_chevron_right_24dp); 193 verify(mDivider).setVisible(true); 194 } 195 196 @Test 197 public void display_showRecentsWithInstantApp() { 198 // Regular app. 199 final List<UsageStats> stats = new ArrayList<>(); 200 final UsageStats stat1 = new UsageStats(); 201 stat1.mLastTimeUsed = System.currentTimeMillis(); 202 stat1.mPackageName = "com.foo.bar"; 203 stats.add(stat1); 204 205 // Instant app. 206 final UsageStats stat2 = new UsageStats(); 207 stat2.mLastTimeUsed = System.currentTimeMillis() + 200; 208 stat2.mPackageName = "com.foo.barinstant"; 209 stats.add(stat2); 210 211 ApplicationsState.AppEntry stat1Entry = mock(ApplicationsState.AppEntry.class); 212 ApplicationsState.AppEntry stat2Entry = mock(ApplicationsState.AppEntry.class); 213 stat1Entry.info = mApplicationInfo; 214 stat2Entry.info = mApplicationInfo; 215 216 when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())).thenReturn(stat1Entry); 217 when(mAppState.getEntry(stat2.mPackageName, UserHandle.myUserId())).thenReturn(stat2Entry); 218 219 // Only the regular app stat1 should have its intent resolve. 220 when(mPackageManager.resolveActivity(argThat(intentMatcher(stat1.mPackageName)), anyInt())) 221 .thenReturn(new ResolveInfo()); 222 223 when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) 224 .thenReturn(stats); 225 226 // Make sure stat2 is considered an instant app. 227 ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", 228 (InstantAppDataProvider) (ApplicationInfo info) -> info == stat2Entry.info); 229 230 mController.displayPreference(mScreen); 231 232 ArgumentCaptor<Preference> prefCaptor = ArgumentCaptor.forClass(Preference.class); 233 verify(mCategory, times(2)).addPreference(prefCaptor.capture()); 234 List<Preference> prefs = prefCaptor.getAllValues(); 235 assertThat(prefs.get(1).getKey()).isEqualTo(stat1.mPackageName); 236 assertThat(prefs.get(0).getKey()).isEqualTo(stat2.mPackageName); 237 } 238 239 @Test 240 public void display_showRecentsWithNullAppEntryOrInfo() { 241 final List<UsageStats> stats = new ArrayList<>(); 242 final UsageStats stat1 = new UsageStats(); 243 final UsageStats stat2 = new UsageStats(); 244 stat1.mLastTimeUsed = System.currentTimeMillis(); 245 stat1.mPackageName = "pkg.class"; 246 stats.add(stat1); 247 248 stat2.mLastTimeUsed = System.currentTimeMillis(); 249 stat2.mPackageName = "pkg.class2"; 250 stats.add(stat2); 251 252 // app1 has AppEntry with null info, app2 has null AppEntry. 253 mAppEntry.info = null; 254 when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) 255 .thenReturn(mAppEntry); 256 when(mAppState.getEntry(stat2.mPackageName, UserHandle.myUserId())) 257 .thenReturn(null); 258 259 when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) 260 .thenReturn(stats); 261 262 // We should not crash here. 263 mController.displayPreference(mScreen); 264 } 265 266 @Test 267 public void display_hasRecentButNoneDisplayable_showAppInfo() { 268 final List<UsageStats> stats = new ArrayList<>(); 269 final UsageStats stat1 = new UsageStats(); 270 final UsageStats stat2 = new UsageStats(); 271 stat1.mLastTimeUsed = System.currentTimeMillis(); 272 stat1.mPackageName = "com.android.phone"; 273 stats.add(stat1); 274 275 stat2.mLastTimeUsed = System.currentTimeMillis(); 276 stat2.mPackageName = "com.android.settings"; 277 stats.add(stat2); 278 279 // stat1, stat2 are not displayable 280 when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) 281 .thenReturn(mock(ApplicationsState.AppEntry.class)); 282 when(mAppState.getEntry(stat2.mPackageName, UserHandle.myUserId())) 283 .thenReturn(mock(ApplicationsState.AppEntry.class)); 284 when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) 285 .thenReturn(new ResolveInfo()); 286 when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) 287 .thenReturn(stats); 288 289 mController.displayPreference(mScreen); 290 291 verify(mCategory, never()).addPreference(any(Preference.class)); 292 verify(mCategory).setTitle(null); 293 verify(mSeeAllPref).setTitle(R.string.applications_settings); 294 verify(mSeeAllPref).setIcon(null); 295 } 296 297 @Test 298 public void display_showRecents_formatSummary() { 299 final UsageStats stat1 = new UsageStats(); 300 stat1.mLastTimeUsed = System.currentTimeMillis(); 301 stat1.mPackageName = "pkg.class"; 302 final List<UsageStats> stats = new ArrayList<>(); 303 stats.add(stat1); 304 305 when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) 306 .thenReturn(mAppEntry); 307 when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) 308 .thenReturn(new ResolveInfo()); 309 when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) 310 .thenReturn(stats); 311 mAppEntry.info = mApplicationInfo; 312 313 mController.displayPreference(mScreen); 314 315 verify(mCategory).addPreference(argThat(summaryMatches("0 minutes ago"))); 316 } 317 318 private static ArgumentMatcher<Preference> summaryMatches(String expected) { 319 return preference -> TextUtils.equals(expected, preference.getSummary()); 320 } 321 322 // Used for matching an intent with a specific package name. 323 private static ArgumentMatcher<Intent> intentMatcher(String packageName) { 324 return intent -> packageName.equals(intent.getPackage()); 325 } 326 } 327