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 setUp()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 cleanUp()92 public void cleanUp() { 93 DatabaseTestUtils.clearDb(mContext); 94 } 95 96 @Test onReceive_toggleChanged()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 toggleUpdate_synchronously_notifyChange_should_be_called()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 = new Uri.Builder() 163 .scheme(ContentResolver.SCHEME_CONTENT) 164 .authority(SettingsSliceProvider.SLICE_AUTHORITY) 165 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) 166 .appendPath(key) 167 .build(); 168 verify(resolver).notifyChange(eq(expectedUri), eq(null)); 169 } 170 171 @Test toggleUpdate_asynchronously_notifyChange_should_not_be_called()172 public void toggleUpdate_asynchronously_notifyChange_should_not_be_called() { 173 // Monitor the ContentResolver 174 final ContentResolver resolver = spy(mContext.getContentResolver()); 175 doReturn(resolver).when(mContext).getContentResolver(); 176 177 final String key = "key"; 178 mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); 179 insertSpecialCase(key); 180 181 FakeToggleController fakeToggleController = new FakeToggleController(mContext, key); 182 fakeToggleController.setChecked(true); 183 // Set the toggle setting update asynchronously. 184 fakeToggleController.setAsyncUpdate(true); 185 Intent intent = new Intent(SettingsSliceProvider.ACTION_TOGGLE_CHANGED); 186 intent.putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, key); 187 188 assertThat(fakeToggleController.isChecked()).isTrue(); 189 190 // Toggle setting 191 mReceiver.onReceive(mContext, intent); 192 193 verify(resolver, never()).notifyChange(null, null); 194 } 195 196 @Test onReceive_sliderChanged()197 public void onReceive_sliderChanged() { 198 final String key = "key"; 199 final Uri uri = new Uri.Builder() 200 .scheme(ContentResolver.SCHEME_CONTENT) 201 .authority(SettingsSliceProvider.SLICE_AUTHORITY) 202 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) 203 .appendPath(key) 204 .build(); 205 final ContentResolver resolver = mock(ContentResolver.class); 206 doReturn(resolver).when(mContext).getContentResolver(); 207 final int position = FakeSliderController.MAX_STEPS - 1; 208 final int oldPosition = FakeSliderController.MAX_STEPS; 209 mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); 210 insertSpecialCase(FakeSliderController.class, key); 211 212 // Set slider setting 213 FakeSliderController fakeSliderController = new FakeSliderController(mContext, key); 214 fakeSliderController.setSliderPosition(oldPosition); 215 // Build action 216 Intent intent = new Intent(SettingsSliceProvider.ACTION_SLIDER_CHANGED); 217 intent.putExtra(Slice.EXTRA_RANGE_VALUE, position); 218 intent.putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, key); 219 220 assertThat(fakeSliderController.getSliderPosition()).isEqualTo(oldPosition); 221 222 // Update the setting. 223 mReceiver.onReceive(mContext, intent); 224 225 assertThat(fakeSliderController.getSliderPosition()).isEqualTo(position); 226 verify(mFakeFeatureFactory.metricsFeatureProvider) 227 .action(eq(mContext), 228 eq(MetricsEvent.ACTION_SETTINGS_SLICE_CHANGED), 229 mLoggingNameArgumentCatpor.capture(), 230 mLoggingValueArgumentCatpor.capture()); 231 232 final Pair<Integer, Object> namePair = mLoggingNameArgumentCatpor.getValue(); 233 final Pair<Integer, Object> valuePair = mLoggingValueArgumentCatpor.getValue(); 234 assertThat(namePair.first).isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME); 235 assertThat(namePair.second).isEqualTo(key); 236 237 verify(resolver).notifyChange(uri, null); 238 assertThat(valuePair.first) 239 .isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE); 240 assertThat(valuePair.second).isEqualTo(position); 241 } 242 243 @Test(expected = IllegalArgumentException.class) onReceive_invalidController_throwsException()244 public void onReceive_invalidController_throwsException() { 245 final String key = "key"; 246 final int position = 0; 247 mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); 248 insertSpecialCase(FakeToggleController.class, key); 249 250 // Build action 251 Intent intent = new Intent(SettingsSliceProvider.ACTION_SLIDER_CHANGED); 252 intent.putExtra(Slice.EXTRA_RANGE_VALUE, position); 253 intent.putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, key); 254 255 // Trigger the exception. 256 mReceiver.onReceive(mContext, intent); 257 } 258 259 @Test(expected = IllegalArgumentException.class) sliderOnReceive_noKey_throwsException()260 public void sliderOnReceive_noKey_throwsException() { 261 // Build action 262 final Intent intent = new Intent(SettingsSliceProvider.ACTION_SLIDER_CHANGED) 263 .putExtra(Slice.EXTRA_RANGE_VALUE, 0); 264 265 // Trigger the exception. 266 mReceiver.onReceive(mContext, intent); 267 } 268 269 @Test(expected = IllegalStateException.class) toggleOnReceive_noExtra_illegalStateException()270 public void toggleOnReceive_noExtra_illegalStateException() { 271 final Intent intent = new Intent(SettingsSliceProvider.ACTION_TOGGLE_CHANGED); 272 mReceiver.onReceive(mContext, intent); 273 } 274 275 @Test(expected = IllegalStateException.class) toggleOnReceive_emptyKey_throwsIllegalStateException()276 public void toggleOnReceive_emptyKey_throwsIllegalStateException() { 277 final Intent intent = new Intent(SettingsSliceProvider.ACTION_TOGGLE_CHANGED) 278 .putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, (String) null); 279 mReceiver.onReceive(mContext, intent); 280 } 281 282 @Test toggleUpdate_unavailableUriNotified()283 public void toggleUpdate_unavailableUriNotified() { 284 // Monitor the ContentResolver 285 final ContentResolver resolver = spy(mContext.getContentResolver()); 286 doReturn(resolver).when(mContext).getContentResolver(); 287 288 // Disable Setting 289 Settings.Global.putInt(mContext.getContentResolver(), 290 FakeToggleController.AVAILABILITY_KEY, 291 BasePreferenceController.UNSUPPORTED_ON_DEVICE); 292 293 // Insert Fake Toggle into Database 294 final String key = "key"; 295 mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); 296 insertSpecialCase(FakeToggleController.class, key); 297 298 // Turn on toggle setting 299 final FakeToggleController fakeToggleController = new FakeToggleController(mContext, key); 300 fakeToggleController.setChecked(true); 301 302 // Build Action 303 final Intent intent = new Intent(SettingsSliceProvider.ACTION_TOGGLE_CHANGED); 304 intent.putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, key); 305 306 // Trigger Slice change 307 mReceiver.onReceive(mContext, intent); 308 309 // Check the value is the same and the Uri has been notified. 310 assertThat(fakeToggleController.isChecked()).isTrue(); 311 final Uri expectedUri = new Uri.Builder() 312 .scheme(ContentResolver.SCHEME_CONTENT) 313 .authority(SettingsSliceProvider.SLICE_AUTHORITY) 314 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) 315 .appendPath(key) 316 .build(); 317 verify(resolver).notifyChange(eq(expectedUri), eq(null)); 318 } 319 320 @Test sliderUpdate_unavailableUriNotified()321 public void sliderUpdate_unavailableUriNotified() { 322 // Monitor the ContentResolver 323 final ContentResolver resolver = spy(mContext.getContentResolver()); 324 doReturn(resolver).when(mContext).getContentResolver(); 325 326 // Disable Setting 327 Settings.Global.putInt(mContext.getContentResolver(), 328 FakeSliderController.AVAILABILITY_KEY, 329 BasePreferenceController.UNSUPPORTED_ON_DEVICE); 330 331 // Insert Fake Slider into Database 332 final String key = "key"; 333 final int position = FakeSliderController.MAX_STEPS - 1; 334 final int oldPosition = FakeSliderController.MAX_STEPS; 335 mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); 336 insertSpecialCase(FakeSliderController.class, key); 337 338 // Set slider setting 339 final FakeSliderController fakeSliderController = new FakeSliderController(mContext, key); 340 fakeSliderController.setSliderPosition(oldPosition); 341 342 // Build action 343 final Intent intent = new Intent(SettingsSliceProvider.ACTION_SLIDER_CHANGED); 344 intent.putExtra(Slice.EXTRA_RANGE_VALUE, position); 345 intent.putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, key); 346 347 // Trigger Slice change 348 mReceiver.onReceive(mContext, intent); 349 350 // Check position is the same and the Uri has been notified. 351 assertThat(fakeSliderController.getSliderPosition()).isEqualTo(oldPosition); 352 final Uri expectedUri = new Uri.Builder() 353 .scheme(ContentResolver.SCHEME_CONTENT) 354 .authority(SettingsSliceProvider.SLICE_AUTHORITY) 355 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) 356 .appendPath(key) 357 .build(); 358 verify(resolver).notifyChange(eq(expectedUri), eq(null)); 359 } 360 insertSpecialCase(String key)361 private void insertSpecialCase(String key) { 362 insertSpecialCase(fakeControllerName, key); 363 } 364 insertSpecialCase(Class controllerClass, String key)365 private void insertSpecialCase(Class controllerClass, String key) { 366 ContentValues values = new ContentValues(); 367 values.put(SlicesDatabaseHelper.IndexColumns.KEY, key); 368 values.put(SlicesDatabaseHelper.IndexColumns.TITLE, fakeTitle); 369 values.put(SlicesDatabaseHelper.IndexColumns.SUMMARY, fakeSummary); 370 values.put(SlicesDatabaseHelper.IndexColumns.SCREENTITLE, fakeScreenTitle); 371 values.put(SlicesDatabaseHelper.IndexColumns.ICON_RESOURCE, fakeIcon); 372 values.put(SlicesDatabaseHelper.IndexColumns.FRAGMENT, fakeFragmentClassName); 373 values.put(SlicesDatabaseHelper.IndexColumns.CONTROLLER, controllerClass.getName()); 374 mDb.replaceOrThrow(SlicesDatabaseHelper.Tables.TABLE_SLICES_INDEX, null, values); 375 } 376 }