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