diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6724ff5355d..ec0852c65e4 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1362,6 +1362,20 @@ android:value="true" /> + + + + + + + + + permittedServices = dpm.getPermittedAccessibilityServices( + UserHandle.myUserId()); + return (permittedServices == null || permittedServices.contains(packageName)); + } + + private AccessibilityServiceInfo getAccessibilityServiceInfo(ComponentName componentName) { + if (componentName == null) { + return null; + } + + final List serviceInfos = AccessibilityManager.getInstance( + getActivity()).getInstalledAccessibilityServiceList(); + final int serviceInfoCount = serviceInfos.size(); + for (int i = 0; i < serviceInfoCount; i++) { + AccessibilityServiceInfo serviceInfo = serviceInfos.get(i); + ResolveInfo resolveInfo = serviceInfo.getResolveInfo(); + if (componentName.getPackageName().equals(resolveInfo.serviceInfo.packageName) + && componentName.getClassName().equals(resolveInfo.serviceInfo.name)) { + return serviceInfo; + } + } + return null; + } + + private Bundle buildArguments(AccessibilityServiceInfo info) { + final ResolveInfo resolveInfo = info.getResolveInfo(); + final String title = resolveInfo.loadLabel(getActivity().getPackageManager()).toString(); + final ServiceInfo serviceInfo = resolveInfo.serviceInfo; + final String packageName = serviceInfo.packageName; + final ComponentName componentName = new ComponentName(packageName, serviceInfo.name); + + final List enabledServiceInfos = AccessibilityManager.getInstance( + getActivity()).getEnabledAccessibilityServiceList( + AccessibilityServiceInfo.FEEDBACK_ALL_MASK); + final Set enabledServices = + AccessibilityUtils.getEnabledServicesFromSettings(getActivity()); + final boolean serviceEnabled = enabledServices.contains(componentName); + String description = info.loadDescription(getActivity().getPackageManager()); + if (TextUtils.isEmpty(description)) { + description = getString(R.string.accessibility_service_default_description); + } + + if (serviceEnabled && AccessibilityUtils.hasServiceCrashed( + packageName, serviceInfo.name, enabledServiceInfos)) { + // Update the summaries for services that have crashed. + description = getString(R.string.accessibility_description_state_stopped); + } + + final Bundle extras = new Bundle(); + extras.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY, + componentName.flattenToString()); + extras.putBoolean(AccessibilitySettings.EXTRA_CHECKED, serviceEnabled); + extras.putString(AccessibilitySettings.EXTRA_TITLE, title); + extras.putParcelable(AccessibilitySettings.EXTRA_RESOLVE_INFO, resolveInfo); + extras.putString(AccessibilitySettings.EXTRA_SUMMARY, description); + + final String settingsClassName = info.getSettingsActivityName(); + if (!TextUtils.isEmpty(settingsClassName)) { + extras.putString(AccessibilitySettings.EXTRA_SETTINGS_TITLE, + getString(R.string.accessibility_menu_item_settings)); + extras.putString(AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME, + new ComponentName(packageName, settingsClassName).flattenToString()); + } + extras.putParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME, componentName); + + return extras; + } + + private void finish() { + Activity activity = getActivity(); + if (activity == null) { + return; + } + activity.finish(); + } +} diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index 5e32e71f583..7abd98d3efe 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -24,6 +24,7 @@ import com.android.settings.Settings; import com.android.settings.TestingSettings; import com.android.settings.TetherSettings; import com.android.settings.TrustedCredentialsSettings; +import com.android.settings.accessibility.AccessibilityDetailsSettingsFragment; import com.android.settings.accessibility.AccessibilitySettings; import com.android.settings.accessibility.AccessibilitySettingsForSetupWizard; import com.android.settings.accessibility.CaptionPropertiesFragment; @@ -178,6 +179,7 @@ public class SettingsGateway { UsageAccessDetails.class.getName(), PrivacySettings.class.getName(), DeviceAdminSettings.class.getName(), + AccessibilityDetailsSettingsFragment.class.getName(), AccessibilitySettings.class.getName(), AccessibilitySettingsForSetupWizard.class.getName(), CaptionPropertiesFragment.class.getName(), diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragmentTest.java new file mode 100644 index 00000000000..9d5a5b89a75 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragmentTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2019 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 org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Bundle; +import android.view.accessibility.AccessibilityManager; + +import androidx.fragment.app.FragmentActivity; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.ShadowAccessibilityManager; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +public class AccessibilityDetailsSettingsFragmentTest { + private final static String PACKAGE_NAME = "com.foo.bar"; + private final static String CLASS_NAME = PACKAGE_NAME + ".fake_a11y_service"; + private final static String COMPONENT_NAME = PACKAGE_NAME + "/" + CLASS_NAME; + + private Context mContext; + private AccessibilityDetailsSettingsFragment mFragment; + private ShadowAccessibilityManager mShadowAccessibilityManager; + @Mock private FragmentActivity mActivity; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + mFragment = spy(new AccessibilityDetailsSettingsFragment()); + mShadowAccessibilityManager = Shadow.extract(AccessibilityManager.getInstance(mContext)); + mShadowAccessibilityManager.setInstalledAccessibilityServiceList(getMockServiceList()); + + doReturn(mActivity).when(mFragment).getActivity(); + doReturn(mContext).when(mFragment).getContext(); + } + + @Test + public void onCreate_hasValidExtraComponentName_shouldOpenAccessibilityDetailsSettings() { + final Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_COMPONENT_NAME, COMPONENT_NAME); + doReturn(intent).when(mActivity).getIntent(); + + mFragment.onCreate(Bundle.EMPTY); + + verify(mFragment).openAccessibilityDetailsSettingsAndFinish(any()); + } + + @Test + public void onCreate_hasInvalidExtraComponentName_shouldOpenAccessibilityServicesList() { + final Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_COMPONENT_NAME, PACKAGE_NAME + "/.service"); + doReturn(intent).when(mActivity).getIntent(); + + mFragment.onCreate(Bundle.EMPTY); + + verify(mFragment).openAccessibilitySettingsAndFinish(); + } + + @Test + public void onCreate_hasNoExtraComponentName_shouldOpenAccessibilityServicesList() { + final Intent intent = new Intent(); + doReturn(intent).when(mActivity).getIntent(); + + mFragment.onCreate(Bundle.EMPTY); + + verify(mFragment).openAccessibilitySettingsAndFinish(); + } + + @Test + public void onCreate_extraComponentNameIsDisallowed_shouldOpenAccessibilityServicesList() { + final Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_COMPONENT_NAME, COMPONENT_NAME); + doReturn(intent).when(mActivity).getIntent(); + doReturn(false).when(mFragment).isServiceAllowed(any()); + + mFragment.onCreate(Bundle.EMPTY); + + verify(mFragment).openAccessibilitySettingsAndFinish(); + } + + private AccessibilityServiceInfo getMockAccessibilityServiceInfo() { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + final ServiceInfo serviceInfo = new ServiceInfo(); + applicationInfo.packageName = PACKAGE_NAME; + serviceInfo.packageName = PACKAGE_NAME; + serviceInfo.name = CLASS_NAME; + serviceInfo.applicationInfo = applicationInfo; + + final ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.serviceInfo = serviceInfo; + + try { + final AccessibilityServiceInfo info = new AccessibilityServiceInfo(resolveInfo, + mContext); + ComponentName componentName = ComponentName.unflattenFromString(COMPONENT_NAME); + info.setComponentName(componentName); + return info; + } catch (XmlPullParserException | IOException e) { + // Do nothing + } + + return null; + } + + private List getMockServiceList() { + final List infoList = new ArrayList<>(); + infoList.add(getMockAccessibilityServiceInfo()); + return infoList; + } +}