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 18 package com.android.settings.slices; 19 20 import static com.google.common.truth.Truth.assertThat; 21 22 import static org.mockito.ArgumentMatchers.any; 23 import static org.mockito.ArgumentMatchers.eq; 24 import static org.mockito.Mockito.doReturn; 25 import static org.mockito.Mockito.mock; 26 import static org.mockito.Mockito.never; 27 import static org.mockito.Mockito.spy; 28 import static org.mockito.Mockito.verify; 29 30 import android.app.slice.Slice; 31 import android.content.ContentResolver; 32 import android.content.ContentValues; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.database.sqlite.SQLiteDatabase; 36 import android.net.Uri; 37 import android.provider.Settings; 38 import android.provider.SettingsSlicesContract; 39 import android.util.Pair; 40 41 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 42 import com.android.settings.core.BasePreferenceController; 43 import com.android.settings.testutils.FakeIndexProvider; 44 import com.android.settings.search.SearchFeatureProvider; 45 import com.android.settings.search.SearchFeatureProviderImpl; 46 import com.android.settings.testutils.DatabaseTestUtils; 47 import com.android.settings.testutils.FakeFeatureFactory; 48 import com.android.settings.testutils.FakeSliderController; 49 import com.android.settings.testutils.FakeToggleController; 50 import com.android.settings.testutils.SettingsRobolectricTestRunner; 51 52 import org.junit.After; 53 import org.junit.Before; 54 import org.junit.Test; 55 import org.junit.runner.RunWith; 56 import org.mockito.ArgumentCaptor; 57 import org.robolectric.RuntimeEnvironment; 58 59 @RunWith(SettingsRobolectricTestRunner.class) 60 public class SliceBroadcastReceiverTest { 61 62 private final String fakeTitle = "title"; 63 private final String fakeSummary = "summary"; 64 private final String fakeScreenTitle = "screen_title"; 65 private final int fakeIcon = 1234; 66 private final String fakeFragmentClassName = FakeIndexProvider.class.getName(); 67 private final Class fakeControllerName = FakeToggleController.class; 68 69 private Context mContext; 70 private SQLiteDatabase mDb; 71 private SliceBroadcastReceiver mReceiver; 72 private SearchFeatureProvider mSearchFeatureProvider; 73 private FakeFeatureFactory mFakeFeatureFactory; 74 private ArgumentCaptor<Pair<Integer, Object>> mLoggingNameArgumentCatpor; 75 private ArgumentCaptor<Pair<Integer, Object>> mLoggingValueArgumentCatpor; 76 77 @Before 78 public void setUp() { 79 mContext = spy(RuntimeEnvironment.application); 80 mDb = SlicesDatabaseHelper.getInstance(mContext).getWritableDatabase(); 81 mReceiver = new SliceBroadcastReceiver(); 82 SlicesDatabaseHelper helper = SlicesDatabaseHelper.getInstance(mContext); 83 helper.setIndexedState(); 84 mSearchFeatureProvider = new SearchFeatureProviderImpl(); 85 mFakeFeatureFactory = FakeFeatureFactory.setupForTest(); 86 mFakeFeatureFactory.searchFeatureProvider = mSearchFeatureProvider; 87 mLoggingNameArgumentCatpor = ArgumentCaptor.forClass(Pair.class); 88 mLoggingValueArgumentCatpor = ArgumentCaptor.forClass(Pair.class); 89 } 90 91 @After 92 public void cleanUp() { 93 DatabaseTestUtils.clearDb(mContext); 94 } 95 96 @Test 97 public void onReceive_toggleChanged() { 98 final String key = "key"; 99 final Uri uri = new Uri.Builder() 100 .scheme(ContentResolver.SCHEME_CONTENT) 101 .authority(SettingsSliceProvider.SLICE_AUTHORITY) 102 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) 103 .appendPath(key) 104 .build(); 105 mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); 106 insertSpecialCase(key); 107 final ContentResolver resolver = mock(ContentResolver.class); 108 doReturn(resolver).when(mContext).getContentResolver(); 109 // Turn on toggle setting 110 FakeToggleController fakeToggleController = new FakeToggleController(mContext, key); 111 fakeToggleController.setChecked(true); 112 Intent intent = new Intent(SettingsSliceProvider.ACTION_TOGGLE_CHANGED); 113 intent.putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, key); 114 115 assertThat(fakeToggleController.isChecked()).isTrue(); 116 117 // Toggle setting 118 mReceiver.onReceive(mContext, intent); 119 120 assertThat(fakeToggleController.isChecked()).isFalse(); 121 verify(mFakeFeatureFactory.metricsFeatureProvider) 122 .action(eq(mContext), 123 eq(MetricsEvent.ACTION_SETTINGS_SLICE_CHANGED), 124 mLoggingNameArgumentCatpor.capture(), 125 mLoggingValueArgumentCatpor.capture()); 126 127 final Pair<Integer, Object> namePair = mLoggingNameArgumentCatpor.getValue(); 128 final Pair<Integer, Object> valuePair = mLoggingValueArgumentCatpor.getValue(); 129 assertThat(namePair.first).isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME); 130 assertThat(namePair.second).isEqualTo(fakeToggleController.getPreferenceKey()); 131 132 verify(resolver).notifyChange(uri, null); 133 assertThat(valuePair.first) 134 .isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE); 135 assertThat(valuePair.second).isEqualTo(0); 136 } 137 138 @Test 139 public void toggleUpdate_synchronously_notifyChange_should_be_called() { 140 // Monitor the ContentResolver 141 final ContentResolver resolver = spy(mContext.getContentResolver()); 142 doReturn(resolver).when(mContext).getContentResolver(); 143 144 final String key = "key"; 145 mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); 146 insertSpecialCase(key); 147 148 FakeToggleController fakeToggleController = new FakeToggleController(mContext, key); 149 fakeToggleController.setChecked(true); 150 // Set the toggle setting update synchronously. 151 fakeToggleController.setAsyncUpdate(false); 152 Intent intent = new Intent(SettingsSliceProvider.ACTION_TOGGLE_CHANGED); 153 intent.putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, key); 154 155 assertThat(fakeToggleController.isChecked()).isTrue(); 156 157 // Toggle setting 158 mReceiver.onReceive(mContext, intent); 159 160 assertThat(fakeToggleController.isChecked()).isFalse(); 161 162 final Uri expectedUri = SliceBuilderUtils.getUri( 163 SettingsSlicesContract.PATH_SETTING_ACTION + "/" + key, false); 164 verify(resolver).notifyChange(eq(expectedUri), eq(null)); 165 } 166 167 @Test 168 public void toggleUpdate_asynchronously_notifyChange_should_not_be_called() { 169 // Monitor the ContentResolver 170 final ContentResolver resolver = spy(mContext.getContentResolver()); 171 doReturn(resolver).when(mContext).getContentResolver(); 172 173 final String key = "key"; 174 mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); 175 insertSpecialCase(key); 176 177 FakeToggleController fakeToggleController = new FakeToggleController(mContext, key); 178 fakeToggleController.setChecked(true); 179 // Set the toggle setting update asynchronously. 180 fakeToggleController.setAsyncUpdate(true); 181 Intent intent = new Intent(SettingsSliceProvider.ACTION_TOGGLE_CHANGED); 182 intent.putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, key); 183 184 assertThat(fakeToggleController.isChecked()).isTrue(); 185 186 // Toggle setting 187 mReceiver.onReceive(mContext, intent); 188 189 verify(resolver, never()).notifyChange(null, null); 190 } 191 192 @Test 193 public void onReceive_sliderChanged() { 194 final String key = "key"; 195 final Uri uri = new Uri.Builder() 196 .scheme(ContentResolver.SCHEME_CONTENT) 197 .authority(SettingsSliceProvider.SLICE_AUTHORITY) 198 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) 199 .appendPath(key) 200 .build(); 201 final ContentResolver resolver = mock(ContentResolver.class); 202 doReturn(resolver).when(mContext).getContentResolver(); 203 final int position = FakeSliderController.MAX_STEPS - 1; 204 final int oldPosition = FakeSliderController.MAX_STEPS; 205 mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); 206 insertSpecialCase(FakeSliderController.class, key); 207 208 // Set slider setting 209 FakeSliderController fakeSliderController = new FakeSliderController(mContext, key); 210 fakeSliderController.setSliderPosition(oldPosition); 211 // Build action 212 Intent intent = new Intent(SettingsSliceProvider.ACTION_SLIDER_CHANGED); 213 intent.putExtra(Slice.EXTRA_RANGE_VALUE, position); 214 intent.putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, key); 215 216 assertThat(fakeSliderController.getSliderPosition()).isEqualTo(oldPosition); 217 218 // Update the setting. 219 mReceiver.onReceive(mContext, intent); 220 221 assertThat(fakeSliderController.getSliderPosition()).isEqualTo(position); 222 verify(mFakeFeatureFactory.metricsFeatureProvider) 223 .action(eq(mContext), 224 eq(MetricsEvent.ACTION_SETTINGS_SLICE_CHANGED), 225 mLoggingNameArgumentCatpor.capture(), 226 mLoggingValueArgumentCatpor.capture()); 227 228 final Pair<Integer, Object> namePair = mLoggingNameArgumentCatpor.getValue(); 229 final Pair<Integer, Object> valuePair = mLoggingValueArgumentCatpor.getValue(); 230 assertThat(namePair.first).isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME); 231 assertThat(namePair.second).isEqualTo(key); 232 233 verify(resolver).notifyChange(uri, null); 234 assertThat(valuePair.first) 235 .isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE); 236 assertThat(valuePair.second).isEqualTo(position); 237 } 238 239 @Test(expected = IllegalArgumentException.class) 240 public void onReceive_invalidController_throwsException() { 241 final String key = "key"; 242 final int position = 0; 243 mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); 244 insertSpecialCase(FakeToggleController.class, key); 245 246 // Build action 247 Intent intent = new Intent(SettingsSliceProvider.ACTION_SLIDER_CHANGED); 248 intent.putExtra(Slice.EXTRA_RANGE_VALUE, position); 249 intent.putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, key); 250 251 // Trigger the exception. 252 mReceiver.onReceive(mContext, intent); 253 } 254 255 @Test(expected = IllegalArgumentException.class) 256 public void sliderOnReceive_noKey_throwsException() { 257 // Build action 258 final Intent intent = new Intent(SettingsSliceProvider.ACTION_SLIDER_CHANGED) 259 .putExtra(Slice.EXTRA_RANGE_VALUE, 0); 260 261 // Trigger the exception. 262 mReceiver.onReceive(mContext, intent); 263 } 264 265 @Test(expected = IllegalStateException.class) 266 public void toggleOnReceive_noExtra_illegalStateException() { 267 final Intent intent = new Intent(SettingsSliceProvider.ACTION_TOGGLE_CHANGED); 268 mReceiver.onReceive(mContext, intent); 269 } 270 271 @Test(expected = IllegalStateException.class) 272 public void toggleOnReceive_emptyKey_throwsIllegalStateException() { 273 final Intent intent = new Intent(SettingsSliceProvider.ACTION_TOGGLE_CHANGED) 274 .putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, (String) null); 275 mReceiver.onReceive(mContext, intent); 276 } 277 278 @Test 279 public void toggleUpdate_unavailableUriNotified() { 280 // Monitor the ContentResolver 281 final ContentResolver resolver = spy(mContext.getContentResolver()); 282 doReturn(resolver).when(mContext).getContentResolver(); 283 284 // Disable Setting 285 Settings.Global.putInt(mContext.getContentResolver(), 286 FakeToggleController.AVAILABILITY_KEY, 287 BasePreferenceController.UNSUPPORTED_ON_DEVICE); 288 289 // Insert Fake Toggle into Database 290 final String key = "key"; 291 mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); 292 insertSpecialCase(FakeToggleController.class, key); 293 294 // Turn on toggle setting 295 final FakeToggleController fakeToggleController = new FakeToggleController(mContext, key); 296 fakeToggleController.setChecked(true); 297 298 // Build Action 299 final Intent intent = new Intent(SettingsSliceProvider.ACTION_TOGGLE_CHANGED); 300 intent.putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, key); 301 302 // Trigger Slice change 303 mReceiver.onReceive(mContext, intent); 304 305 // Check the value is the same and the Uri has been notified. 306 assertThat(fakeToggleController.isChecked()).isTrue(); 307 final Uri expectedUri = new Uri.Builder() 308 .scheme(ContentResolver.SCHEME_CONTENT) 309 .authority(SettingsSliceProvider.SLICE_AUTHORITY) 310 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) 311 .appendPath(key) 312 .build(); 313 verify(resolver).notifyChange(eq(expectedUri), eq(null)); 314 } 315 316 @Test 317 public void sliderUpdate_unavailableUriNotified() { 318 // Monitor the ContentResolver 319 final ContentResolver resolver = spy(mContext.getContentResolver()); 320 doReturn(resolver).when(mContext).getContentResolver(); 321 322 // Disable Setting 323 Settings.Global.putInt(mContext.getContentResolver(), 324 FakeSliderController.AVAILABILITY_KEY, 325 BasePreferenceController.UNSUPPORTED_ON_DEVICE); 326 327 // Insert Fake Slider into Database 328 final String key = "key"; 329 final int position = FakeSliderController.MAX_STEPS - 1; 330 final int oldPosition = FakeSliderController.MAX_STEPS; 331 mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); 332 insertSpecialCase(FakeSliderController.class, key); 333 334 // Set slider setting 335 final FakeSliderController fakeSliderController = new FakeSliderController(mContext, key); 336 fakeSliderController.setSliderPosition(oldPosition); 337 338 // Build action 339 final Intent intent = new Intent(SettingsSliceProvider.ACTION_SLIDER_CHANGED); 340 intent.putExtra(Slice.EXTRA_RANGE_VALUE, position); 341 intent.putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, key); 342 343 // Trigger Slice change 344 mReceiver.onReceive(mContext, intent); 345 346 // Check position is the same and the Uri has been notified. 347 assertThat(fakeSliderController.getSliderPosition()).isEqualTo(oldPosition); 348 final Uri expectedUri = new Uri.Builder() 349 .scheme(ContentResolver.SCHEME_CONTENT) 350 .authority(SettingsSliceProvider.SLICE_AUTHORITY) 351 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) 352 .appendPath(key) 353 .build(); 354 verify(resolver).notifyChange(eq(expectedUri), eq(null)); 355 } 356 357 private void insertSpecialCase(String key) { 358 insertSpecialCase(fakeControllerName, key); 359 } 360 361 private void insertSpecialCase(Class controllerClass, String key) { 362 ContentValues values = new ContentValues(); 363 values.put(SlicesDatabaseHelper.IndexColumns.KEY, key); 364 values.put(SlicesDatabaseHelper.IndexColumns.TITLE, fakeTitle); 365 values.put(SlicesDatabaseHelper.IndexColumns.SUMMARY, fakeSummary); 366 values.put(SlicesDatabaseHelper.IndexColumns.SCREENTITLE, fakeScreenTitle); 367 values.put(SlicesDatabaseHelper.IndexColumns.ICON_RESOURCE, fakeIcon); 368 values.put(SlicesDatabaseHelper.IndexColumns.FRAGMENT, fakeFragmentClassName); 369 values.put(SlicesDatabaseHelper.IndexColumns.CONTROLLER, controllerClass.getName()); 370 mDb.replaceOrThrow(SlicesDatabaseHelper.Tables.TABLE_SLICES_INDEX, null, values); 371 } 372 }