diff --git a/res/values/config.xml b/res/values/config.xml index ec611f09694..e3ec74f2636 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -128,4 +128,7 @@ doesn't interact well with scroll view --> true + + + diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java index af95101916e..0268ca48817 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -344,6 +344,21 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements return super.onPreferenceTreeClick(preference); } + public static CharSequence getServiceSummary(Context context, AccessibilityServiceInfo info, + boolean serviceEnabled) { + final String serviceState = serviceEnabled + ? context.getString(R.string.accessibility_summary_state_enabled) + : context.getString(R.string.accessibility_summary_state_disabled); + final CharSequence serviceSummary = info.loadSummary(context.getPackageManager()); + final String stateSummaryCombo = context.getString( + R.string.preference_summary_default_combination, + serviceState, serviceSummary); + + return (TextUtils.isEmpty(serviceSummary)) + ? serviceState + : stateSummaryCombo; + } + private void handleToggleTextContrastPreferenceClick() { Settings.Secure.putInt(getContentResolver(), Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, @@ -545,15 +560,9 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements preference.setSummary(R.string.accessibility_summary_state_stopped); description = getString(R.string.accessibility_description_state_stopped); } else { - final String serviceState = serviceEnabled ? - getString(R.string.accessibility_summary_state_enabled) : - getString(R.string.accessibility_summary_state_disabled); - final CharSequence serviceSummary = info.loadSummary(getPackageManager()); - final String stateSummaryCombo = getString( - R.string.preference_summary_default_combination, - serviceState, serviceSummary); - preference.setSummary((TextUtils.isEmpty(serviceSummary)) ? serviceState - : stateSummaryCombo); + final CharSequence serviceSummary = getServiceSummary(getContext(), info, + serviceEnabled); + preference.setSummary(serviceSummary); } // Disable all accessibility services that are not permitted. diff --git a/src/com/android/settings/accessibility/AccessibilitySlicePreferenceController.java b/src/com/android/settings/accessibility/AccessibilitySlicePreferenceController.java new file mode 100644 index 00000000000..6b9a480ff81 --- /dev/null +++ b/src/com/android/settings/accessibility/AccessibilitySlicePreferenceController.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.provider.Settings; +import android.view.accessibility.AccessibilityManager; + +import com.android.settings.accessibility.AccessibilitySettings; +import com.android.settings.core.TogglePreferenceController; +import com.android.settingslib.accessibility.AccessibilityUtils; + +import java.util.List; +import java.util.Set; + +/** + * PreferenceController for accessibility services to be used by Slices. + * Wraps the common logic which enables accessibility services and checks their availability. + *

