2 * Copyright (C) 2018 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 package com.android.settings.slices;
20 import static com.google.common.truth.Truth.assertThat;
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;
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;
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;
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;
59 @RunWith(SettingsRobolectricTestRunner.class)
60 public class SliceBroadcastReceiverTest {
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;
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;
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);
92 public void cleanUp() {
93 DatabaseTestUtils.clearDb(mContext);
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)
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);
115 assertThat(fakeToggleController.isChecked()).isTrue();
118 mReceiver.onReceive(mContext, intent);
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());
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());
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);
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();
144 final String key = "key";
145 mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear();
146 insertSpecialCase(key);
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);
155 assertThat(fakeToggleController.isChecked()).isTrue();
158 mReceiver.onReceive(mContext, intent);
160 assertThat(fakeToggleController.isChecked()).isFalse();
162 final Uri expectedUri = new Uri.Builder()
163 .scheme(ContentResolver.SCHEME_CONTENT)
164 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
165 .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
168 verify(resolver).notifyChange(expectedUri, null);
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();
177 final String key = "key";
178 mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear();
179 insertSpecialCase(key);
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);
188 assertThat(fakeToggleController.isChecked()).isTrue();
191 mReceiver.onReceive(mContext, intent);
193 verify(resolver, never()).notifyChange(null, null);
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)
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);
212 // Set slider setting
213 FakeSliderController fakeSliderController = new FakeSliderController(mContext, key);
214 fakeSliderController.setSliderPosition(oldPosition);
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);
220 assertThat(fakeSliderController.getSliderPosition()).isEqualTo(oldPosition);
222 // Update the setting.
223 mReceiver.onReceive(mContext, intent);
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());
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);
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);
243 @Test(expected = IllegalArgumentException.class)
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);
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);
255 // Trigger the exception.
256 mReceiver.onReceive(mContext, intent);
259 @Test(expected = IllegalArgumentException.class)
260 public void sliderOnReceive_noKey_throwsException() {
262 final Intent intent = new Intent(SettingsSliceProvider.ACTION_SLIDER_CHANGED)
263 .putExtra(Slice.EXTRA_RANGE_VALUE, 0);
265 // Trigger the exception.
266 mReceiver.onReceive(mContext, intent);
269 @Test(expected = IllegalStateException.class)
270 public void toggleOnReceive_noExtra_illegalStateException() {
271 final Intent intent = new Intent(SettingsSliceProvider.ACTION_TOGGLE_CHANGED);
272 mReceiver.onReceive(mContext, intent);
275 @Test(expected = IllegalStateException.class)
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);
283 public void toggleUpdate_unavailableUriNotified() {
284 // Monitor the ContentResolver
285 final ContentResolver resolver = spy(mContext.getContentResolver());
286 doReturn(resolver).when(mContext).getContentResolver();
289 Settings.Global.putInt(mContext.getContentResolver(),
290 FakeToggleController.AVAILABILITY_KEY,
291 BasePreferenceController.UNSUPPORTED_ON_DEVICE);
293 // Insert Fake Toggle into Database
294 final String key = "key";
295 mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear();
296 insertSpecialCase(FakeToggleController.class, key);
298 // Turn on toggle setting
299 final FakeToggleController fakeToggleController = new FakeToggleController(mContext, key);
300 fakeToggleController.setChecked(true);
303 final Intent intent = new Intent(SettingsSliceProvider.ACTION_TOGGLE_CHANGED);
304 intent.putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, key);
306 // Trigger Slice change
307 mReceiver.onReceive(mContext, intent);
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)
317 verify(resolver).notifyChange(eq(expectedUri), eq(null));
321 public void sliderUpdate_unavailableUriNotified() {
322 // Monitor the ContentResolver
323 final ContentResolver resolver = spy(mContext.getContentResolver());
324 doReturn(resolver).when(mContext).getContentResolver();
327 Settings.Global.putInt(mContext.getContentResolver(),
328 FakeSliderController.AVAILABILITY_KEY,
329 BasePreferenceController.UNSUPPORTED_ON_DEVICE);
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);
338 // Set slider setting
339 final FakeSliderController fakeSliderController = new FakeSliderController(mContext, key);
340 fakeSliderController.setSliderPosition(oldPosition);
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);
347 // Trigger Slice change
348 mReceiver.onReceive(mContext, intent);
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)
358 verify(resolver).notifyChange(eq(expectedUri), eq(null));
361 private void insertSpecialCase(String key) {
362 insertSpecialCase(fakeControllerName, 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);