2 * Copyright (C) 2013 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.settings.accessibility;
19 import android.accessibilityservice.AccessibilityServiceInfo;
20 import android.app.AlertDialog;
21 import android.app.Dialog;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.content.pm.ResolveInfo;
27 import android.net.Uri;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.provider.Settings;
31 import android.text.TextUtils;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.accessibility.AccessibilityManager;
35 import android.widget.ImageView;
36 import android.widget.LinearLayout;
37 import android.widget.TextView;
39 import com.android.settings.R;
40 import com.android.settings.accessibility.ToggleSwitch.OnBeforeCheckedChangeListener;
42 import java.util.Collections;
43 import java.util.HashSet;
44 import java.util.List;
47 public class ToggleAccessibilityServicePreferenceFragment
48 extends ToggleFeaturePreferenceFragment implements DialogInterface.OnClickListener {
50 private static final int DIALOG_ID_ENABLE_WARNING = 1;
51 private static final int DIALOG_ID_DISABLE_WARNING = 2;
53 private final SettingsContentObserver mSettingsContentObserver =
54 new SettingsContentObserver(new Handler()) {
56 public void onChange(boolean selfChange, Uri uri) {
57 String settingValue = Settings.Secure.getString(getContentResolver(),
58 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
59 final boolean enabled = settingValue.contains(mComponentName.flattenToString());
60 mToggleSwitch.setCheckedInternal(enabled);
64 private ComponentName mComponentName;
66 private int mShownDialogId;
69 public void onResume() {
70 mSettingsContentObserver.register(getContentResolver());
75 public void onPause() {
76 mSettingsContentObserver.unregister(getContentResolver());
81 public void onPreferenceToggled(String preferenceKey, boolean enabled) {
82 // Parse the enabled services.
83 Set<ComponentName> enabledServices = AccessibilityUtils.getEnabledServicesFromSettings(
86 if (enabledServices == (Set<?>) Collections.emptySet()) {
87 enabledServices = new HashSet<ComponentName>();
90 // Determine enabled services and accessibility state.
91 ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey);
92 boolean accessibilityEnabled = false;
94 enabledServices.add(toggledService);
95 // Enabling at least one service enables accessibility.
96 accessibilityEnabled = true;
98 enabledServices.remove(toggledService);
99 // Check how many enabled and installed services are present.
100 Set<ComponentName> installedServices = AccessibilitySettings.sInstalledServices;
101 for (ComponentName enabledService : enabledServices) {
102 if (installedServices.contains(enabledService)) {
103 // Disabling the last service disables accessibility.
104 accessibilityEnabled = true;
110 // Update the enabled services setting.
111 StringBuilder enabledServicesBuilder = new StringBuilder();
112 // Keep the enabled services even if they are not installed since we
113 // have no way to know whether the application restore process has
114 // completed. In general the system should be responsible for the
115 // clean up not settings.
116 for (ComponentName enabledService : enabledServices) {
117 enabledServicesBuilder.append(enabledService.flattenToString());
118 enabledServicesBuilder.append(
119 AccessibilitySettings.ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
121 final int enabledServicesBuilderLength = enabledServicesBuilder.length();
122 if (enabledServicesBuilderLength > 0) {
123 enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
125 Settings.Secure.putString(getContentResolver(),
126 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
127 enabledServicesBuilder.toString());
129 // Update accessibility enabled.
130 Settings.Secure.putInt(getContentResolver(),
131 Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0);
134 // IMPORTANT: Refresh the info since there are dynamically changing
136 // example, before JellyBean MR2 the user was granting the explore by touch
138 private AccessibilityServiceInfo getAccessibilityServiceInfo() {
139 List<AccessibilityServiceInfo> serviceInfos = AccessibilityManager.getInstance(
140 getActivity()).getInstalledAccessibilityServiceList();
141 final int serviceInfoCount = serviceInfos.size();
142 for (int i = 0; i < serviceInfoCount; i++) {
143 AccessibilityServiceInfo serviceInfo = serviceInfos.get(i);
144 ResolveInfo resolveInfo = serviceInfo.getResolveInfo();
145 if (mComponentName.getPackageName().equals(resolveInfo.serviceInfo.packageName)
146 && mComponentName.getClassName().equals(resolveInfo.serviceInfo.name)) {
154 public Dialog onCreateDialog(int dialogId) {
156 case DIALOG_ID_ENABLE_WARNING: {
157 mShownDialogId = DIALOG_ID_ENABLE_WARNING;
158 AccessibilityServiceInfo info = getAccessibilityServiceInfo();
162 return new AlertDialog.Builder(getActivity())
163 .setTitle(getString(R.string.enable_service_title,
164 info.getResolveInfo().loadLabel(getPackageManager())))
165 .setIconAttribute(android.R.attr.alertDialogIcon)
166 .setView(createEnableDialogContentView(info))
168 .setPositiveButton(android.R.string.ok, this)
169 .setNegativeButton(android.R.string.cancel, this)
172 case DIALOG_ID_DISABLE_WARNING: {
173 mShownDialogId = DIALOG_ID_DISABLE_WARNING;
174 AccessibilityServiceInfo info = getAccessibilityServiceInfo();
178 return new AlertDialog.Builder(getActivity())
179 .setTitle(getString(R.string.disable_service_title,
180 info.getResolveInfo().loadLabel(getPackageManager())))
181 .setIconAttribute(android.R.attr.alertDialogIcon)
182 .setMessage(getString(R.string.disable_service_message,
183 info.getResolveInfo().loadLabel(getPackageManager())))
185 .setPositiveButton(android.R.string.ok, this)
186 .setNegativeButton(android.R.string.cancel, this)
190 throw new IllegalArgumentException();
195 private View createEnableDialogContentView(AccessibilityServiceInfo info) {
196 LayoutInflater inflater = (LayoutInflater) getSystemService(
197 Context.LAYOUT_INFLATER_SERVICE);
199 View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content,
202 TextView capabilitiesHeaderView = (TextView) content.findViewById(
203 R.id.capabilities_header);
204 capabilitiesHeaderView.setText(getString(R.string.capabilities_list_title,
205 info.getResolveInfo().loadLabel(getPackageManager())));
207 LinearLayout capabilitiesView = (LinearLayout) content.findViewById(R.id.capabilities);
209 // This capability is implicit for all services.
210 View capabilityView = inflater.inflate(
211 com.android.internal.R.layout.app_permission_item_old, null);
213 ImageView imageView = (ImageView) capabilityView.findViewById(
214 com.android.internal.R.id.perm_icon);
215 imageView.setImageDrawable(getResources().getDrawable(
216 com.android.internal.R.drawable.ic_text_dot));
218 TextView labelView = (TextView) capabilityView.findViewById(
219 com.android.internal.R.id.permission_group);
220 labelView.setText(getString(R.string.capability_title_receiveAccessibilityEvents));
222 TextView descriptionView = (TextView) capabilityView.findViewById(
223 com.android.internal.R.id.permission_list);
224 descriptionView.setText(getString(R.string.capability_desc_receiveAccessibilityEvents));
226 List<AccessibilityServiceInfo.CapabilityInfo> capabilities =
227 info.getCapabilityInfos();
229 capabilitiesView.addView(capabilityView);
231 // Service specific capabilities.
232 final int capabilityCount = capabilities.size();
233 for (int i = 0; i < capabilityCount; i++) {
234 AccessibilityServiceInfo.CapabilityInfo capability = capabilities.get(i);
236 capabilityView = inflater.inflate(
237 com.android.internal.R.layout.app_permission_item_old, null);
239 imageView = (ImageView) capabilityView.findViewById(
240 com.android.internal.R.id.perm_icon);
241 imageView.setImageDrawable(getResources().getDrawable(
242 com.android.internal.R.drawable.ic_text_dot));
244 labelView = (TextView) capabilityView.findViewById(
245 com.android.internal.R.id.permission_group);
246 labelView.setText(getString(capability.titleResId));
248 descriptionView = (TextView) capabilityView.findViewById(
249 com.android.internal.R.id.permission_list);
250 descriptionView.setText(getString(capability.descResId));
252 capabilitiesView.addView(capabilityView);
259 public void onClick(DialogInterface dialog, int which) {
260 final boolean checked;
262 case DialogInterface.BUTTON_POSITIVE:
263 checked = (mShownDialogId == DIALOG_ID_ENABLE_WARNING);
264 mToggleSwitch.setCheckedInternal(checked);
265 getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, checked);
266 onPreferenceToggled(mPreferenceKey, checked);
268 case DialogInterface.BUTTON_NEGATIVE:
269 checked = (mShownDialogId == DIALOG_ID_DISABLE_WARNING);
270 mToggleSwitch.setCheckedInternal(checked);
271 getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, checked);
272 onPreferenceToggled(mPreferenceKey, checked);
275 throw new IllegalArgumentException();
280 protected void onInstallActionBarToggleSwitch() {
281 super.onInstallActionBarToggleSwitch();
282 mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
284 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
286 toggleSwitch.setCheckedInternal(false);
287 getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, false);
288 showDialog(DIALOG_ID_ENABLE_WARNING);
290 toggleSwitch.setCheckedInternal(true);
291 getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, true);
292 showDialog(DIALOG_ID_DISABLE_WARNING);
300 protected void onProcessArguments(Bundle arguments) {
301 super.onProcessArguments(arguments);
302 // Settings title and intent.
303 String settingsTitle = arguments.getString(AccessibilitySettings.EXTRA_SETTINGS_TITLE);
304 String settingsComponentName = arguments.getString(
305 AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME);
306 if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) {
307 Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(
308 ComponentName.unflattenFromString(settingsComponentName.toString()));
309 if (!getPackageManager().queryIntentActivities(settingsIntent, 0).isEmpty()) {
310 mSettingsTitle = settingsTitle;
311 mSettingsIntent = settingsIntent;
312 setHasOptionsMenu(true);
316 mComponentName = arguments.getParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME);