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