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