+ * Should not be used in a {@link com.android.settings.dashboard.DashboardFragment}. + */ +public class AccessibilitySlicePreferenceController extends TogglePreferenceController { + + private final ComponentName mComponentName; + + private final int ON = 1; + private final int OFF = 0; + + public AccessibilitySlicePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mComponentName = ComponentName.unflattenFromString(getPreferenceKey()); + + if (mComponentName == null) { + throw new IllegalArgumentException( + "Illegal Component Name from: " + preferenceKey); + } + } + + @Override + public CharSequence getSummary() { + final AccessibilityServiceInfo serviceInfo = getAccessibilityServiceInfo(); + return serviceInfo == null + ? "" : AccessibilitySettings.getServiceSummary(mContext, serviceInfo, isChecked()); + } + + @Override + public boolean isChecked() { + final ContentResolver contentResolver = mContext.getContentResolver(); + final boolean accessibilityEnabled = Settings.Secure.getInt(contentResolver, + Settings.Secure.ACCESSIBILITY_ENABLED, OFF) == ON; + + if (!accessibilityEnabled) { + return false; + } + + final Set componentNames = + AccessibilityUtils.getEnabledServicesFromSettings(mContext); + + return componentNames.contains(mComponentName); + } + + @Override + public boolean setChecked(boolean isChecked) { + if (getAccessibilityServiceInfo() == null) { + return false; + } + AccessibilityUtils.setAccessibilityServiceState(mContext, mComponentName, isChecked); + return isChecked == isChecked(); // Verify that it was probably changed. + } + + @Override + public int getAvailabilityStatus() { + // Return unsupported when the service is disabled or not installed. + return getAccessibilityServiceInfo() == null ? DISABLED_UNSUPPORTED : AVAILABLE; + } + + private AccessibilityServiceInfo getAccessibilityServiceInfo() { + final AccessibilityManager accessibilityManager = mContext.getSystemService( + AccessibilityManager.class); + final List serviceList = + accessibilityManager.getInstalledAccessibilityServiceList(); + + for (AccessibilityServiceInfo serviceInfo : serviceList) { + if (mComponentName.equals(serviceInfo.getComponentName())) { + return serviceInfo; + } + } + + return null; + } +} diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java index 4b4b9c2bd90..ab2cb8213ea 100644 --- a/src/com/android/settings/slices/SettingsSliceProvider.java +++ b/src/com/android/settings/slices/SettingsSliceProvider.java @@ -234,7 +234,7 @@ public class SettingsSliceProvider extends SliceProvider { void loadSlice(Uri uri) { long startBuildTime = System.currentTimeMillis(); - SliceData sliceData = mSlicesDatabaseAccessor.getSliceDataFromUri(uri); + final SliceData sliceData = mSlicesDatabaseAccessor.getSliceDataFromUri(uri); mSliceDataCache.put(uri, sliceData); getContext().getContentResolver().notifyChange(uri, null /* content observer */); diff --git a/src/com/android/settings/slices/SliceBuilderUtils.java b/src/com/android/settings/slices/SliceBuilderUtils.java index e6ba4a8e856..6674344d576 100644 --- a/src/com/android/settings/slices/SliceBuilderUtils.java +++ b/src/com/android/settings/slices/SliceBuilderUtils.java @@ -113,13 +113,13 @@ public class SliceBuilderUtils { * - key *

* Examples of valid paths are: - * - intent/wifi - * - intent/bluetooth - * - action/wifi - * - action/accessibility/servicename + * - /intent/wifi + * - /intent/bluetooth + * - /action/wifi + * - /action/accessibility/servicename * * @param uri of the Slice. Follows pattern outlined in {@link SettingsSliceProvider}. - * @return Pair whose first element {@code true} if the path is prepended with "action", and + * @return Pair whose first element {@code true} if the path is prepended with "intent", and * second is a key. */ public static Pair getPathData(Uri uri) { @@ -133,10 +133,10 @@ public class SliceBuilderUtils { return null; } - final boolean isInline = TextUtils.equals(SettingsSlicesContract.PATH_SETTING_ACTION, + final boolean isIntent = TextUtils.equals(SettingsSlicesContract.PATH_SETTING_INTENT, split[1]); - return new Pair<>(isInline, split[2]); + return new Pair<>(isIntent, split[2]); } /** @@ -215,8 +215,8 @@ public class SliceBuilderUtils { static Intent getContentIntent(Context context, SliceData sliceData) { final Uri contentUri = new Uri.Builder().appendPath(sliceData.getKey()).build(); final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(context, - sliceData.getFragmentClassName(), sliceData.getKey(), sliceData.getScreenTitle(), - 0 /* TODO */); + sliceData.getFragmentClassName(), sliceData.getKey(), + sliceData.getScreenTitle().toString(), 0 /* TODO */); intent.setClassName(context.getPackageName(), SubSettings.class.getName()); intent.setData(contentUri); return intent; diff --git a/src/com/android/settings/slices/SliceData.java b/src/com/android/settings/slices/SliceData.java index c02b1135699..2caf6e6be17 100644 --- a/src/com/android/settings/slices/SliceData.java +++ b/src/com/android/settings/slices/SliceData.java @@ -57,7 +57,7 @@ public class SliceData { private final String mSummary; - private final String mScreenTitle; + private final CharSequence mScreenTitle; private final int mIconResource; @@ -84,7 +84,7 @@ public class SliceData { return mSummary; } - public String getScreenTitle() { + public CharSequence getScreenTitle() { return mScreenTitle; } @@ -146,7 +146,7 @@ public class SliceData { private String mSummary; - private String mScreenTitle; + private CharSequence mScreenTitle; private int mIconResource; @@ -175,7 +175,7 @@ public class SliceData { return this; } - public Builder setScreenTitle(String screenTitle) { + public Builder setScreenTitle(CharSequence screenTitle) { mScreenTitle = screenTitle; return this; } diff --git a/src/com/android/settings/slices/SliceDataConverter.java b/src/com/android/settings/slices/SliceDataConverter.java index 7cf1994f9ed..27724bfddf5 100644 --- a/src/com/android/settings/slices/SliceDataConverter.java +++ b/src/com/android/settings/slices/SliceDataConverter.java @@ -23,7 +23,12 @@ import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_PLATFO import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_SUMMARY; import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_TITLE; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.os.Bundle; @@ -32,9 +37,14 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; +import android.view.accessibility.AccessibilityManager; +import com.android.settings.accessibility.AccessibilitySlicePreferenceController; import com.android.settings.core.PreferenceXmlParserUtils; import com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag; +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.R; +import com.android.settings.accessibility.AccessibilitySettings; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.DatabaseIndexingUtils; @@ -46,10 +56,16 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** - * Converts {@link DashboardFragment} to {@link SliceData}. + * Converts all Slice sources into {@link SliceData}. + * This includes: + * - All {@link DashboardFragment DashboardFragments} indexed by settings search + * - Accessibility services */ class SliceDataConverter { @@ -101,6 +117,8 @@ class SliceDataConverter { mSliceData.addAll(providerSliceData); } + final List a11ySliceData = getAccessibilitySliceData(); + mSliceData.addAll(a11ySliceData); return mSliceData; } @@ -208,4 +226,58 @@ class SliceDataConverter { } return xmlSliceData; } + + private List getAccessibilitySliceData() { + final List sliceData = new ArrayList<>(); + + final String accessibilityControllerClassName = + AccessibilitySlicePreferenceController.class.getName(); + final String fragmentClassName = AccessibilitySettings.class.getName(); + final CharSequence screenTitle = mContext.getText(R.string.accessibility_settings); + + final SliceData.Builder sliceDataBuilder = new SliceData.Builder() + .setFragmentName(fragmentClassName) + .setScreenTitle(screenTitle) + .setPreferenceControllerClassName(accessibilityControllerClassName); + + final Set a11yServiceNames = new HashSet<>(); + Collections.addAll(a11yServiceNames, mContext.getResources() + .getStringArray(R.array.config_settings_slices_accessibility_components)); + final List installedServices = getAccessibilityServiceInfoList(); + final PackageManager packageManager = mContext.getPackageManager(); + + for (AccessibilityServiceInfo a11yServiceInfo : installedServices) { + final ResolveInfo resolveInfo = a11yServiceInfo.getResolveInfo(); + final ServiceInfo serviceInfo = resolveInfo.serviceInfo; + final String packageName = serviceInfo.packageName; + final ComponentName componentName = new ComponentName(packageName, serviceInfo.name); + final String flattenedName = componentName.flattenToString(); + + if (!a11yServiceNames.contains(flattenedName)) { + continue; + } + + final String title = resolveInfo.loadLabel(packageManager).toString(); + int iconResource = resolveInfo.getIconResource(); + if (iconResource == 0) { + iconResource = R.mipmap.ic_accessibility_generic; + } + + sliceDataBuilder.setKey(flattenedName) + .setTitle(title) + .setIcon(iconResource) + .setSliceType(SliceData.SliceType.SWITCH); + + sliceData.add(sliceDataBuilder.build()); + } + + return sliceData; + } + + @VisibleForTesting + List getAccessibilityServiceInfoList() { + final AccessibilityManager accessibilityManager = AccessibilityManager.getInstance( + mContext); + return accessibilityManager.getInstalledAccessibilityServiceList(); + } } \ No newline at end of file diff --git a/src/com/android/settings/slices/SlicesDatabaseAccessor.java b/src/com/android/settings/slices/SlicesDatabaseAccessor.java index 0509f9c1f9f..432be36b77c 100644 --- a/src/com/android/settings/slices/SlicesDatabaseAccessor.java +++ b/src/com/android/settings/slices/SlicesDatabaseAccessor.java @@ -81,7 +81,7 @@ public class SlicesDatabaseAccessor { */ public SliceData getSliceDataFromKey(String key) { Cursor cursor = getIndexedSliceData(key); - return buildSliceData(cursor, null /* uri */, false /* isInlineOnly */); + return buildSliceData(cursor, null /* uri */, false /* isIntentOnly */); } /** @@ -144,7 +144,7 @@ public class SlicesDatabaseAccessor { .toString(); } - private SliceData buildSliceData(Cursor cursor, Uri uri, boolean isInlineOnly) { + private SliceData buildSliceData(Cursor cursor, Uri uri, boolean isIntentOnly) { final String key = cursor.getString(cursor.getColumnIndex(IndexColumns.KEY)); final String title = cursor.getString(cursor.getColumnIndex(IndexColumns.TITLE)); final String summary = cursor.getString(cursor.getColumnIndex(IndexColumns.SUMMARY)); @@ -160,7 +160,7 @@ public class SlicesDatabaseAccessor { int sliceType = cursor.getInt( cursor.getColumnIndex(IndexColumns.SLICE_TYPE)); - if (!isInlineOnly) { + if (isIntentOnly) { sliceType = SliceData.SliceType.INTENT; } diff --git a/src/com/android/settings/slices/SlicesIndexer.java b/src/com/android/settings/slices/SlicesIndexer.java index d7de7bc27a9..e2ab40d407d 100644 --- a/src/com/android/settings/slices/SlicesIndexer.java +++ b/src/com/android/settings/slices/SlicesIndexer.java @@ -104,7 +104,7 @@ class SlicesIndexer implements Runnable { values.put(IndexColumns.KEY, dataRow.getKey()); values.put(IndexColumns.TITLE, dataRow.getTitle()); values.put(IndexColumns.SUMMARY, dataRow.getSummary()); - values.put(IndexColumns.SCREENTITLE, dataRow.getScreenTitle()); + values.put(IndexColumns.SCREENTITLE, dataRow.getScreenTitle().toString()); values.put(IndexColumns.ICON_RESOURCE, dataRow.getIconResource()); values.put(IndexColumns.FRAGMENT, dataRow.getFragmentClassName()); values.put(IndexColumns.CONTROLLER, dataRow.getPreferenceController()); diff --git a/tests/robotests/assets/grandfather_slice_controller_not_in_xml b/tests/robotests/assets/grandfather_slice_controller_not_in_xml index 5a0999739df..d2274e65faf 100644 --- a/tests/robotests/assets/grandfather_slice_controller_not_in_xml +++ b/tests/robotests/assets/grandfather_slice_controller_not_in_xml @@ -1,2 +1,4 @@ com.android.settings.testutils.FakeToggleController -com.android.settings.testutils.FakeSliderController \ No newline at end of file +com.android.settings.testutils.FakeSliderController +com.android.settings.core.TogglePreferenceControllerTest$FakeToggle +com.android.settings.accessibility.AccessibilitySlicePreferenceController diff --git a/tests/robotests/res/values-mcc999/config.xml b/tests/robotests/res/values-mcc999/config.xml index 39620d8e0a5..cecc9c59c59 100644 --- a/tests/robotests/res/values-mcc999/config.xml +++ b/tests/robotests/res/values-mcc999/config.xml @@ -62,4 +62,9 @@ false false true + + + + fake_package/fake_service + diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySlicePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySlicePreferenceControllerTest.java new file mode 100644 index 00000000000..fe6d1a3f5fd --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySlicePreferenceControllerTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.settings.accessibility; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.DISABLED_UNSUPPORTED; + +import static com.google.common.truth.Truth.assertThat; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.provider.Settings; +import android.view.accessibility.AccessibilityManager; + +import com.android.settings.accessibility.AccessibilitySlicePreferenceController; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.accessibility.AccessibilityUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.ShadowAccessibilityManager; +import org.xmlpull.v1.XmlPullParserException; + +@RunWith(SettingsRobolectricTestRunner.class) +public class AccessibilitySlicePreferenceControllerTest { + + private final String PACKAGE_NAME = "com.android.settings.fake"; + private final String CLASS_NAME = "com.android.settings.fake.classname"; + private final String SERVICE_NAME = PACKAGE_NAME + "/" + CLASS_NAME; + + private Context mContext; + + private AccessibilitySlicePreferenceController mController; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + + final ContentResolver contentResolver = mContext.getContentResolver(); + Settings.Secure.putInt(contentResolver, Settings.Secure.ACCESSIBILITY_ENABLED, 1 /* on */); + Settings.Secure.putString(contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + SERVICE_NAME); + + // Register the fake a11y Service + ShadowAccessibilityManager shadowAccessibilityManager = Shadow.extract( + RuntimeEnvironment.application.getSystemService(AccessibilityManager.class)); + shadowAccessibilityManager.setInstalledAccessibilityServiceList(getFakeServiceList()); + + mController = new AccessibilitySlicePreferenceController(mContext, SERVICE_NAME); + } + + @Test + public void getAvailability_availableService_returnsAvailable() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void getAvailability_unknownService_returnsUnsupported() { + AccessibilitySlicePreferenceController controller = + new AccessibilitySlicePreferenceController(mContext, "fake_service/name"); + + assertThat(controller.getAvailabilityStatus()).isEqualTo(DISABLED_UNSUPPORTED); + } + + @Test + public void setChecked_availableService_serviceIsEnabled() { + mController.setChecked(true); + + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void setNotChecked_availableService_serviceIsDisabled() { + mController.setChecked(false); + + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void isChecked_serviceEnabled_returnsTrue() { + AccessibilityUtils.setAccessibilityServiceState(mContext, + ComponentName.unflattenFromString(mController.getPreferenceKey()), true); + + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void isChecked_serviceNotEnabled_returnsFalse() { + AccessibilitySlicePreferenceController controller = + new AccessibilitySlicePreferenceController(mContext, "fake_service/name"); + + assertThat(controller.isChecked()).isFalse(); + } + + @Test(expected = IllegalArgumentException.class) + public void illegalServiceName_exceptionThrown() { + new AccessibilitySlicePreferenceController(mContext, "not_split_by_slash"); + } + + private List getFakeServiceList() { + final List infoList = new ArrayList<>(); + + final ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.packageName = PACKAGE_NAME; + serviceInfo.name = CLASS_NAME; + + final ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.serviceInfo = serviceInfo; + + try { + final AccessibilityServiceInfo info = new AccessibilityServiceInfo(resolveInfo, + mContext); + ComponentName componentName = new ComponentName(PACKAGE_NAME, CLASS_NAME); + info.setComponentName(componentName); + infoList.add(info); + } catch (XmlPullParserException | IOException e) { + + } + + return infoList; + } +} diff --git a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java index 8b99c4a312d..96cf1dc0bbc 100644 --- a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java +++ b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java @@ -277,7 +277,7 @@ public class SliceBuilderUtilsTest { final Pair pathPair = SliceBuilderUtils.getPathData(uri); - assertThat(pathPair.first).isFalse(); + assertThat(pathPair.first).isTrue(); assertThat(pathPair.second).isEqualTo(KEY); } @@ -291,7 +291,7 @@ public class SliceBuilderUtilsTest { final Pair pathPair = SliceBuilderUtils.getPathData(uri); - assertThat(pathPair.first).isTrue(); + assertThat(pathPair.first).isFalse(); assertThat(pathPair.second).isEqualTo(KEY); } @@ -318,7 +318,7 @@ public class SliceBuilderUtilsTest { final Pair pathPair = SliceBuilderUtils.getPathData(uri); - assertThat(pathPair.first).isTrue(); + assertThat(pathPair.first).isFalse(); assertThat(pathPair.second).isEqualTo(KEY + "/" + KEY); } diff --git a/tests/robotests/src/com/android/settings/slices/SliceDataConverterTest.java b/tests/robotests/src/com/android/settings/slices/SliceDataConverterTest.java index 36c27548c13..adc7a96e7e1 100644 --- a/tests/robotests/src/com/android/settings/slices/SliceDataConverterTest.java +++ b/tests/robotests/src/com/android/settings/slices/SliceDataConverterTest.java @@ -17,8 +17,22 @@ package com.android.settings.slices; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.text.TextUtils; + +import com.android.settings.R; +import com.android.settings.accessibility.AccessibilitySettings; +import com.android.settings.accessibility.AccessibilitySlicePreferenceController; import com.android.settings.search.FakeIndexProvider; import com.android.settings.search.SearchFeatureProvider; import com.android.settings.search.SearchFeatureProviderImpl; @@ -32,17 +46,29 @@ import org.junit.runner.RunWith; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import java.util.ArrayList; import java.util.List; @RunWith(SettingsRobolectricTestRunner.class) public class SliceDataConverterTest { - private final String fakeKey = "key"; - private final String fakeTitle = "title"; - private final String fakeSummary = "summary"; - private final String fakeScreenTitle = "screen_title"; - private final String fakeFragmentClassName = FakeIndexProvider.class.getName(); - private final String fakeControllerName = FakePreferenceController.class.getName(); + private final String FAKE_KEY = "key"; + private final String FAKE_TITLE = "title"; + private final String FAKE_SUMMARY = "summary"; + private final String FAKE_SCREEN_TITLE = "screen_title"; + private final String FAKE_FRAGMENT_CLASSNAME = FakeIndexProvider.class.getName(); + private final String FAKE_CONTROLLER_NAME = FakePreferenceController.class.getName(); + + private final String ACCESSIBILITY_FRAGMENT = AccessibilitySettings.class.getName(); + private final String A11Y_CONTROLLER_NAME = + AccessibilitySlicePreferenceController.class.getName(); + private final String FAKE_SERVICE_NAME = "fake_service"; + private final String FAKE_ACCESSIBILITY_PACKAGE = "fake_package"; + private final String FAKE_A11Y_SERVICE_NAME = + FAKE_ACCESSIBILITY_PACKAGE + "/" + FAKE_SERVICE_NAME; + private final int FAKE_ICON = 1234; + + private Context mContext; private SliceDataConverter mSliceDataConverter; private SearchFeatureProvider mSearchFeatureProvider; @@ -50,7 +76,8 @@ public class SliceDataConverterTest { @Before public void setUp() { - mSliceDataConverter = new SliceDataConverter(RuntimeEnvironment.application); + mContext = RuntimeEnvironment.application; + mSliceDataConverter = spy(new SliceDataConverter(RuntimeEnvironment.application)); mSearchFeatureProvider = new SearchFeatureProviderImpl(); mFakeFeatureFactory = FakeFeatureFactory.setupForTest(); mFakeFeatureFactory.searchFeatureProvider = mSearchFeatureProvider; @@ -68,20 +95,64 @@ public class SliceDataConverterTest { mSearchFeatureProvider.getSearchIndexableResources().getProviderValues() .add(FakeIndexProvider.class); + doReturn(getFakeService()).when(mSliceDataConverter).getAccessibilityServiceInfoList(); + List sliceDataList = mSliceDataConverter.getSliceData(); - assertThat(sliceDataList).hasSize(1); - SliceData fakeSlice = sliceDataList.get(0); + assertThat(sliceDataList).hasSize(2); + SliceData fakeSlice0 = sliceDataList.get(0); + SliceData fakeSlice1 = sliceDataList.get(1); - assertThat(fakeSlice.getKey()).isEqualTo(fakeKey); - assertThat(fakeSlice.getTitle()).isEqualTo(fakeTitle); - assertThat(fakeSlice.getSummary()).isEqualTo(fakeSummary); - assertThat(fakeSlice.getScreenTitle()).isEqualTo(fakeScreenTitle); + // Should not assume the order of the data list. + if (TextUtils.equals(fakeSlice0.getKey(), FAKE_KEY)) { + assertFakeSlice(fakeSlice0); + assertFakeA11ySlice(fakeSlice1); + } else { + assertFakeSlice(fakeSlice1); + assertFakeA11ySlice(fakeSlice0); + } + } + + private void assertFakeSlice(SliceData fakeSlice) { + assertThat(fakeSlice.getKey()).isEqualTo(FAKE_KEY); + assertThat(fakeSlice.getTitle()).isEqualTo(FAKE_TITLE); + assertThat(fakeSlice.getSummary()).isEqualTo(FAKE_SUMMARY); + assertThat(fakeSlice.getScreenTitle()).isEqualTo(FAKE_SCREEN_TITLE); assertThat(fakeSlice.getIconResource()).isNotNull(); assertThat(fakeSlice.getUri()).isNull(); - assertThat(fakeSlice.getFragmentClassName()).isEqualTo(fakeFragmentClassName); - assertThat(fakeSlice.getPreferenceController()).isEqualTo(fakeControllerName); - assertThat(fakeSlice.getSliceType()).isEqualTo(SliceData.SliceType.SLIDER); // from XML + assertThat(fakeSlice.getFragmentClassName()).isEqualTo(FAKE_FRAGMENT_CLASSNAME); + assertThat(fakeSlice.getPreferenceController()).isEqualTo(FAKE_CONTROLLER_NAME); + assertThat(fakeSlice.getSliceType()).isEqualTo(SliceData.SliceType.SLIDER); assertThat(fakeSlice.isPlatformDefined()).isTrue(); // from XML } + + private void assertFakeA11ySlice(SliceData fakeSlice) { + assertThat(fakeSlice.getKey()).isEqualTo(FAKE_A11Y_SERVICE_NAME); + assertThat(fakeSlice.getTitle()).isEqualTo(FAKE_TITLE); + assertThat(fakeSlice.getSummary()).isNull(); + assertThat(fakeSlice.getScreenTitle()).isEqualTo( + mContext.getString(R.string.accessibility_settings)); + assertThat(fakeSlice.getIconResource()).isEqualTo(FAKE_ICON); + assertThat(fakeSlice.getUri()).isNull(); + assertThat(fakeSlice.getFragmentClassName()).isEqualTo(ACCESSIBILITY_FRAGMENT); + assertThat(fakeSlice.getPreferenceController()).isEqualTo(A11Y_CONTROLLER_NAME); + } + + // This is fragile. Should be replaced by a proper fake Service if possible. + private List getFakeService() { + List serviceInfoList = new ArrayList<>(); + AccessibilityServiceInfo serviceInfo = spy(new AccessibilityServiceInfo()); + + ResolveInfo resolveInfo = spy(new ResolveInfo()); + resolveInfo.serviceInfo = new ServiceInfo(); + resolveInfo.serviceInfo.name = FAKE_SERVICE_NAME; + resolveInfo.serviceInfo.packageName = FAKE_ACCESSIBILITY_PACKAGE; + doReturn(FAKE_TITLE).when(resolveInfo).loadLabel(any(PackageManager.class)); + doReturn(FAKE_ICON).when(resolveInfo).getIconResource(); + + doReturn(resolveInfo).when(serviceInfo).getResolveInfo(); + serviceInfoList.add(serviceInfo); + + return serviceInfoList; + } } \ No newline at end of file