A11y shortcut settings enhancement

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
This commit is contained in:
Phil Weaver
2017-03-16 14:52:31 -07:00
parent d3505c76cd
commit 296b7263ad
11 changed files with 482 additions and 119 deletions

View File

@@ -4095,8 +4095,14 @@
<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 cant zoom in on the keyboard or navigation bar.</string> <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 cant 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] --> <!-- 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> <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> <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] --> <!-- 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> <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 accessibility preference to auto update screen magnification. [CHAR LIMIT=35] -->
@@ -4280,6 +4286,9 @@
<!-- Title for the prompt shown as a placeholder if no accessibility services are installed. [CHAR LIMIT=50] --> <!-- 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> <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] --> <!-- 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> <string name="accessibility_service_default_description">No description provided.</string>

View File

@@ -18,8 +18,9 @@
android:title="@string/accessibility_settings" android:title="@string/accessibility_settings"
android:persistent="true"> android:persistent="true">
<ListPreference <Preference
android:key="accessibility_shortcut_preference" android:key="accessibility_shortcut_preference"
android:fragment="com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment"
android:title="@string/accessibility_global_gesture_preference_title"/> android:title="@string/accessibility_global_gesture_preference_title"/>
<PreferenceCategory <PreferenceCategory

View File

@@ -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>

View File

