From 02b373522a0bd8fe64bbafdb8a3a6459612ed0d6 Mon Sep 17 00:00:00 2001 From: jasonwshsu Date: Tue, 29 Mar 2022 21:43:33 +0800 Subject: [PATCH] Add mechanism to add accessibility service or activity into bluetooth 'Related' category Bug: 225117933 Test: make RunSettingsRoboTests ROBOTEST_FILTER=BluetoothDetailsRelatedToolsControllerTest Change-Id: Iaa3593b4fd9d916fe7f96b3e4bb0965fdbbe36ec --- ...uetoothDetailsCompanionAppsController.java | 2 +- ...luetoothDetailsRelatedToolsController.java | 60 +++++++++- .../BluetoothDeviceDetailsFragment.java | 4 +- .../bluetooth/BluetoothFeatureProvider.java | 17 ++- .../BluetoothFeatureProviderImpl.java | 14 ++- .../settings/overlay/FeatureFactory.java | 5 +- .../settings/overlay/FeatureFactoryImpl.java | 5 +- ...oothDetailsRelatedToolsControllerTest.java | 109 ++++++++++++++++++ .../testutils/FakeFeatureFactory.java | 2 +- .../testutils/FakeFeatureFactory.java | 2 +- 10 files changed, 199 insertions(+), 21 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsCompanionAppsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsCompanionAppsController.java index 272d142e3e8..e3f0f093505 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsCompanionAppsController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsCompanionAppsController.java @@ -186,7 +186,7 @@ public class BluetoothDetailsCompanionAppsController extends BluetoothDetailsCon String address, PreferenceCategory container) { // If the device is FastPair, remove CDM companion apps. final BluetoothFeatureProvider bluetoothFeatureProvider = FeatureFactory.getFactory(context) - .getBluetoothFeatureProvider(context); + .getBluetoothFeatureProvider(); final boolean sliceEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, SettingsUIDeviceConfig.BT_SLICE_SETTINGS_ENABLED, true); final Uri settingsUri = bluetoothFeatureProvider.getBluetoothDeviceSettingsUri( diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsRelatedToolsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsRelatedToolsController.java index 71d60490f25..bfaea85d20f 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsRelatedToolsController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsRelatedToolsController.java @@ -17,22 +17,40 @@ package com.android.settings.bluetooth; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.AccessibilityShortcutInfo; +import android.content.ComponentName; import android.content.Context; +import android.os.UserHandle; +import android.view.accessibility.AccessibilityManager; +import androidx.annotation.NonNull; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceScreen; +import com.android.net.module.util.CollectionUtils; +import com.android.settings.accessibility.RestrictedPreferenceHelper; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.RestrictedPreference; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.core.lifecycle.Lifecycle; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + /** * This class adds related tools preference. */ public class BluetoothDetailsRelatedToolsController extends BluetoothDetailsController{ private static final String KEY_RELATED_TOOLS_GROUP = "bluetooth_related_tools"; private static final String KEY_LIVE_CAPTION = "live_caption"; + private static final int ORDINAL = 99; + + private PreferenceCategory mPreferenceCategory; public BluetoothDetailsRelatedToolsController(Context context, PreferenceFragmentCompat fragment, CachedBluetoothDevice device, Lifecycle lifecycle) { @@ -51,14 +69,20 @@ public class BluetoothDetailsRelatedToolsController extends BluetoothDetailsCont return; } - final PreferenceCategory preferenceCategory = screen.findPreference(getPreferenceKey()); + mPreferenceCategory = screen.findPreference(getPreferenceKey()); final Preference liveCaptionPreference = screen.findPreference(KEY_LIVE_CAPTION); if (!liveCaptionPreference.isVisible()) { - preferenceCategory.removePreference(liveCaptionPreference); + mPreferenceCategory.removePreference(liveCaptionPreference); } - if (preferenceCategory.getPreferenceCount() == 0) { - screen.removePreference(preferenceCategory); + final List relatedToolsList = FeatureFactory.getFactory( + mContext).getBluetoothFeatureProvider().getRelatedTools(); + if (!CollectionUtils.isEmpty(relatedToolsList)) { + addAccessibilityInstalledRelatedPreference(relatedToolsList); + } + + if (mPreferenceCategory.getPreferenceCount() == 0) { + screen.removePreference(mPreferenceCategory); } } @@ -69,4 +93,32 @@ public class BluetoothDetailsRelatedToolsController extends BluetoothDetailsCont public String getPreferenceKey() { return KEY_RELATED_TOOLS_GROUP; } + + private void addAccessibilityInstalledRelatedPreference( + @NonNull List componentNameList) { + final AccessibilityManager a11yManager = AccessibilityManager.getInstance(mContext); + final RestrictedPreferenceHelper preferenceHelper = new RestrictedPreferenceHelper( + mContext); + + final List a11yServiceInfoList = + a11yManager.getInstalledAccessibilityServiceList().stream() + .filter(info -> componentNameList.contains(info.getComponentName())) + .collect(Collectors.toList()); + final List a11yShortcutInfoList = + a11yManager.getInstalledAccessibilityShortcutListAsUser(mContext, + UserHandle.myUserId()).stream() + .filter(info -> componentNameList.contains(info.getComponentName())) + .collect(Collectors.toList()); + + final List preferences = Stream.of( + preferenceHelper.createAccessibilityServicePreferenceList(a11yServiceInfoList), + preferenceHelper.createAccessibilityActivityPreferenceList(a11yShortcutInfoList)) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + + for (RestrictedPreference preference : preferences) { + preference.setOrder(ORDINAL); + mPreferenceCategory.addPreference(preference); + } + } } diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java index ca212f3b128..a9ac2868878 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java @@ -129,7 +129,7 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment use(LeAudioBluetoothDetailsHeaderController.class).init(mCachedDevice, mManager); final BluetoothFeatureProvider featureProvider = FeatureFactory.getFactory( - context).getBluetoothFeatureProvider(context); + context).getBluetoothFeatureProvider(); final boolean sliceEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, SettingsUIDeviceConfig.BT_SLICE_SETTINGS_ENABLED, true); @@ -141,7 +141,7 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment private void updateExtraControlUri(int viewWidth) { BluetoothFeatureProvider featureProvider = FeatureFactory.getFactory( - getContext()).getBluetoothFeatureProvider(getContext()); + getContext()).getBluetoothFeatureProvider(); boolean sliceEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, SettingsUIDeviceConfig.BT_SLICE_SETTINGS_ENABLED, true); Uri controlUri = null; diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java index 51ef8e2a649..648ca307d59 100644 --- a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java +++ b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java @@ -17,15 +17,18 @@ package com.android.settings.bluetooth; import android.bluetooth.BluetoothDevice; +import android.content.ComponentName; import android.net.Uri; +import java.util.List; + /** - * Provider for bluetooth related feature + * Provider for bluetooth related features. */ public interface BluetoothFeatureProvider { /** - * Get the {@link Uri} that represents extra settings for a specific bluetooth device + * Gets the {@link Uri} that represents extra settings for a specific bluetooth device * * @param bluetoothDevice bluetooth device * @return {@link Uri} for extra settings @@ -33,10 +36,18 @@ public interface BluetoothFeatureProvider { Uri getBluetoothDeviceSettingsUri(BluetoothDevice bluetoothDevice); /** - * Get the {@link Uri} that represents extra control for a specific bluetooth device + * Gets the {@link Uri} that represents extra control for a specific bluetooth device * * @param bluetoothDevice bluetooth device * @return {@link String} uri string for extra control */ String getBluetoothDeviceControlUri(BluetoothDevice bluetoothDevice); + + /** + * Gets the {@link ComponentName} of services or activities that need to be shown in related + * tools. + * + * @return list of {@link ComponentName} + */ + List getRelatedTools(); } diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java index 04d3ec4cc1f..5ddf0626724 100644 --- a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java +++ b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java @@ -17,21 +17,20 @@ package com.android.settings.bluetooth; import android.bluetooth.BluetoothDevice; +import android.content.ComponentName; import android.content.Context; import android.net.Uri; import com.android.settingslib.bluetooth.BluetoothUtils; +import java.util.List; + /** * Impl of {@link BluetoothFeatureProvider} */ public class BluetoothFeatureProviderImpl implements BluetoothFeatureProvider { - private Context mContext; - - public BluetoothFeatureProviderImpl(Context context) { - mContext = context; - } + public BluetoothFeatureProviderImpl(Context context) {} @Override public Uri getBluetoothDeviceSettingsUri(BluetoothDevice bluetoothDevice) { @@ -44,4 +43,9 @@ public class BluetoothFeatureProviderImpl implements BluetoothFeatureProvider { public String getBluetoothDeviceControlUri(BluetoothDevice bluetoothDevice) { return BluetoothUtils.getControlUriMetaData(bluetoothDevice); } + + @Override + public List getRelatedTools() { + return null; + } } diff --git a/src/com/android/settings/overlay/FeatureFactory.java b/src/com/android/settings/overlay/FeatureFactory.java index 8b78882ce41..aff7197e274 100644 --- a/src/com/android/settings/overlay/FeatureFactory.java +++ b/src/com/android/settings/overlay/FeatureFactory.java @@ -148,7 +148,10 @@ public abstract class FeatureFactory { public abstract ContextualCardFeatureProvider getContextualCardFeatureProvider(Context context); - public abstract BluetoothFeatureProvider getBluetoothFeatureProvider(Context context); + /** + * Retrieves implementation for Bluetooth feature. + */ + public abstract BluetoothFeatureProvider getBluetoothFeatureProvider(); public abstract AwareFeatureProvider getAwareFeatureProvider(); diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.java b/src/com/android/settings/overlay/FeatureFactoryImpl.java index 4d8dad07e6b..89f74de8233 100644 --- a/src/com/android/settings/overlay/FeatureFactoryImpl.java +++ b/src/com/android/settings/overlay/FeatureFactoryImpl.java @@ -280,10 +280,9 @@ public class FeatureFactoryImpl extends FeatureFactory { } @Override - public BluetoothFeatureProvider getBluetoothFeatureProvider(Context context) { + public BluetoothFeatureProvider getBluetoothFeatureProvider() { if (mBluetoothFeatureProvider == null) { - mBluetoothFeatureProvider = new BluetoothFeatureProviderImpl( - context.getApplicationContext()); + mBluetoothFeatureProvider = new BluetoothFeatureProviderImpl(getAppContext()); } return mBluetoothFeatureProvider; } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsRelatedToolsControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsRelatedToolsControllerTest.java index 2035f061323..31d6397fff6 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsRelatedToolsControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsRelatedToolsControllerTest.java @@ -20,20 +20,65 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.ComponentName; +import android.content.pm.ApplicationInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.view.accessibility.AccessibilityManager; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; + +import com.android.settings.testutils.FakeFeatureFactory; + +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.ShadowAccessibilityManager; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.List; /** Tests for {@link BluetoothDetailsRelatedToolsController}. */ @RunWith(RobolectricTestRunner.class) public class BluetoothDetailsRelatedToolsControllerTest extends BluetoothDetailsControllerTestBase { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + private static final String PACKAGE_NAME = "com.android.test"; + private static final String PACKAGE_NAME2 = "com.android.test2"; + private static final String CLASS_NAME = PACKAGE_NAME + ".test_a11y_service"; + private static final String KEY_RELATED_TOOLS_GROUP = "bluetooth_related_tools"; + private static final String KEY_LIVE_CAPTION = "live_caption"; + + private BluetoothDetailsRelatedToolsController mController; + private BluetoothFeatureProvider mFeatureProvider; + private ShadowAccessibilityManager mShadowAccessibilityManager; + private PreferenceCategory mPreferenceCategory; @Override public void setUp() { super.setUp(); + final FakeFeatureFactory fakeFeatureFactory = FakeFeatureFactory.setupForTest(); + mFeatureProvider = fakeFeatureFactory.getBluetoothFeatureProvider(); + mShadowAccessibilityManager = Shadow.extract(AccessibilityManager.getInstance(mContext)); + final Preference preference = new Preference(mContext); + preference.setKey(KEY_LIVE_CAPTION); + mPreferenceCategory = new PreferenceCategory(mContext); + mPreferenceCategory.setKey(KEY_RELATED_TOOLS_GROUP); + mScreen.addPreference(mPreferenceCategory); + mScreen.addPreference(preference); + mController = new BluetoothDetailsRelatedToolsController(mContext, mFragment, mCachedDevice, mLifecycle); + mController.init(mScreen); } @Test @@ -49,4 +94,68 @@ public class BluetoothDetailsRelatedToolsControllerTest extends BluetoothDetails assertThat(mController.isAvailable()).isFalse(); } + + @Test + public void displayPreference_oneRelatedToolsMatchA11yService_showOnePreference() { + when(mCachedDevice.isHearingAidDevice()).thenReturn(true); + mShadowAccessibilityManager.setInstalledAccessibilityServiceList( + List.of(getMockAccessibilityServiceInfo(PACKAGE_NAME, CLASS_NAME))); + when(mFeatureProvider.getRelatedTools()).thenReturn( + List.of(new ComponentName(PACKAGE_NAME, CLASS_NAME))); + + mController.displayPreference(mScreen); + + assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(1); + } + + @Test + public void displayPreference_oneRelatedToolsNotMatchA11yService_showNoPreference() { + when(mCachedDevice.isHearingAidDevice()).thenReturn(true); + mShadowAccessibilityManager.setInstalledAccessibilityServiceList( + List.of(getMockAccessibilityServiceInfo(PACKAGE_NAME, CLASS_NAME))); + when(mFeatureProvider.getRelatedTools()).thenReturn( + List.of(new ComponentName(PACKAGE_NAME2, CLASS_NAME))); + + mController.displayPreference(mScreen); + + assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(0); + } + + @Test + public void displayPreference_noRelatedTools_showNoPreference() { + when(mCachedDevice.isHearingAidDevice()).thenReturn(true); + mShadowAccessibilityManager.setInstalledAccessibilityServiceList( + List.of(getMockAccessibilityServiceInfo(PACKAGE_NAME, CLASS_NAME))); + when(mFeatureProvider.getRelatedTools()).thenReturn(null); + + mController.displayPreference(mScreen); + + assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(0); + } + + private AccessibilityServiceInfo getMockAccessibilityServiceInfo(String packageName, + String className) { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + final ServiceInfo serviceInfo = new ServiceInfo(); + applicationInfo.packageName = packageName; + serviceInfo.packageName = packageName; + serviceInfo.name = className; + serviceInfo.applicationInfo = applicationInfo; + + final ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.serviceInfo = serviceInfo; + + try { + final AccessibilityServiceInfo info = new AccessibilityServiceInfo(resolveInfo, + mContext); + ComponentName componentName = ComponentName.unflattenFromString( + packageName + "/" + className); + info.setComponentName(componentName); + return info; + } catch (XmlPullParserException | IOException e) { + // Do nothing + } + + return null; + } } diff --git a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java index d430232b8f7..518aee9d23a 100644 --- a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java +++ b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java @@ -239,7 +239,7 @@ public class FakeFeatureFactory extends FeatureFactory { } @Override - public BluetoothFeatureProvider getBluetoothFeatureProvider(Context context) { + public BluetoothFeatureProvider getBluetoothFeatureProvider() { return mBluetoothFeatureProvider; } diff --git a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java index e9764cbbab4..717de791c55 100644 --- a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java +++ b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java @@ -225,7 +225,7 @@ public class FakeFeatureFactory extends FeatureFactory { } @Override - public BluetoothFeatureProvider getBluetoothFeatureProvider(Context context) { + public BluetoothFeatureProvider getBluetoothFeatureProvider() { return mBluetoothFeatureProvider; }