OSDN Git Service

A11y shortcut settings enhancement
authorPhil Weaver <pweaver@google.com>
Thu, 16 Mar 2017 21:52:31 +0000 (14:52 -0700)
committerPhil Weaver <pweaver@google.com>
Fri, 24 Mar 2017 20:15:13 +0000 (13:15 -0700)
Adding shortcut on/off switch, improving the service picker,
and adding a switch to enable the shortcut on the lock screen.

Also adjusted setting search code to avoid indexing the
accessibility shortcut aside from the main accessibility settings
page.

Bug: 35872328
Bug: 35219988
Test: Ran in a variety of conditions, ran existing settings test.
Also added basic robo test, verified existing robo tests pass.
Change-Id: I4da9bad74caf96d9c8f3640e7db5417b4ee5d602

res/values/strings.xml
res/xml/accessibility_settings.xml
res/xml/accessibility_shortcut_settings.xml [new file with mode: 0644]
src/com/android/settings/accessibility/AccessibilityServiceWarning.java
src/com/android/settings/accessibility/AccessibilitySettings.java
src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java [new file with mode: 0644]
src/com/android/settings/accessibility/ShortcutServicePickerFragment.java [new file with mode: 0644]
src/com/android/settings/applications/defaultapps/DefaultAppInfo.java
src/com/android/settings/applications/defaultapps/DefaultAppPickerFragment.java
src/com/android/settings/search/SearchIndexableResources.java
tests/robotests/src/com/android/settings/accessibility/ShortcutServicePickerFragmentTest.java [new file with mode: 0644]

index 4be267f..e971356 100644 (file)
     <string name="accessibility_screen_magnification_navbar_summary">When magnification is turned on, use the Accessibility button at the bottom of the screen to quickly magnify.\n\n<b>To zoom</b>, tap the Accessibility button, then tap anywhere on the screen.\n<ul><li>Drag 2 or more fingers to scroll</li>\n<li>Pinch 2 or more fingers to adjust zoom</li></ul>\n\n<b>To zoom temporarily</b>, tap the Accessibility button, then touch &amp; hold anywhere on the screen.\n<ul><li>Drag to move around the screen</li>\n<li>Lift finger to zoom out</li></ul>\n\nYou can’t zoom in on the keyboard or navigation bar.</string>
     <!-- Summary text appearing on the accessibility preference screen to enable screen magnification from the nav bar when the feature is enabled, but the accessibility button is not configured correctly for the feature to be used [CHAR LIMIT=none] -->
     <string name="accessibility_screen_magnification_navbar_configuration_warning">The Accessibility button is set to <xliff:g id="service" example="Select to Speak">%1$s</xliff:g>. To use magnification, touch &amp; hold the Accessibility button, then select magnification.</string>
-    <!-- Title for the preference to enable the global geture that turns on accessibility. [CHAR LIMIT=35] -->
+    <!-- Title for the preference to configure the accessibility shortcut. [CHAR LIMIT=35] -->
     <string name="accessibility_global_gesture_preference_title">Accessibility shortcut</string>
+    <!-- Title for the preference to choose the service that is turned on and off by the accessibility shortcut. [CHAR LIMIT=35] -->
+    <string name="accessibility_shortcut_service_title">Shortcut service</string>
+    <!-- Title for the switch preference that controls whether or not the accessibility shortcut works on the lock screen. [CHAR LIMIT=35] -->
+    <string name="accessibility_shortcut_service_on_lock_screen_title">Allow from lock screen</string>
+    <!-- Description of accessibility shortcut. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_shortcut_description">When the shortcut is on, you can press both volume buttons for 3 seconds to start an accessibility feature.</string>
     <!-- Title for the accessibility preference to high contrast text. [CHAR LIMIT=35] -->
     <string name="accessibility_toggle_high_text_contrast_preference_title">High contrast text</string>
     <!-- Title for the accessibility preference to auto update screen magnification. [CHAR LIMIT=35] -->
     <!-- Title for the prompt shown as a placeholder if no accessibility services are installed. [CHAR LIMIT=50] -->
     <string name="accessibility_no_services_installed">No services installed</string>
 