@@ -78,9 +78,16 @@ public class AccessibilityServiceWarning {
return StorageManager.isNonDefaultBlockEncrypted(); 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) { AccessibilityServiceInfo info) {
LayoutInflater inflater = (LayoutInflater) parentActivity.getSystemService( LayoutInflater inflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE); Context.LAYOUT_INFLATER_SERVICE);
View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content, View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content,
@@ -89,8 +96,8 @@ public class AccessibilityServiceWarning {
TextView encryptionWarningView = (TextView) content.findViewById( TextView encryptionWarningView = (TextView) content.findViewById(
R.id.encryption_warning); R.id.encryption_warning);
if (isFullDiskEncrypted()) { if (isFullDiskEncrypted()) {
String text = parentActivity.getString(R.string.enable_service_encryption_warning, String text = context.getString(R.string.enable_service_encryption_warning,
info.getResolveInfo().loadLabel(parentActivity.getPackageManager())); info.getResolveInfo().loadLabel(context.getPackageManager()));
encryptionWarningView.setText(text); encryptionWarningView.setText(text);
encryptionWarningView.setVisibility(View.VISIBLE); encryptionWarningView.setVisibility(View.VISIBLE);
} else { } else {
@@ -99,8 +106,8 @@ public class AccessibilityServiceWarning {
TextView capabilitiesHeaderView = (TextView) content.findViewById( TextView capabilitiesHeaderView = (TextView) content.findViewById(
R.id.capabilities_header); R.id.capabilities_header);
capabilitiesHeaderView.setText(parentActivity.getString(R.string.capabilities_list_title, capabilitiesHeaderView.setText(context.getString(R.string.capabilities_list_title,
info.getResolveInfo().loadLabel(parentActivity.getPackageManager()))); info.getResolveInfo().loadLabel(context.getPackageManager())));
LinearLayout capabilitiesView = (LinearLayout) content.findViewById(R.id.capabilities); LinearLayout capabilitiesView = (LinearLayout) content.findViewById(R.id.capabilities);
@@ -110,21 +117,21 @@ public class AccessibilityServiceWarning {
ImageView imageView = (ImageView) capabilityView.findViewById( ImageView imageView = (ImageView) capabilityView.findViewById(
com.android.internal.R.id.perm_icon); com.android.internal.R.id.perm_icon);
imageView.setImageDrawable(parentActivity.getDrawable( imageView.setImageDrawable(context.getDrawable(
com.android.internal.R.drawable.ic_text_dot)); com.android.internal.R.drawable.ic_text_dot));
TextView labelView = (TextView) capabilityView.findViewById( TextView labelView = (TextView) capabilityView.findViewById(
com.android.internal.R.id.permission_group); com.android.internal.R.id.permission_group);
labelView.setText(parentActivity.getString( labelView.setText(context.getString(
R.string.capability_title_receiveAccessibilityEvents)); R.string.capability_title_receiveAccessibilityEvents));
TextView descriptionView = (TextView) capabilityView.findViewById( TextView descriptionView = (TextView) capabilityView.findViewById(
com.android.internal.R.id.permission_list); com.android.internal.R.id.permission_list);
descriptionView.setText( descriptionView.setText(
parentActivity.getString(R.string.capability_desc_receiveAccessibilityEvents)); context.getString(R.string.capability_desc_receiveAccessibilityEvents));
List<AccessibilityServiceInfo.CapabilityInfo> capabilities = List<AccessibilityServiceInfo.CapabilityInfo> capabilities =
info.getCapabilityInfos(parentActivity); info.getCapabilityInfos(context);
capabilitiesView.addView(capabilityView); capabilitiesView.addView(capabilityView);
@@ -138,16 +145,16 @@ public class AccessibilityServiceWarning {
imageView = (ImageView) capabilityView.findViewById( imageView = (ImageView) capabilityView.findViewById(
com.android.internal.R.id.perm_icon); com.android.internal.R.id.perm_icon);
imageView.setImageDrawable(parentActivity.getDrawable( imageView.setImageDrawable(context.getDrawable(
com.android.internal.R.drawable.ic_text_dot)); com.android.internal.R.drawable.ic_text_dot));
labelView = (TextView) capabilityView.findViewById( labelView = (TextView) capabilityView.findViewById(
com.android.internal.R.id.permission_group); com.android.internal.R.id.permission_group);
labelView.setText(parentActivity.getString(capability.titleResId)); labelView.setText(context.getString(capability.titleResId));
descriptionView = (TextView) capabilityView.findViewById( descriptionView = (TextView) capabilityView.findViewById(
com.android.internal.R.id.permission_list); com.android.internal.R.id.permission_list);
descriptionView.setText(parentActivity.getString(capability.descResId)); descriptionView.setText(context.getString(capability.descResId));
capabilitiesView.addView(capabilityView); capabilitiesView.addView(capabilityView);
} }

View File

@@ -17,11 +17,9 @@
package com.android.settings.accessibility; package com.android.settings.accessibility;
import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Dialog;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo; import android.content.pm.ServiceInfo;
import android.content.res.Resources; import android.content.res.Resources;
@@ -130,9 +128,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
// presentation. // presentation.
private static final long DELAY_UPDATE_SERVICES_MILLIS = 1000; 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 Map<String, String> mLongPressTimeoutValueToTitleMap = new HashMap<>();
private final Handler mHandler = new Handler(); private final Handler mHandler = new Handler();
@@ -205,7 +200,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
private Preference mDisplayMagnificationPreferenceScreen; private Preference mDisplayMagnificationPreferenceScreen;
private Preference mFontSizePreferenceScreen; private Preference mFontSizePreferenceScreen;
private Preference mAutoclickPreferenceScreen; private Preference mAutoclickPreferenceScreen;
private ListPreference mAccessibilityShortcutPreference; private Preference mAccessibilityShortcutPreferenceScreen;
private Preference mDisplayDaltonizerPreferenceScreen; private Preference mDisplayDaltonizerPreferenceScreen;
private SwitchPreference mToggleInversionPreference; private SwitchPreference mToggleInversionPreference;
@@ -264,9 +259,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
} else if (mToggleInversionPreference == preference) { } else if (mToggleInversionPreference == preference) {
handleToggleInversionPreferenceChange((Boolean) newValue); handleToggleInversionPreferenceChange((Boolean) newValue);
return true; return true;
} else if (mAccessibilityShortcutPreference == preference) {
handleAccessibilityShortcutPreferenceChange((String) newValue);
return true;
} }
return false; return false;
} }
@@ -283,58 +275,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, (checked ? 1 : 0)); 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 @Override
public boolean onPreferenceTreeClick(Preference preference) { public boolean onPreferenceTreeClick(Preference preference) {
if (mToggleHighTextContrastPreference == preference) { if (mToggleHighTextContrastPreference == preference) {
@@ -458,9 +398,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
mDisplayDaltonizerPreferenceScreen = findPreference(DISPLAY_DALTONIZER_PREFERENCE_SCREEN); mDisplayDaltonizerPreferenceScreen = findPreference(DISPLAY_DALTONIZER_PREFERENCE_SCREEN);
// Accessibility shortcut // Accessibility shortcut
mAccessibilityShortcutPreference = mAccessibilityShortcutPreferenceScreen = findPreference(ACCESSIBILITY_SHORTCUT_PREFERENCE);
(ListPreference) findPreference(ACCESSIBILITY_SHORTCUT_PREFERENCE);
mAccessibilityShortcutPreference.setOnPreferenceChangeListener(this);
} }
private void updateAllPreferences() { private void updateAllPreferences() {
@@ -651,7 +589,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
updateAutoclickSummary(mAutoclickPreferenceScreen); updateAutoclickSummary(mAutoclickPreferenceScreen);
updateAccessibilityShortcut(); updateAccessibilityShortcut(mAccessibilityShortcutPreferenceScreen);
} }
private void updateFeatureSummary(String prefKey, Preference pref) { private void updateFeatureSummary(String prefKey, Preference pref) {
@@ -700,35 +638,21 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
mToggleMasterMonoPreference.setChecked(masterMono); mToggleMasterMonoPreference.setChecked(masterMono);
} }
private void updateAccessibilityShortcut() { private void updateAccessibilityShortcut(Preference preference) {
String currentShortcutNameString = if (AccessibilityManager.getInstance(getActivity())
AccessibilityUtils.getShortcutTargetServiceComponentNameString(getActivity(), .getInstalledAccessibilityServiceList().isEmpty()) {
UserHandle.myUserId()); mAccessibilityShortcutPreferenceScreen
final PackageManager pm = getPackageManager(); .setSummary(getString(R.string.accessibility_no_services_installed));
final AccessibilityManager accessibilityManager = getActivity() mAccessibilityShortcutPreferenceScreen.setEnabled(false);
.getSystemService(AccessibilityManager.class); } else {
final List<AccessibilityServiceInfo> installedServices = mAccessibilityShortcutPreferenceScreen.setEnabled(true);
accessibilityManager.getInstalledAccessibilityServiceList(); boolean shortcutEnabled =
final int numInstalledServices = installedServices.size(); AccessibilityUtils.isShortcutEnabled(getContext(), UserHandle.myUserId());
CharSequence summary = shortcutEnabled
CharSequence[] entries = new CharSequence[numInstalledServices + 1]; ? AccessibilityShortcutPreferenceFragment.getServiceName(getContext())
CharSequence[] entryValues = new CharSequence[numInstalledServices + 1]; : getString(R.string.accessibility_feature_state_off);
int currentSettingIndex = numInstalledServices; mAccessibilityShortcutPreferenceScreen.setSummary(summary);
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;
}
} }
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 = public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =

View File

@@ -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;
}
};
}

