Files
app_Settings/src/com/android/settings/AccessibilitySettings.java
Svetoslav 3b2d307b89 Scroll if an accessibility setting description does not fit the screen.
1. Certain accessibility settings are shown on a separate screen with
   a toggle switch and a description. Sometimes the description does not
   fit the screen. The preference framework is using a list view for
   holding the description item. This list view was disabled, thus not
   scrolling (done to avoid drawable state change on click). Now the
   list view is enabled and the drawable state change on click problem
   is solved by setting the selector and divider drawables to a
   transparent one.

2. The layout for the list item that shows the feature description had
   an unnecessary linear layout.

bug:8632146

Change-Id: Ib81a8513158d5b8d90fa80f57720c8590022ae1a
2013-04-26 12:36:00 -07:00

1113 lines
49 KiB
Java

/*
* Copyright (C) 2009 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;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.ActionBar;
import android.app.Activity;
import android.app.ActivityManagerNative;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceScreen;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
import android.view.Gravity;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Switch;
import android.widget.TextView;
import com.android.internal.content.PackageMonitor;
import com.android.internal.view.RotationPolicy;
import com.android.settings.AccessibilitySettings.ToggleSwitch.OnBeforeCheckedChangeListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Activity with the accessibility settings.
*/
public class AccessibilitySettings extends SettingsPreferenceFragment implements DialogCreatable,
Preference.OnPreferenceChangeListener {
private static final String DEFAULT_SCREENREADER_MARKET_LINK =
"market://search?q=pname:com.google.android.marvin.talkback";
private static final float LARGE_FONT_SCALE = 1.3f;
private static final String SYSTEM_PROPERTY_MARKET_URL = "ro.screenreader.market";
// Timeout before we update the services if packages are added/removed since
// the AccessibilityManagerService has to do that processing first to
// generate
// the AccessibilityServiceInfo we need for proper presentation.
private static final long DELAY_UPDATE_SERVICES_MILLIS = 1000;
private static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':';
private static final String KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE =
"key_install_accessibility_service_offered_once";
// Preference categories
private static final String SERVICES_CATEGORY = "services_category";
private static final String SYSTEM_CATEGORY = "system_category";
// Preferences
private static final String TOGGLE_LARGE_TEXT_PREFERENCE =
"toggle_large_text_preference";
private static final String TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE =
"toggle_power_button_ends_call_preference";
private static final String TOGGLE_LOCK_SCREEN_ROTATION_PREFERENCE =
"toggle_lock_screen_rotation_preference";
private static final String TOGGLE_SPEAK_PASSWORD_PREFERENCE =
"toggle_speak_password_preference";
private static final String SELECT_LONG_PRESS_TIMEOUT_PREFERENCE =
"select_long_press_timeout_preference";
private static final String ENABLE_ACCESSIBILITY_GESTURE_PREFERENCE_SCREEN =
"enable_global_gesture_preference_screen";
private static final String DISPLAY_MAGNIFICATION_PREFERENCE_SCREEN =
"screen_magnification_preference_screen";
// Extras passed to sub-fragments.
private static final String EXTRA_PREFERENCE_KEY = "preference_key";
private static final String EXTRA_CHECKED = "checked";
private static final String EXTRA_TITLE = "title";
private static final String EXTRA_SUMMARY = "summary";
private static final String EXTRA_SETTINGS_TITLE = "settings_title";
private static final String EXTRA_COMPONENT_NAME = "component_name";
private static final String EXTRA_SETTINGS_COMPONENT_NAME = "settings_component_name";
// Dialog IDs.
private static final int DIALOG_ID_NO_ACCESSIBILITY_SERVICES = 1;
// Auxiliary members.
private final static SimpleStringSplitter sStringColonSplitter =
new SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
private static final Set<ComponentName> sInstalledServices = new HashSet<ComponentName>();
private final Map<String, String> mLongPressTimeoutValuetoTitleMap =
new HashMap<String, String>();
private final Configuration mCurConfig = new Configuration();
private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor();
private final Handler mHandler = new Handler() {
@Override
public void dispatchMessage(Message msg) {
super.dispatchMessage(msg);
loadInstalledServices();
updateServicesPreferences();
}
};
private final SettingsContentObserver mSettingsContentObserver =
new SettingsContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
loadInstalledServices();
updateServicesPreferences();
}
};
private final RotationPolicy.RotationPolicyListener mRotationPolicyListener =
new RotationPolicy.RotationPolicyListener() {
@Override
public void onChange() {
updateLockScreenRotationCheckbox();
}
};
// Preference controls.
private PreferenceCategory mServicesCategory;
private PreferenceCategory mSystemsCategory;
private CheckBoxPreference mToggleLargeTextPreference;
private CheckBoxPreference mTogglePowerButtonEndsCallPreference;
private CheckBoxPreference mToggleLockScreenRotationPreference;
private CheckBoxPreference mToggleSpeakPasswordPreference;
private ListPreference mSelectLongPressTimeoutPreference;
private Preference mNoServicesMessagePreference;
private PreferenceScreen mDisplayMagnificationPreferenceScreen;
private PreferenceScreen mGlobalGesturePreferenceScreen;
private int mLongPressTimeoutDefault;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.accessibility_settings);
initializeAllPreferences();
}
@Override
public void onResume() {
super.onResume();
loadInstalledServices();
updateAllPreferences();
offerInstallAccessibilitySerivceOnce();
mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
mSettingsContentObserver.register(getContentResolver());
RotationPolicy.registerRotationPolicyListener(getActivity(),
mRotationPolicyListener);
}
@Override
public void onPause() {
mSettingsPackageMonitor.unregister();
RotationPolicy.unregisterRotationPolicyListener(getActivity(),
mRotationPolicyListener);
mSettingsContentObserver.unregister(getContentResolver());
super.onPause();
}
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference == mSelectLongPressTimeoutPreference) {
String stringValue = (String) newValue;
Settings.Secure.putInt(getContentResolver(),
Settings.Secure.LONG_PRESS_TIMEOUT, Integer.parseInt(stringValue));
mSelectLongPressTimeoutPreference.setSummary(
mLongPressTimeoutValuetoTitleMap.get(stringValue));
return true;
}
return false;
}
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
if (mToggleLargeTextPreference == preference) {
handleToggleLargeTextPreferenceClick();
return true;
} else if (mTogglePowerButtonEndsCallPreference == preference) {
handleTogglePowerButtonEndsCallPreferenceClick();
return true;
} else if (mToggleLockScreenRotationPreference == preference) {
handleLockScreenRotationPreferenceClick();
return true;
} else if (mToggleSpeakPasswordPreference == preference) {
handleToggleSpeakPasswordPreferenceClick();
return true;
} else if (mGlobalGesturePreferenceScreen == preference) {
handleTogglEnableAccessibilityGesturePreferenceClick();
return true;
} else if (mDisplayMagnificationPreferenceScreen == preference) {
handleDisplayMagnificationPreferenceScreenClick();
return true;
}
return super.onPreferenceTreeClick(preferenceScreen, preference);
}
private void handleToggleLargeTextPreferenceClick() {
try {
mCurConfig.fontScale = mToggleLargeTextPreference.isChecked() ? LARGE_FONT_SCALE : 1;
ActivityManagerNative.getDefault().updatePersistentConfiguration(mCurConfig);
} catch (RemoteException re) {
/* ignore */
}
}
private void handleTogglePowerButtonEndsCallPreferenceClick() {
Settings.Secure.putInt(getContentResolver(),
Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
(mTogglePowerButtonEndsCallPreference.isChecked()
? Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP
: Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF));
}
private void handleLockScreenRotationPreferenceClick() {
RotationPolicy.setRotationLockForAccessibility(getActivity(),
!mToggleLockScreenRotationPreference.isChecked());
}
private void handleToggleSpeakPasswordPreferenceClick() {
Settings.Secure.putInt(getContentResolver(),
Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD,
mToggleSpeakPasswordPreference.isChecked() ? 1 : 0);
}
private void handleTogglEnableAccessibilityGesturePreferenceClick() {
Bundle extras = mGlobalGesturePreferenceScreen.getExtras();
extras.putString(EXTRA_TITLE, getString(
R.string.accessibility_global_gesture_preference_title));
extras.putString(EXTRA_SUMMARY, getString(
R.string.accessibility_global_gesture_preference_description));
extras.putBoolean(EXTRA_CHECKED, Settings.Global.getInt(getContentResolver(),
Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1);
super.onPreferenceTreeClick(mGlobalGesturePreferenceScreen,
mGlobalGesturePreferenceScreen);
}
private void handleDisplayMagnificationPreferenceScreenClick() {
Bundle extras = mDisplayMagnificationPreferenceScreen.getExtras();
extras.putString(EXTRA_TITLE, getString(
R.string.accessibility_screen_magnification_title));
extras.putCharSequence(EXTRA_SUMMARY, getActivity().getResources().getText(
R.string.accessibility_screen_magnification_summary));
extras.putBoolean(EXTRA_CHECKED, Settings.Secure.getInt(getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0) == 1);
super.onPreferenceTreeClick(mDisplayMagnificationPreferenceScreen,
mDisplayMagnificationPreferenceScreen);
}
private void initializeAllPreferences() {
mServicesCategory = (PreferenceCategory) findPreference(SERVICES_CATEGORY);
mSystemsCategory = (PreferenceCategory) findPreference(SYSTEM_CATEGORY);
// Large text.
mToggleLargeTextPreference =
(CheckBoxPreference) findPreference(TOGGLE_LARGE_TEXT_PREFERENCE);
// Power button ends calls.
mTogglePowerButtonEndsCallPreference =
(CheckBoxPreference) findPreference(TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE);
if (!KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)
|| !Utils.isVoiceCapable(getActivity())) {
mSystemsCategory.removePreference(mTogglePowerButtonEndsCallPreference);
}
// Lock screen rotation.
mToggleLockScreenRotationPreference =
(CheckBoxPreference) findPreference(TOGGLE_LOCK_SCREEN_ROTATION_PREFERENCE);
// Speak passwords.
mToggleSpeakPasswordPreference =
(CheckBoxPreference) findPreference(TOGGLE_SPEAK_PASSWORD_PREFERENCE);
// Long press timeout.
mSelectLongPressTimeoutPreference =
(ListPreference) findPreference(SELECT_LONG_PRESS_TIMEOUT_PREFERENCE);
mSelectLongPressTimeoutPreference.setOnPreferenceChangeListener(this);
if (mLongPressTimeoutValuetoTitleMap.size() == 0) {
String[] timeoutValues = getResources().getStringArray(
R.array.long_press_timeout_selector_values);
mLongPressTimeoutDefault = Integer.parseInt(timeoutValues[0]);
String[] timeoutTitles = getResources().getStringArray(
R.array.long_press_timeout_selector_titles);
final int timeoutValueCount = timeoutValues.length;
for (int i = 0; i < timeoutValueCount; i++) {
mLongPressTimeoutValuetoTitleMap.put(timeoutValues[i], timeoutTitles[i]);
}
}
// Display magnification.
mDisplayMagnificationPreferenceScreen = (PreferenceScreen) findPreference(
DISPLAY_MAGNIFICATION_PREFERENCE_SCREEN);
// Global gesture.
mGlobalGesturePreferenceScreen =
(PreferenceScreen) findPreference(ENABLE_ACCESSIBILITY_GESTURE_PREFERENCE_SCREEN);
}
private void updateAllPreferences() {
updateServicesPreferences();
updateSystemPreferences();
}
private void updateServicesPreferences() {
// Since services category is auto generated we have to do a pass
// to generate it since services can come and go and then based on
// the global accessibility state to decided whether it is enabled.
// Generate.
mServicesCategory.removeAll();
AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(getActivity());
List<AccessibilityServiceInfo> installedServices =
accessibilityManager.getInstalledAccessibilityServiceList();
Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity());
final boolean accessibilityEnabled = Settings.Secure.getInt(getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
for (int i = 0, count = installedServices.size(); i < count; ++i) {
AccessibilityServiceInfo info = installedServices.get(i);
PreferenceScreen preference = getPreferenceManager().createPreferenceScreen(
getActivity());
String title = info.getResolveInfo().loadLabel(getPackageManager()).toString();
ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
ComponentName componentName = new ComponentName(serviceInfo.packageName,
serviceInfo.name);
preference.setKey(componentName.flattenToString());
preference.setTitle(title);
final boolean serviceEnabled = accessibilityEnabled
&& enabledServices.contains(componentName);
if (serviceEnabled) {
preference.setSummary(getString(R.string.accessibility_feature_state_on));
} else {
preference.setSummary(getString(R.string.accessibility_feature_state_off));
}
preference.setOrder(i);
preference.setFragment(ToggleAccessibilityServicePreferenceFragment.class.getName());
preference.setPersistent(true);
Bundle extras = preference.getExtras();
extras.putString(EXTRA_PREFERENCE_KEY, preference.getKey());
extras.putBoolean(EXTRA_CHECKED, serviceEnabled);
extras.putString(EXTRA_TITLE, title);
String description = info.loadDescription(getPackageManager());
if (TextUtils.isEmpty(description)) {
description = getString(R.string.accessibility_service_default_description);
}
extras.putString(EXTRA_SUMMARY, description);
String settingsClassName = info.getSettingsActivityName();
if (!TextUtils.isEmpty(settingsClassName)) {
extras.putString(EXTRA_SETTINGS_TITLE,
getString(R.string.accessibility_menu_item_settings));
extras.putString(EXTRA_SETTINGS_COMPONENT_NAME,
new ComponentName(info.getResolveInfo().serviceInfo.packageName,
settingsClassName).flattenToString());
}
extras.putParcelable(EXTRA_COMPONENT_NAME, componentName);
mServicesCategory.addPreference(preference);
}
if (mServicesCategory.getPreferenceCount() == 0) {
if (mNoServicesMessagePreference == null) {
mNoServicesMessagePreference = new Preference(getActivity()) {
@Override
protected void onBindView(View view) {
super.onBindView(view);
TextView summaryView = (TextView) view.findViewById(R.id.summary);
String title = getString(R.string.accessibility_no_services_installed);
summaryView.setText(title);
}
};
mNoServicesMessagePreference.setPersistent(false);
mNoServicesMessagePreference.setLayoutResource(
R.layout.text_description_preference);
mNoServicesMessagePreference.setSelectable(false);
}
mServicesCategory.addPreference(mNoServicesMessagePreference);
}
}
private void updateSystemPreferences() {
// Large text.
try {
mCurConfig.updateFrom(ActivityManagerNative.getDefault().getConfiguration());
} catch (RemoteException re) {
/* ignore */
}
mToggleLargeTextPreference.setChecked(mCurConfig.fontScale == LARGE_FONT_SCALE);
// Power button ends calls.
if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)
&& Utils.isVoiceCapable(getActivity())) {
final int incallPowerBehavior = Settings.Secure.getInt(getContentResolver(),
Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT);
final boolean powerButtonEndsCall =
(incallPowerBehavior == Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP);
mTogglePowerButtonEndsCallPreference.setChecked(powerButtonEndsCall);
}
// Auto-rotate screen
updateLockScreenRotationCheckbox();
// Speak passwords.
final boolean speakPasswordEnabled = Settings.Secure.getInt(getContentResolver(),
Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0;
mToggleSpeakPasswordPreference.setChecked(speakPasswordEnabled);
// Long press timeout.
final int longPressTimeout = Settings.Secure.getInt(getContentResolver(),
Settings.Secure.LONG_PRESS_TIMEOUT, mLongPressTimeoutDefault);
String value = String.valueOf(longPressTimeout);
mSelectLongPressTimeoutPreference.setValue(value);
mSelectLongPressTimeoutPreference.setSummary(mLongPressTimeoutValuetoTitleMap.get(value));
// Screen magnification.
final boolean magnificationEnabled = Settings.Secure.getInt(getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0) == 1;
if (magnificationEnabled) {
mDisplayMagnificationPreferenceScreen.setSummary(
R.string.accessibility_feature_state_on);
} else {
mDisplayMagnificationPreferenceScreen.setSummary(
R.string.accessibility_feature_state_off);
}
// Global gesture
final boolean globalGestureEnabled = Settings.Global.getInt(getContentResolver(),
Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1;
if (globalGestureEnabled) {
mGlobalGesturePreferenceScreen.setSummary(
R.string.accessibility_global_gesture_preference_summary_on);
} else {
mGlobalGesturePreferenceScreen.setSummary(
R.string.accessibility_global_gesture_preference_summary_off);
}
}
private void updateLockScreenRotationCheckbox() {
Context context = getActivity();
if (context != null) {
mToggleLockScreenRotationPreference.setChecked(
!RotationPolicy.isRotationLocked(context));
}
}
private void offerInstallAccessibilitySerivceOnce() {
// There is always one preference - if no services it is just a message.
if (mServicesCategory.getPreference(0) != mNoServicesMessagePreference) {
return;
}
SharedPreferences preferences = getActivity().getPreferences(Context.MODE_PRIVATE);
final boolean offerInstallService = !preferences.getBoolean(
KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE, false);
if (offerInstallService) {
String screenreaderMarketLink = SystemProperties.get(
SYSTEM_PROPERTY_MARKET_URL,
DEFAULT_SCREENREADER_MARKET_LINK);
Uri marketUri = Uri.parse(screenreaderMarketLink);
Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri);
if (getPackageManager().resolveActivity(marketIntent, 0) == null) {
// Don't show the dialog if no market app is found/installed.
return;
}
preferences.edit().putBoolean(KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE,
true).commit();
// Notify user that they do not have any accessibility
// services installed and direct them to Market to get TalkBack.
showDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
}
}
@Override
public Dialog onCreateDialog(int dialogId) {
switch (dialogId) {
case DIALOG_ID_NO_ACCESSIBILITY_SERVICES:
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.accessibility_service_no_apps_title)
.setMessage(R.string.accessibility_service_no_apps_message)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// dismiss the dialog before launching
// the activity otherwise
// the dialog removal occurs after
// onSaveInstanceState which
// triggers an exception
removeDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
String screenreaderMarketLink = SystemProperties.get(
SYSTEM_PROPERTY_MARKET_URL,
DEFAULT_SCREENREADER_MARKET_LINK);
Uri marketUri = Uri.parse(screenreaderMarketLink);
Intent marketIntent = new Intent(Intent.ACTION_VIEW,
marketUri);
startActivity(marketIntent);
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
default:
return null;
}
}
private void loadInstalledServices() {
List<AccessibilityServiceInfo> installedServiceInfos =
AccessibilityManager.getInstance(getActivity())
.getInstalledAccessibilityServiceList();
Set<ComponentName> installedServices = sInstalledServices;
installedServices.clear();
final int installedServiceInfoCount = installedServiceInfos.size();
for (int i = 0; i < installedServiceInfoCount; i++) {
ResolveInfo resolveInfo = installedServiceInfos.get(i).getResolveInfo();
ComponentName installedService = new ComponentName(
resolveInfo.serviceInfo.packageName,
resolveInfo.serviceInfo.name);
installedServices.add(installedService);
}
}
private static Set<ComponentName> getEnabledServicesFromSettings(Context context) {
String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (enabledServicesSetting == null) {
enabledServicesSetting = "";
}
Set<ComponentName> enabledServices = new HashSet<ComponentName>();
SimpleStringSplitter colonSplitter = sStringColonSplitter;
colonSplitter.setString(enabledServicesSetting);
while (colonSplitter.hasNext()) {
String componentNameString = colonSplitter.next();
ComponentName enabledService = ComponentName.unflattenFromString(
componentNameString);
if (enabledService != null) {
enabledServices.add(enabledService);
}
}
return enabledServices;
}
private class SettingsPackageMonitor extends PackageMonitor {
@Override
public void onPackageAdded(String packageName, int uid) {
Message message = mHandler.obtainMessage();
mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
}
@Override
public void onPackageAppeared(String packageName, int reason) {
Message message = mHandler.obtainMessage();
mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
}
@Override
public void onPackageDisappeared(String packageName, int reason) {
Message message = mHandler.obtainMessage();
mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
}
@Override
public void onPackageRemoved(String packageName, int uid) {
Message message = mHandler.obtainMessage();
mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
}
}
public static class ToggleSwitch extends Switch {
private OnBeforeCheckedChangeListener mOnBeforeListener;
public static interface OnBeforeCheckedChangeListener {
public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked);
}
public ToggleSwitch(Context context) {
super(context);
}
public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) {
mOnBeforeListener = listener;
}
@Override
public void setChecked(boolean checked) {
if (mOnBeforeListener != null
&& mOnBeforeListener.onBeforeCheckedChanged(this, checked)) {
return;
}
super.setChecked(checked);
}
public void setCheckedInternal(boolean checked) {
super.setChecked(checked);
}
}
public static class ToggleAccessibilityServicePreferenceFragment
extends ToggleFeaturePreferenceFragment implements DialogInterface.OnClickListener {
private static final int DIALOG_ID_ENABLE_WARNING = 1;
private static final int DIALOG_ID_DISABLE_WARNING = 2;
private final SettingsContentObserver mSettingsContentObserver =
new SettingsContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange, Uri uri) {
String settingValue = Settings.Secure.getString(getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
final boolean enabled = settingValue.contains(mComponentName.flattenToString());
mToggleSwitch.setCheckedInternal(enabled);
}
};
private ComponentName mComponentName;
private int mShownDialogId;
@Override
public void onResume() {
mSettingsContentObserver.register(getContentResolver());
super.onResume();
}
@Override
public void onPause() {
mSettingsContentObserver.unregister(getContentResolver());
super.onPause();
}
@Override
public void onPreferenceToggled(String preferenceKey, boolean enabled) {
// Parse the enabled services.
Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity());
// Determine enabled services and accessibility state.
ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey);
boolean accessibilityEnabled = false;
if (enabled) {
enabledServices.add(toggledService);
// Enabling at least one service enables accessibility.
accessibilityEnabled = true;
} else {
enabledServices.remove(toggledService);
// Check how many enabled and installed services are present.
Set<ComponentName> installedServices = sInstalledServices;
for (ComponentName enabledService : enabledServices) {
if (installedServices.contains(enabledService)) {
// Disabling the last service disables accessibility.
accessibilityEnabled = true;
break;
}
}
}
// Update the enabled services setting.
StringBuilder enabledServicesBuilder = new StringBuilder();
// Keep the enabled services even if they are not installed since we
// have no way to know whether the application restore process has
// completed. In general the system should be responsible for the
// clean up not settings.
for (ComponentName enabledService : enabledServices) {
enabledServicesBuilder.append(enabledService.flattenToString());
enabledServicesBuilder.append(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
}
final int enabledServicesBuilderLength = enabledServicesBuilder.length();
if (enabledServicesBuilderLength > 0) {
enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
}
Settings.Secure.putString(getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
enabledServicesBuilder.toString());
// Update accessibility enabled.
Settings.Secure.putInt(getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0);
}
// IMPORTANT: Refresh the info since there are dynamically changing capabilities. For
// example, before JellyBean MR2 the user was granting the explore by touch one.
private AccessibilityServiceInfo getAccessibilityServiceInfo() {
List<AccessibilityServiceInfo> 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 (mComponentName.getPackageName().equals(resolveInfo.serviceInfo.packageName)
&& mComponentName.getClassName().equals(resolveInfo.serviceInfo.name)) {
return serviceInfo;
}
}
return null;
}
@Override
public Dialog onCreateDialog(int dialogId) {
switch (dialogId) {
case DIALOG_ID_ENABLE_WARNING: {
mShownDialogId = DIALOG_ID_ENABLE_WARNING;
AccessibilityServiceInfo info = getAccessibilityServiceInfo();
if (info == null) {
return null;
}
return new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.enable_service_title,
info.getResolveInfo().loadLabel(getPackageManager())))
.setIconAttribute(android.R.attr.alertDialogIcon)
.setView(createEnableDialogContentView(info))
.setCancelable(true)
.setPositiveButton(android.R.string.ok, this)
.setNegativeButton(android.R.string.cancel, this)
.create();
}
case DIALOG_ID_DISABLE_WARNING: {
mShownDialogId = DIALOG_ID_DISABLE_WARNING;
AccessibilityServiceInfo info = getAccessibilityServiceInfo();
if (info == null) {
return null;
}
return new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.disable_service_title,
info.getResolveInfo().loadLabel(getPackageManager())))
.setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(getString(R.string.disable_service_message,
info.getResolveInfo().loadLabel(getPackageManager())))
.setCancelable(true)
.setPositiveButton(android.R.string.ok, this)
.setNegativeButton(android.R.string.cancel, this)
.create();
}
default: {
throw new IllegalArgumentException();
}
}
}
private View createEnableDialogContentView(AccessibilityServiceInfo info) {
LayoutInflater inflater = (LayoutInflater) getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content,
null);
TextView capabilitiesHeaderView = (TextView) content.findViewById(
R.id.capabilities_header);
capabilitiesHeaderView.setText(getString(R.string.capabilities_list_title,
info.getResolveInfo().loadLabel(getPackageManager())));
LinearLayout capabilitiesView = (LinearLayout) content.findViewById(R.id.capabilities);
// This capability is implicit for all services.
View capabilityView = inflater.inflate(
com.android.internal.R.layout.app_permission_item_old, null);
ImageView imageView = (ImageView) capabilityView.findViewById(
com.android.internal.R.id.perm_icon);
imageView.setImageDrawable(getResources().getDrawable(
com.android.internal.R.drawable.ic_text_dot));
TextView labelView = (TextView) capabilityView.findViewById(
com.android.internal.R.id.permission_group);
labelView.setText(getString(R.string.capability_title_receiveAccessibilityEvents));
TextView descriptionView = (TextView) capabilityView.findViewById(
com.android.internal.R.id.permission_list);
descriptionView.setText(getString(R.string.capability_desc_receiveAccessibilityEvents));
List<AccessibilityServiceInfo.CapabilityInfo> capabilities =
info.getCapabilityInfos();
capabilitiesView.addView(capabilityView);
// Service specific capabilities.
final int capabilityCount = capabilities.size();
for (int i = 0; i < capabilityCount; i++) {
AccessibilityServiceInfo.CapabilityInfo capability = capabilities.get(i);
capabilityView = inflater.inflate(
com.android.internal.R.layout.app_permission_item_old, null);
imageView = (ImageView) capabilityView.findViewById(
com.android.internal.R.id.perm_icon);
imageView.setImageDrawable(getResources().getDrawable(
com.android.internal.R.drawable.ic_text_dot));
labelView = (TextView) capabilityView.findViewById(
com.android.internal.R.id.permission_group);
labelView.setText(getString(capability.titleResId));
descriptionView = (TextView) capabilityView.findViewById(
com.android.internal.R.id.permission_list);
descriptionView.setText(getString(capability.descResId));
capabilitiesView.addView(capabilityView);
}
return content;
}
@Override
public void onClick(DialogInterface dialog, int which) {
final boolean checked;
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
checked = (mShownDialogId == DIALOG_ID_ENABLE_WARNING);
mToggleSwitch.setCheckedInternal(checked);
getArguments().putBoolean(EXTRA_CHECKED, checked);
onPreferenceToggled(mPreferenceKey, checked);
break;
case DialogInterface.BUTTON_NEGATIVE:
checked = (mShownDialogId == DIALOG_ID_DISABLE_WARNING);
mToggleSwitch.setCheckedInternal(checked);
getArguments().putBoolean(EXTRA_CHECKED, checked);
onPreferenceToggled(mPreferenceKey, checked);
break;
default:
throw new IllegalArgumentException();
}
}
@Override
protected void onInstallActionBarToggleSwitch() {
super.onInstallActionBarToggleSwitch();
mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
@Override
public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
if (checked) {
toggleSwitch.setCheckedInternal(false);
getArguments().putBoolean(EXTRA_CHECKED, false);
showDialog(DIALOG_ID_ENABLE_WARNING);
} else {
toggleSwitch.setCheckedInternal(true);
getArguments().putBoolean(EXTRA_CHECKED, true);
showDialog(DIALOG_ID_DISABLE_WARNING);
}
return true;
}
});
}
@Override
protected void onProcessArguments(Bundle arguments) {
super.onProcessArguments(arguments);
// Settings title and intent.
String settingsTitle = arguments.getString(EXTRA_SETTINGS_TITLE);
String settingsComponentName = arguments.getString(EXTRA_SETTINGS_COMPONENT_NAME);
if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) {
Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(
ComponentName.unflattenFromString(settingsComponentName.toString()));
if (!getPackageManager().queryIntentActivities(settingsIntent, 0).isEmpty()) {
mSettingsTitle = settingsTitle;
mSettingsIntent = settingsIntent;
setHasOptionsMenu(true);
}
}
mComponentName = arguments.getParcelable(EXTRA_COMPONENT_NAME);
}
}
public static class ToggleScreenMagnificationPreferenceFragment
extends ToggleFeaturePreferenceFragment {
@Override
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
Settings.Secure.putInt(getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, enabled? 1 : 0);
}
@Override
protected void onInstallActionBarToggleSwitch() {
super.onInstallActionBarToggleSwitch();
mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
@Override
public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
toggleSwitch.setCheckedInternal(checked);
getArguments().putBoolean(EXTRA_CHECKED, checked);
onPreferenceToggled(mPreferenceKey, checked);
return false;
}
});
}
}
public static class ToggleGlobalGesturePreferenceFragment
extends ToggleFeaturePreferenceFragment {
@Override
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
Settings.Global.putInt(getContentResolver(),
Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, enabled ? 1 : 0);
}
@Override
protected void onInstallActionBarToggleSwitch() {
super.onInstallActionBarToggleSwitch();
mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
@Override
public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
toggleSwitch.setCheckedInternal(checked);
getArguments().putBoolean(EXTRA_CHECKED, checked);
onPreferenceToggled(mPreferenceKey, checked);
return false;
}
});
}
}
public static abstract class ToggleFeaturePreferenceFragment
extends SettingsPreferenceFragment {
protected ToggleSwitch mToggleSwitch;
protected String mPreferenceKey;
protected Preference mSummaryPreference;
protected CharSequence mSettingsTitle;
protected Intent mSettingsIntent;
// TODO: Showing sub-sub fragment does not handle the activity title
// so we do it but this is wrong. Do a real fix when there is time.
private CharSequence mOldActivityTitle;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
getActivity());
setPreferenceScreen(preferenceScreen);
mSummaryPreference = new Preference(getActivity()) {
@Override
protected void onBindView(View view) {
super.onBindView(view);
TextView summaryView = (TextView) view.findViewById(R.id.summary);
summaryView.setText(getSummary());
sendAccessibilityEvent(summaryView);
}
private void sendAccessibilityEvent(View view) {
// Since the view is still not attached we create, populate,
// and send the event directly since we do not know when it
// will be attached and posting commands is not as clean.
AccessibilityManager accessibilityManager =
AccessibilityManager.getInstance(getActivity());
if (accessibilityManager.isEnabled()) {
AccessibilityEvent event = AccessibilityEvent.obtain();
event.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
view.onInitializeAccessibilityEvent(event);
view.dispatchPopulateAccessibilityEvent(event);
accessibilityManager.sendAccessibilityEvent(event);
}
}
};
mSummaryPreference.setPersistent(false);
mSummaryPreference.setLayoutResource(R.layout.text_description_preference);
preferenceScreen.addPreference(mSummaryPreference);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
onInstallActionBarToggleSwitch();
onProcessArguments(getArguments());
// Set a transparent drawable to prevent use of the default one.
getListView().setSelector(new ColorDrawable(Color.TRANSPARENT));
getListView().setDivider(null);
}
@Override
public void onDestroyView() {
getActivity().getActionBar().setCustomView(null);
if (mOldActivityTitle != null) {
getActivity().getActionBar().setTitle(mOldActivityTitle);
}
mToggleSwitch.setOnBeforeCheckedChangeListener(null);
super.onDestroyView();
}
protected abstract void onPreferenceToggled(String preferenceKey, boolean enabled);
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
MenuItem menuItem = menu.add(mSettingsTitle);
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menuItem.setIntent(mSettingsIntent);
}
protected void onInstallActionBarToggleSwitch() {
mToggleSwitch = createAndAddActionBarToggleSwitch(getActivity());
}
private ToggleSwitch createAndAddActionBarToggleSwitch(Activity activity) {
ToggleSwitch toggleSwitch = new ToggleSwitch(activity);
final int padding = activity.getResources().getDimensionPixelSize(
R.dimen.action_bar_switch_padding);
toggleSwitch.setPaddingRelative(0, 0, padding, 0);
activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
ActionBar.DISPLAY_SHOW_CUSTOM);
activity.getActionBar().setCustomView(toggleSwitch,
new ActionBar.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT,
ActionBar.LayoutParams.WRAP_CONTENT,
Gravity.CENTER_VERTICAL | Gravity.END));
return toggleSwitch;
}
protected void onProcessArguments(Bundle arguments) {
// Key.
mPreferenceKey = arguments.getString(EXTRA_PREFERENCE_KEY);
// Enabled.
final boolean enabled = arguments.getBoolean(EXTRA_CHECKED);
mToggleSwitch.setCheckedInternal(enabled);
// Title.
PreferenceActivity activity = (PreferenceActivity) getActivity();
if (!activity.onIsMultiPane() || activity.onIsHidingHeaders()) {
mOldActivityTitle = getActivity().getTitle();
String title = arguments.getString(EXTRA_TITLE);
getActivity().getActionBar().setTitle(title);
}
// Summary.
CharSequence summary = arguments.getCharSequence(EXTRA_SUMMARY);
mSummaryPreference.setSummary(summary);
}
}
private static abstract class SettingsContentObserver extends ContentObserver {
public SettingsContentObserver(Handler handler) {
super(handler);
}
public void register(ContentResolver contentResolver) {
contentResolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_ENABLED), false, this);
contentResolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES), false, this);
}
public void unregister(ContentResolver contentResolver) {
contentResolver.unregisterContentObserver(this);
}
@Override
public abstract void onChange(boolean selfChange, Uri uri);
}
}