+    <!-- Title for the acccessibility shortcut's summary if no service is selected for use with the shortcut. [CHAR LIMIT=50] -->
+    <string name="accessibility_no_service_selected">No service selected</string>
+
     <!-- Default description for an accessibility service if the latter doesn't provide one. [CHAR LIMIT=NONE] -->
     <string name="accessibility_service_default_description">No description provided.</string>
 
index 065ead1..48e11af 100644 (file)
@@ -18,8 +18,9 @@
         android:title="@string/accessibility_settings"
         android:persistent="true">
 
-    <ListPreference
+    <Preference
             android:key="accessibility_shortcut_preference"
+            android:fragment="com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment"
             android:title="@string/accessibility_global_gesture_preference_title"/>
 
     <PreferenceCategory
diff --git a/res/xml/accessibility_shortcut_settings.xml b/res/xml/accessibility_shortcut_settings.xml
new file mode 100644 (file)
index 0000000..1245050
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  android:title="@string/accessibility_global_gesture_preference_title" >
+
+    <Preference
+            android:key="accessibility_shortcut_service"
+            android:title="@string/accessibility_shortcut_service_title"
+            android:fragment="com.android.settings.accessibility.ShortcutServicePickerFragment"/>
+
+    <SwitchPreference
+            android:key="accessibility_shortcut_on_lock_screen"
+            android:title="@string/accessibility_shortcut_service_on_lock_screen_title"/>
+</PreferenceScreen>
\ No newline at end of file
index 3a84d74..71cafba 100644 (file)
@@ -78,9 +78,16 @@ public class AccessibilityServiceWarning {
         return StorageManager.isNonDefaultBlockEncrypted();
     }
 