View File

@@ -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));
}
}
}
}

View File

@@ -20,6 +20,7 @@ import android.app.AppGlobals;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageItemInfo; import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
@@ -76,16 +77,15 @@ public class DefaultAppInfo extends RadioButtonPickerFragment.CandidateInfo {
public CharSequence loadLabel() { public CharSequence loadLabel() {
if (componentName != null) { if (componentName != null) {
try { try {
final ActivityInfo actInfo = AppGlobals.getPackageManager().getActivityInfo( final ComponentInfo componentInfo = getComponentInfo();
componentName, 0, userId); if (componentInfo != null) {
if (actInfo != null) { return componentInfo.loadLabel(mPm.getPackageManager());
return actInfo.loadLabel(mPm.getPackageManager());
} else { } else {
final ApplicationInfo appInfo = mPm.getApplicationInfoAsUser( final ApplicationInfo appInfo = mPm.getApplicationInfoAsUser(
componentName.getPackageName(), 0, userId); componentName.getPackageName(), 0, userId);
return appInfo.loadLabel(mPm.getPackageManager()); return appInfo.loadLabel(mPm.getPackageManager());
} }
} catch (RemoteException | PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
return null; return null;
} }
} else if (packageItemInfo != null) { } else if (packageItemInfo != null) {
@@ -100,16 +100,15 @@ public class DefaultAppInfo extends RadioButtonPickerFragment.CandidateInfo {
public Drawable loadIcon() { public Drawable loadIcon() {
if (componentName != null) { if (componentName != null) {
try { try {
final ActivityInfo actInfo = AppGlobals.getPackageManager().getActivityInfo( final ComponentInfo componentInfo = getComponentInfo();
componentName, 0, userId); if (componentInfo != null) {
if (actInfo != null) { return componentInfo.loadIcon(mPm.getPackageManager());
return actInfo.loadIcon(mPm.getPackageManager());
} else { } else {
final ApplicationInfo appInfo = mPm.getApplicationInfoAsUser( final ApplicationInfo appInfo = mPm.getApplicationInfoAsUser(
componentName.getPackageName(), 0, userId); componentName.getPackageName(), 0, userId);
return appInfo.loadIcon(mPm.getPackageManager()); return appInfo.loadIcon(mPm.getPackageManager());
} }
} catch (RemoteException | PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
return null; return null;
} }
} }
@@ -130,4 +129,18 @@ public class DefaultAppInfo extends RadioButtonPickerFragment.CandidateInfo {
return null; 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;
}
}
} }

View File

@@ -61,7 +61,6 @@ public abstract class DefaultAppPickerFragment extends RadioButtonPickerFragment
} }
} }
@Override @Override
public void bindPreferenceExtra(RadioButtonPreference pref, public void bindPreferenceExtra(RadioButtonPreference pref,
String key, CandidateInfo info, String defaultKey, String systemDefaultKey) { String key, CandidateInfo info, String defaultKey, String systemDefaultKey) {

View File

@@ -30,6 +30,7 @@ import com.android.settings.R;
import com.android.settings.ScreenPinningSettings; import com.android.settings.ScreenPinningSettings;
import com.android.settings.SecuritySettings; import com.android.settings.SecuritySettings;
import com.android.settings.accessibility.AccessibilitySettings; import com.android.settings.accessibility.AccessibilitySettings;
import com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment;
import com.android.settings.accessibility.MagnificationPreferenceFragment; import com.android.settings.accessibility.MagnificationPreferenceFragment;
import com.android.settings.accounts.UserAndAccountDashboardFragment; import com.android.settings.accounts.UserAndAccountDashboardFragment;
import com.android.settings.applications.AdvancedAppSettings; import com.android.settings.applications.AdvancedAppSettings;
@@ -176,6 +177,8 @@ public final class SearchIndexableResources {
R.drawable.ic_settings_security); R.drawable.ic_settings_security);
addIndex(MagnificationPreferenceFragment.class, NO_DATA_RES_ID, addIndex(MagnificationPreferenceFragment.class, NO_DATA_RES_ID,
R.drawable.ic_settings_accessibility); R.drawable.ic_settings_accessibility);
addIndex(AccessibilityShortcutPreferenceFragment.class, NO_DATA_RES_ID,
R.drawable.ic_settings_accessibility);
} }
private SearchIndexableResources() { private SearchIndexableResources() {

View File

@@ -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);
}
}