Home | History | Annotate | Download | only in slices
      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 }