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