-    private static View createEnableDialogContentView(Activity parentActivity,
+    /**
+     * Get a content View for a dialog to confirm that they want to enable a service.
+     *
+     * @param context A valid context
+     * @param info The info about a service
+     * @return A content view suitable for viewing
+     */
+    private static View createEnableDialogContentView(Context context,
             AccessibilityServiceInfo info) {
-        LayoutInflater inflater = (LayoutInflater) parentActivity.getSystemService(
+        LayoutInflater inflater = (LayoutInflater) context.getSystemService(
                 Context.LAYOUT_INFLATER_SERVICE);
 
         View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content,
@@ -89,8 +96,8 @@ public class AccessibilityServiceWarning {
         TextView encryptionWarningView = (TextView) content.findViewById(
                 R.id.encryption_warning);
         if (isFullDiskEncrypted()) {
-            String text = parentActivity.getString(R.string.enable_service_encryption_warning,
-                    info.getResolveInfo().loadLabel(parentActivity.getPackageManager()));
+            String text = context.getString(R.string.enable_service_encryption_warning,
+                    info.getResolveInfo().loadLabel(context.getPackageManager()));
             encryptionWarningView.setText(text);
             encryptionWarningView.setVisibility(View.VISIBLE);
         } else {
@@ -99,8 +106,8 @@ public class AccessibilityServiceWarning {
 
         TextView capabilitiesHeaderView = (TextView) content.findViewById(
                 R.id.capabilities_header);
-        capabilitiesHeaderView.setText(parentActivity.getString(R.string.capabilities_list_title,
-                info.getResolveInfo().loadLabel(parentActivity.getPackageManager())));
+        capabilitiesHeaderView.setText(context.getString(R.string.capabilities_list_title,
+                info.getResolveInfo().loadLabel(context.getPackageManager())));
 
         LinearLayout capabilitiesView = (LinearLayout) content.findViewById(R.id.capabilities);
 
@@ -110,21 +117,21 @@ public class AccessibilityServiceWarning {
 
         ImageView imageView = (ImageView) capabilityView.findViewById(
                 com.android.internal.R.id.perm_icon);
-        imageView.setImageDrawable(parentActivity.getDrawable(
+        imageView.setImageDrawable(context.getDrawable(
                 com.android.internal.R.drawable.ic_text_dot));
 
         TextView labelView = (TextView) capabilityView.findViewById(
                 com.android.internal.R.id.permission_group);
-        labelView.setText(parentActivity.getString(
+        labelView.setText(context.getString(
                 R.string.capability_title_receiveAccessibilityEvents));
 
         TextView descriptionView = (TextView) capabilityView.findViewById(
                 com.android.internal.R.id.permission_list);
         descriptionView.setText(
-                parentActivity.getString(R.string.capability_desc_receiveAccessibilityEvents));
+                context.getString(R.string.capability_desc_receiveAccessibilityEvents));
 
         List<AccessibilityServiceInfo.CapabilityInfo> capabilities =
-                info.getCapabilityInfos(parentActivity);
+                info.getCapabilityInfos(context);
 
         capabilitiesView.addView(capabilityView);
 
@@ -138,16 +145,16 @@ public class AccessibilityServiceWarning {
 
             imageView = (ImageView) capabilityView.findViewById(
                     com.android.internal.R.id.perm_icon);
-            imageView.setImageDrawable(parentActivity.getDrawable(
+            imageView.setImageDrawable(context.getDrawable(
                     com.android.internal.R.drawable.ic_text_dot));
 
             labelView = (TextView) capabilityView.findViewById(
                     com.android.internal.R.id.permission_group);
-            labelView.setText(parentActivity.getString(capability.titleResId));
+            labelView.setText(context.getString(capability.titleResId));
 
             descriptionView = (TextView) capabilityView.findViewById(
                     com.android.internal.R.id.permission_list);
-            descriptionView.setText(parentActivity.getString(capability.descResId));
+            descriptionView.setText(context.getString(capability.descResId));
 
             capabilitiesView.addView(capabilityView);
         }
index 9b2c853..5e791ae 100644 (file)
 package com.android.settings.accessibility;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
-import android.app.Dialog;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
@@ -130,9 +128,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
     // presentation.
     private static final long DELAY_UPDATE_SERVICES_MILLIS = 1000;
 
-    // ID for dialog that confirms shortcut capabilities
-    private static final int DIALOG_ID_ADD_SHORTCUT_WARNING = 1;
-
     private final Map<String, String> mLongPressTimeoutValueToTitleMap = new HashMap<>();
 
     private final Handler mHandler = new Handler();
@@ -205,7 +200,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
     private Preference mDisplayMagnificationPreferenceScreen;
     private Preference mFontSizePreferenceScreen;
     private Preference mAutoclickPreferenceScreen;
-    private ListPreference mAccessibilityShortcutPreference;
+    private Preference mAccessibilityShortcutPreferenceScreen;
     private Preference mDisplayDaltonizerPreferenceScreen;
     private SwitchPreference mToggleInversionPreference;
 
@@ -264,9 +259,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
         } else if (mToggleInversionPreference == preference) {
             handleToggleInversionPreferenceChange((Boolean) newValue);
             return true;
-        } else if (mAccessibilityShortcutPreference == preference) {
-            handleAccessibilityShortcutPreferenceChange((String) newValue);
-            return true;
         }
         return false;
     }
@@ -283,58 +275,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
                 Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, (checked ? 1 : 0));
     }
 
-    private void handleAccessibilityShortcutPreferenceChange(String serviceComponentName) {
-        // When assigning a service to the shortcut the user must explicitly agree to the same
-        // capabilities that are present if the service were being enabled.
-        // No need if clearing the setting or the service is already enabled.
-        if (TextUtils.isEmpty(serviceComponentName)
-                || AccessibilityUtils.getEnabledServicesFromSettings(getActivity())
-                        .contains(ComponentName.unflattenFromString(serviceComponentName))) {
-            Settings.Secure.putString(getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, serviceComponentName);
-            updateAccessibilityShortcut();
-            return;
-        }
-        if (!serviceComponentName.equals(mAccessibilityShortcutPreference.getValue())) {
-            showDialog(DIALOG_ID_ADD_SHORTCUT_WARNING);
-        }
-    }
-
-    @Override
-    public Dialog onCreateDialog(int dialogId) {
-        switch (dialogId) {
-            case DIALOG_ID_ADD_SHORTCUT_WARNING: {
-                DialogInterface.OnClickListener listener =
-                        (DialogInterface dialogInterface, int buttonId) -> {
-                            if (buttonId == DialogInterface.BUTTON_POSITIVE) {
-                                Settings.Secure.putString(getContentResolver(),
-                                        Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
-                                        mAccessibilityShortcutPreference.getValue());
-                            }
-                            updateAccessibilityShortcut();
-                        };
-                AccessibilityServiceInfo info = AccessibilityManager.getInstance(getActivity())
-                        .getInstalledServiceInfoWithComponentName(
-                                ComponentName.unflattenFromString(
-                                        mAccessibilityShortcutPreference.getValue()));
-                if (info == null) {
-                    return null;
-                }
-                return AccessibilityServiceWarning
-                        .createCapabilitiesDialog(getActivity(), info, listener);
-            }
-            default: {
-                throw new IllegalArgumentException();
-            }
-        }
-    }
-
-    @Override
-    public int getDialogMetricsCategory(int dialogId) {
-        // The only dialog is the one that confirms the properties for the accessibility shortcut
-        return MetricsEvent.ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE;
-    }
-
     @Override
     public boolean onPreferenceTreeClick(Preference preference) {
         if (mToggleHighTextContrastPreference == preference) {
@@ -458,9 +398,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
         mDisplayDaltonizerPreferenceScreen = findPreference(DISPLAY_DALTONIZER_PREFERENCE_SCREEN);
 
         // Accessibility shortcut
-        mAccessibilityShortcutPreference =
-                (ListPreference) findPreference(ACCESSIBILITY_SHORTCUT_PREFERENCE);
-        mAccessibilityShortcutPreference.setOnPreferenceChangeListener(this);
+        mAccessibilityShortcutPreferenceScreen = findPreference(ACCESSIBILITY_SHORTCUT_PREFERENCE);
     }
 
     private void updateAllPreferences() {
@@ -651,7 +589,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
 
         updateAutoclickSummary(mAutoclickPreferenceScreen);
 
-        updateAccessibilityShortcut();
+        updateAccessibilityShortcut(mAccessibilityShortcutPreferenceScreen);
     }
 
     private void updateFeatureSummary(String prefKey, Preference pref) {
@@ -700,35 +638,21 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
         mToggleMasterMonoPreference.setChecked(masterMono);
     }
 
-    private void updateAccessibilityShortcut() {
-        String currentShortcutNameString =
-                AccessibilityUtils.getShortcutTargetServiceComponentNameString(getActivity(),
-                        UserHandle.myUserId());
-        final PackageManager pm = getPackageManager();
-        final AccessibilityManager accessibilityManager = getActivity()
-                .getSystemService(AccessibilityManager.class);
-        final List<AccessibilityServiceInfo> installedServices =
-                accessibilityManager.getInstalledAccessibilityServiceList();
-        final int numInstalledServices = installedServices.size();
-
-        CharSequence[] entries = new CharSequence[numInstalledServices + 1];
-        CharSequence[] entryValues = new CharSequence[numInstalledServices + 1];
-        int currentSettingIndex = numInstalledServices;
-        for (int i = 0; i < numInstalledServices; i++) {
-            AccessibilityServiceInfo installedService = installedServices.get(i);
-            entries[i] = installedService.getResolveInfo().loadLabel(pm);
-            entryValues[i] = installedService.getComponentName().flattenToShortString();
-            if (installedService.getId().equals(currentShortcutNameString)) {
-                currentSettingIndex = i;
-            }
+    private void updateAccessibilityShortcut(Preference preference) {
+        if (AccessibilityManager.getInstance(getActivity())
+                .getInstalledAccessibilityServiceList().isEmpty()) {
+            mAccessibilityShortcutPreferenceScreen
+                    .setSummary(getString(R.string.accessibility_no_services_installed));
+            mAccessibilityShortcutPreferenceScreen.setEnabled(false);
+        } else {
+            mAccessibilityShortcutPreferenceScreen.setEnabled(true);
+            boolean shortcutEnabled =
+                    AccessibilityUtils.isShortcutEnabled(getContext(), UserHandle.myUserId());
+            CharSequence summary = shortcutEnabled
+                    ? AccessibilityShortcutPreferenceFragment.getServiceName(getContext())
+                    : getString(R.string.accessibility_feature_state_off);
+            mAccessibilityShortcutPreferenceScreen.setSummary(summary);
         }
-        entries[numInstalledServices] =
-                getString(com.android.internal.R.string.disable_accessibility_shortcut);
-        entryValues[numInstalledServices] = "";
-        mAccessibilityShortcutPreference.setEntryValues(entryValues);
-        mAccessibilityShortcutPreference.setEntries(entries);
-        mAccessibilityShortcutPreference.setSummary(entries[currentSettingIndex]);
-        mAccessibilityShortcutPreference.setValueIndex(currentSettingIndex);
     }
 
     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
diff --git a/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java b/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java
new file mode 100644 (file)
index 0000000..6ed06da
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 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.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.text.TextUtils;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.Switch;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settingslib.accessibility.AccessibilityUtils;
+
+/**
+ * Settings page for accessibility shortcut
+ */
+public class AccessibilityShortcutPreferenceFragment extends ToggleFeaturePreferenceFragment
+        implements Indexable {
+
+    public static final String SHORTCUT_SERVICE_KEY = "accessibility_shortcut_service";
+    public static final String ON_LOCK_SCREEN_KEY = "accessibility_shortcut_on_lock_screen";
+    // ID for dialog that confirms shortcut capabilities
+    private static final int DIALOG_ID_ADD_SHORTCUT_WARNING = 1;
+
+    private Preference mServicePreference;
+    private SwitchPreference mOnLockScreenSwitchPreference;
+    private String mSelectedServiceComponentNameString;
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsEvent.ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        addPreferencesFromResource(R.xml.accessibility_shortcut_settings);
+        mServicePreference = findPreference(SHORTCUT_SERVICE_KEY);
+        mOnLockScreenSwitchPreference = (SwitchPreference) findPreference(ON_LOCK_SCREEN_KEY);
+        mOnLockScreenSwitchPreference.setOnPreferenceChangeListener((Preference p, Object o) -> {
+            Settings.Secure.putInt(getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
+                    ((Boolean) o) ? 1 : 0);
+            return true;
+        });
+        mFooterPreferenceMixin.createFooterPreference()
+                .setTitle(R.string.accessibility_shortcut_description);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        updatePreferences();
+    }
+
+    @Override
+    protected void onInstallSwitchBarToggleSwitch() {
+        super.onInstallSwitchBarToggleSwitch();
+        mSwitchBar.addOnSwitchChangeListener((Switch switchView, boolean enabled) -> {
+            onPreferenceToggled(Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, enabled);
+        });
+    }
+
+    @Override
+    protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
+        Settings.Secure.putInt(getContentResolver(), preferenceKey, enabled ? 1 : 0);
+    }
+
+    private void updatePreferences() {
+        ContentResolver cr = getContentResolver();
+        boolean isEnabled = Settings.Secure
+                .getInt(cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1) == 1;
+        mToggleSwitch.setChecked(isEnabled);
+        CharSequence serviceName = getServiceName(getContext());
+        mServicePreference.setSummary(serviceName);
+        mOnLockScreenSwitchPreference.setChecked(Settings.Secure.getInt(
+                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, 1) == 1);
+        if (TextUtils.equals(serviceName, getString(R.string.accessibility_no_service_selected))) {
+            // If there's no service configured, enabling the shortcut will have no effect
+            // It should already be disabled, but force the switch to off just in case
+            mToggleSwitch.setChecked(false);
+            mToggleSwitch.setEnabled(false);
+            mSwitchBar.setEnabled(false);
+        } else {
+            mToggleSwitch.setEnabled(true);
+            mSwitchBar.setEnabled(true);
+        }
+    }
+
+    /**
+     * Get the user-visible name of the service currently selected for the shortcut.
+     *
+     * @param context The current context
+     * @return The name of the service or a string saying that none is selected.
+     */
+    public static CharSequence getServiceName(Context context) {
+        ComponentName shortcutServiceName = ComponentName.unflattenFromString(
+                AccessibilityUtils.getShortcutTargetServiceComponentNameString(
+                        context, UserHandle.myUserId()));
+        AccessibilityServiceInfo shortcutServiceInfo = AccessibilityManager.getInstance(context)
+                .getInstalledServiceInfoWithComponentName(shortcutServiceName);
+        if (shortcutServiceInfo != null) {
+            return shortcutServiceInfo.getResolveInfo().loadLabel(context.getPackageManager());
+        }
+        return context.getString(R.string.accessibility_no_service_selected);
+    }
+
+    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider() {
+                // This fragment is for details of the shortcut. Only the shortcut itself needs
+                // to be indexed.
+                protected boolean isPageSearchEnabled(Context context) {
+                    return false;
+                }
+            };
+}
diff --git a/src/com/android/settings/accessibility/ShortcutServicePickerFragment.java b/src/com/android/settings/accessibility/ShortcutServicePickerFragment.java
new file mode 100644 (file)
index 0000000..8b6d52a
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 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.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.support.v7.preference.Preference;
+import android.text.TextUtils;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.DialogCreatable;
+import com.android.settings.applications.defaultapps.DefaultAppInfo;
+import com.android.settings.applications.defaultapps.DefaultAppPickerFragment;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.widget.RadioButtonPreference;
+import com.android.settingslib.accessibility.AccessibilityUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Fragment for picking accessibility shortcut service
+ */
+public class ShortcutServicePickerFragment extends DefaultAppPickerFragment {
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsEvent.ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE;
+    }
+
+    @Override
+    protected List<? extends DefaultAppInfo> getCandidates() {
+        final AccessibilityManager accessibilityManager = getContext()
+                .getSystemService(AccessibilityManager.class);
+        final List<AccessibilityServiceInfo> installedServices =
+                accessibilityManager.getInstalledAccessibilityServiceList();
+        final int numInstalledServices = installedServices.size();
+
+        List<DefaultAppInfo> candidates = new ArrayList<>(numInstalledServices);
+        for (int i = 0; i < numInstalledServices; i++) {
+            AccessibilityServiceInfo installedServiceInfo = installedServices.get(i);
+            candidates.add(new DefaultAppInfo(mPm,
+                    UserHandle.myUserId(),
+                    installedServiceInfo.getComponentName(),
+                    installedServiceInfo.loadSummary(mPm.getPackageManager()),
+                    true /* enabled */));
+        }
+
+        return candidates;
+    }
+
+    @Override
+    protected String getDefaultKey() {
+        String shortcutServiceString = AccessibilityUtils
+                .getShortcutTargetServiceComponentNameString(getContext(), UserHandle.myUserId());
+        if (shortcutServiceString != null) {
+            ComponentName shortcutName = ComponentName.unflattenFromString(shortcutServiceString);
+            if (shortcutName != null) {
+                return shortcutName.flattenToString();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected boolean setDefaultKey(String key) {
+        Settings.Secure.putString(getContext().getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, key);
+        return true;
+    }
+
+    @Override
+    public void onRadioButtonClicked(RadioButtonPreference selected) {
+        final String selectedKey = selected.getKey();
+
+        final Activity activity = getActivity();
+        if (TextUtils.isEmpty(selectedKey)) {
+            super.onRadioButtonClicked(selected);
+        } else if (activity != null) {
+            final DialogFragment fragment = ConfirmationDialogFragment.newInstance(
+                    this, selectedKey);
+            fragment.show(activity.getFragmentManager(), ConfirmationDialogFragment.TAG);
+        }
+    }
+
+    private void onServiceConfirmed(String serviceKey) {
+        onRadioButtonConfirmed(serviceKey);
+    }
+
+    public static class ConfirmationDialogFragment extends InstrumentedDialogFragment
+            implements DialogInterface.OnClickListener {
+        private static final String EXTRA_KEY = "extra_key";
+        private static final String TAG = "ConfirmationDialogFragment";
+
+        public static ConfirmationDialogFragment newInstance(ShortcutServicePickerFragment parent,
+                String key) {
+            final ConfirmationDialogFragment fragment = new ConfirmationDialogFragment();
+            final Bundle argument = new Bundle();
+            argument.putString(EXTRA_KEY, key);
+            fragment.setArguments(argument);
+            fragment.setTargetFragment(parent, 0);
+            return fragment;
+        }
+
+        @Override
+        public int getMetricsCategory() {
+            return MetricsEvent.ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE;
+        }
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            final Bundle bundle = getArguments();
+            final String key = bundle.getString(EXTRA_KEY);
+            final ComponentName serviceComponentName = ComponentName.unflattenFromString(key);
+            final AccessibilityManager accessibilityManager = getActivity()
+                    .getSystemService(AccessibilityManager.class);
+            AccessibilityServiceInfo info = accessibilityManager
+                    .getInstalledServiceInfoWithComponentName(serviceComponentName);
+            return AccessibilityServiceWarning.createCapabilitiesDialog(getActivity(), info, this);
+        }
+
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            final Fragment fragment = getTargetFragment();
+            if (fragment instanceof DefaultAppPickerFragment) {
+                final Bundle bundle = getArguments();
+                ((ShortcutServicePickerFragment) fragment).onServiceConfirmed(
+                        bundle.getString(EXTRA_KEY));
+            }
+        }
+    }
+}
index e99b106..b40943f 100644 (file)
@@ -20,6 +20,7 @@ import android.app.AppGlobals;
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ComponentInfo;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
@@ -76,16 +77,15 @@ public class DefaultAppInfo extends RadioButtonPickerFragment.CandidateInfo {
     public CharSequence loadLabel() {
         if (componentName != null) {
             try {
-                final ActivityInfo actInfo = AppGlobals.getPackageManager().getActivityInfo(
-                        componentName, 0, userId);
-                if (actInfo != null) {
-                    return actInfo.loadLabel(mPm.getPackageManager());
+                final ComponentInfo componentInfo = getComponentInfo();
+                if (componentInfo != null) {
+                    return componentInfo.loadLabel(mPm.getPackageManager());
                 } else {
                     final ApplicationInfo appInfo = mPm.getApplicationInfoAsUser(
                             componentName.getPackageName(), 0, userId);
                     return appInfo.loadLabel(mPm.getPackageManager());
                 }
-            } catch (RemoteException | PackageManager.NameNotFoundException e) {
+            } catch (PackageManager.NameNotFoundException e) {
                 return null;
             }
         } else if (packageItemInfo != null) {
@@ -100,16 +100,15 @@ public class DefaultAppInfo extends RadioButtonPickerFragment.CandidateInfo {
     public Drawable loadIcon() {
         if (componentName != null) {
             try {
-                final ActivityInfo actInfo = AppGlobals.getPackageManager().getActivityInfo(
-                        componentName, 0, userId);
-                if (actInfo != null) {
-                    return actInfo.loadIcon(mPm.getPackageManager());
+                final ComponentInfo componentInfo = getComponentInfo();
+                if (componentInfo != null) {
+                    return componentInfo.loadIcon(mPm.getPackageManager());
                 } else {
                     final ApplicationInfo appInfo = mPm.getApplicationInfoAsUser(
                             componentName.getPackageName(), 0, userId);
                     return appInfo.loadIcon(mPm.getPackageManager());
                 }
-            } catch (RemoteException | PackageManager.NameNotFoundException e) {
+            } catch (PackageManager.NameNotFoundException e) {
                 return null;
             }
         }
@@ -130,4 +129,18 @@ public class DefaultAppInfo extends RadioButtonPickerFragment.CandidateInfo {
             return null;
         }
     }
+
+    private ComponentInfo getComponentInfo() {
+        try {
+            ComponentInfo componentInfo = AppGlobals.getPackageManager().getActivityInfo(
+                    componentName, 0, userId);
+            if (componentInfo == null) {
+                componentInfo = AppGlobals.getPackageManager().getServiceInfo(
+                        componentName, 0, userId);
+            }
+            return componentInfo;
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
 }
index d08ac99..98557ee 100644 (file)
@@ -61,7 +61,6 @@ public abstract class DefaultAppPickerFragment extends RadioButtonPickerFragment
         }
     }
 
-
     @Override
     public void bindPreferenceExtra(RadioButtonPreference pref,
             String key, CandidateInfo info, String defaultKey, String systemDefaultKey) {
index 8b19834..aca6a15 100644 (file)
@@ -30,6 +30,7 @@ import com.android.settings.R;
 import com.android.settings.ScreenPinningSettings;
 import com.android.settings.SecuritySettings;
 import com.android.settings.accessibility.AccessibilitySettings;
+import com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment;
 import com.android.settings.accessibility.MagnificationPreferenceFragment;
 import com.android.settings.accounts.UserAndAccountDashboardFragment;
 import com.android.settings.applications.AdvancedAppSettings;
@@ -176,6 +177,8 @@ public final class SearchIndexableResources {
             R.drawable.ic_settings_security);
         addIndex(MagnificationPreferenceFragment.class, NO_DATA_RES_ID,
                 R.drawable.ic_settings_accessibility);
+        addIndex(AccessibilityShortcutPreferenceFragment.class, NO_DATA_RES_ID,
+                R.drawable.ic_settings_accessibility);
     }
 
     private SearchIndexableResources() {
diff --git a/tests/robotests/src/com/android/settings/accessibility/ShortcutServicePickerFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ShortcutServicePickerFragmentTest.java
new file mode 100644 (file)
index 0000000..9161f06
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 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.app.Activity;
+import android.content.Context;
+import android.os.UserManager;
+
+import android.test.mock.MockContentResolver;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class ShortcutServicePickerFragmentTest {
+
+    private static final String TEST_SERVICE_KEY_1 = "abc/123";
+    private static final String TEST_SERVICE_KEY_2 = "abcd/1234";
+
+    private static final String SUMMARY_1 = "summary1";
+    private static final String SUMMARY_2 = "summary2";
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Activity mActivity;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private PackageManagerWrapper mPackageManager;
+
+    private ShortcutServicePickerFragment mFragment;
+    private MockContentResolver mContentResolver;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mActivity);
+        when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+
+        mFragment = spy(new ShortcutServicePickerFragment());
+        mFragment.onAttach((Context) mActivity);
+
+        doReturn(RuntimeEnvironment.application).when(mFragment).getContext();
+    }
+
+    @Test
+    public void setAndGetDefaultAppKey_shouldUpdateDefaultAppKey() {
+        assertThat(mFragment.setDefaultKey(TEST_SERVICE_KEY_1)).isTrue();
+        assertThat(mFragment.getDefaultKey()).isEqualTo(TEST_SERVICE_KEY_1);
+    }
+}
+