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.internal.widget.LockPatternUtils;
40 import com.android.settings.R;
41 import com.android.settings.widget.ToggleSwitch;
42 import com.android.settings.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
44 import java.util.Collections;
45 import java.util.HashSet;
46 import java.util.List;
49 public class ToggleAccessibilityServicePreferenceFragment
50 extends ToggleFeaturePreferenceFragment implements DialogInterface.OnClickListener {
52 private static final int DIALOG_ID_ENABLE_WARNING = 1;
53 private static final int DIALOG_ID_DISABLE_WARNING = 2;
55 private final SettingsContentObserver mSettingsContentObserver =
56 new SettingsContentObserver(new Handler()) {
58 public void onChange(boolean selfChange, Uri uri) {
59 String settingValue = Settings.Secure.getString(getContentResolver(),
60 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
61 final boolean enabled = settingValue.contains(mComponentName.flattenToString());
62 mSwitchBar.setCheckedInternal(enabled);
66 private ComponentName mComponentName;
68 private int mShownDialogId;
71 public void onResume() {
72 mSettingsContentObserver.register(getContentResolver());
77 public void onPause() {
78 mSettingsContentObserver.unregister(getContentResolver());
83 public void onPreferenceToggled(String preferenceKey, boolean enabled) {
84 // Parse the enabled services.
85 Set<ComponentName> enabledServices = AccessibilityUtils.getEnabledServicesFromSettings(
88 if (enabledServices == (Set<?>) Collections.emptySet()) {
89 enabledServices = new HashSet<ComponentName>();
92 // Determine enabled services and accessibility state.
93 ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey);
94 boolean accessibilityEnabled = false;
96 enabledServices.add(toggledService);
97 // Enabling at least one service enables accessibility.
98 accessibilityEnabled = true;
100 enabledServices.remove(toggledService);
101 // Check how many enabled and installed services are present.
102 Set<ComponentName> installedServices = AccessibilitySettings.sInstalledServices;
103 for (ComponentName enabledService : enabledServices) {
104 if (installedServices.contains(enabledService)) {
105 // Disabling the last service disables accessibility.
106 accessibilityEnabled = true;
112 // Update the enabled services setting.
113 StringBuilder enabledServicesBuilder = new StringBuilder();
114 // Keep the enabled services even if they are not installed since we
115 // have no way to know whether the application restore process has
116 // completed. In general the system should be responsible for the
117 // clean up not settings.
118 for (ComponentName enabledService : enabledServices) {
119 enabledServicesBuilder.append(enabledService.flattenToString());
120 enabledServicesBuilder.append(
121 AccessibilitySettings.ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
123 final int enabledServicesBuilderLength = enabledServicesBuilder.length();
124 if (enabledServicesBuilderLength > 0) {
125 enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
127 Settings.Secure.putString(getContentResolver(),
128 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
129 enabledServicesBuilder.toString());
131 // Update accessibility enabled.
132 Settings.Secure.putInt(getContentResolver(),
133 Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0);
136 // IMPORTANT: Refresh the info since there are dynamically changing
138 // example, before JellyBean MR2 the user was granting the explore by touch
140 private AccessibilityServiceInfo getAccessibilityServiceInfo() {
141 List<AccessibilityServiceInfo> serviceInfos = AccessibilityManager.getInstance(
142 getActivity()).getInstalledAccessibilityServiceList();
143 final int serviceInfoCount = serviceInfos.size();
144 for (int i = 0; i < serviceInfoCount; i++) {
145 AccessibilityServiceInfo serviceInfo = serviceInfos.get(i);
146 ResolveInfo resolveInfo = serviceInfo.getResolveInfo();
147 if (mComponentName.getPackageName().equals(resolveInfo.serviceInfo.packageName)
148 && mComponentName.getClassName().equals(resolveInfo.serviceInfo.name)) {
156 public Dialog onCreateDialog(int dialogId) {
158 case DIALOG_ID_ENABLE_WARNING: {
159 mShownDialogId = DIALOG_ID_ENABLE_WARNING;
160 AccessibilityServiceInfo info = getAccessibilityServiceInfo();
164 AlertDialog ad = new AlertDialog.Builder(getActivity())
165 .setTitle(getString(R.string.enable_service_title,
166 info.getResolveInfo().loadLabel(getPackageManager())))
167 .setView(createEnableDialogContentView(info))
169 .setPositiveButton(android.R.string.ok, this)
170 .setNegativeButton(android.R.string.cancel, this)
173 ad.getButton(AlertDialog.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
176 case DIALOG_ID_DISABLE_WARNING: {
177 mShownDialogId = DIALOG_ID_DISABLE_WARNING;
178 AccessibilityServiceInfo info = getAccessibilityServiceInfo();
182 return new AlertDialog.Builder(getActivity())
183 .setTitle(getString(R.string.disable_service_title,
184 info.getResolveInfo().loadLabel(getPackageManager())))
185 .setMessage(getString(R.string.disable_service_message,
186 info.getResolveInfo().loadLabel(getPackageManager())))
188 .setPositiveButton(android.R.string.ok, this)
189 .setNegativeButton(android.R.string.cancel, this)
193 throw new IllegalArgumentException();
198 private View createEnableDialogContentView(AccessibilityServiceInfo info) {
199 LayoutInflater inflater = (LayoutInflater) getSystemService(
200 Context.LAYOUT_INFLATER_SERVICE);
202 View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content,
205 TextView encryptionWarningView = (TextView) content.findViewById(
206 R.id.encryption_warning);
207 if (LockPatternUtils.isDeviceEncrypted()) {
208 String text = getString(R.string.enable_service_encryption_warning,
209 info.getResolveInfo().loadLabel(getPackageManager()));
210 encryptionWarningView.setText(text);
211 encryptionWarningView.setVisibility(View.VISIBLE);
213 encryptionWarningView.setVisibility(View.GONE);
216 TextView capabilitiesHeaderView = (TextView) content.findViewById(
217 R.id.capabilities_header);
218 capabilitiesHeaderView.setText(getString(R.string.capabilities_list_title,
219 info.getResolveInfo().loadLabel(getPackageManager())));
221 LinearLayout capabilitiesView = (LinearLayout) content.findViewById(R.id.capabilities);
223 // This capability is implicit for all services.
224 View capabilityView = inflater.inflate(
225 com.android.internal.R.layout.app_permission_item_old, null);
227 ImageView imageView = (ImageView) capabilityView.findViewById(
228 com.android.internal.R.id.perm_icon);
229 imageView.setImageDrawable(getResources().getDrawable(
230 com.android.internal.R.drawable.ic_text_dot));
232 TextView labelView = (TextView) capabilityView.findViewById(
233 com.android.internal.R.id.permission_group);
234 labelView.setText(getString(R.string.capability_title_receiveAccessibilityEvents));
236 TextView descriptionView = (TextView) capabilityView.findViewById(
237 com.android.internal.R.id.permission_list);
238 descriptionView.setText(getString(R.string.capability_desc_receiveAccessibilityEvents));
240 List<AccessibilityServiceInfo.CapabilityInfo> capabilities =
241 info.getCapabilityInfos();
243 capabilitiesView.addView(capabilityView);
245 // Service specific capabilities.
246 final int capabilityCount = capabilities.size();
247 for (int i = 0; i < capabilityCount; i++) {
248 AccessibilityServiceInfo.CapabilityInfo capability = capabilities.get(i);
250 capabilityView = inflater.inflate(
251 com.android.internal.R.layout.app_permission_item_old, null);
253 imageView = (ImageView) capabilityView.findViewById(
254 com.android.internal.R.id.perm_icon);
255 imageView.setImageDrawable(getResources().getDrawable(
256 com.android.internal.R.drawable.ic_text_dot));
258 labelView = (TextView) capabilityView.findViewById(
259 com.android.internal.R.id.permission_group);
260 labelView.setText(getString(capability.titleResId));
262 descriptionView = (TextView) capabilityView.findViewById(
263 com.android.internal.R.id.permission_list);
264 descriptionView.setText(getString(capability.descResId));
266 capabilitiesView.addView(capabilityView);
273 public void onClick(DialogInterface dialog, int which) {
274 final boolean checked;
276 case DialogInterface.BUTTON_POSITIVE:
277 checked = (mShownDialogId == DIALOG_ID_ENABLE_WARNING);
278 mSwitchBar.setCheckedInternal(checked);
279 getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, checked);
280 onPreferenceToggled(mPreferenceKey, checked);
282 case DialogInterface.BUTTON_NEGATIVE:
283 checked = (mShownDialogId == DIALOG_ID_DISABLE_WARNING);
284 mSwitchBar.setCheckedInternal(checked);
285 getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, checked);
286 onPreferenceToggled(mPreferenceKey, checked);
289 throw new IllegalArgumentException();
294 protected void onInstallSwitchBarToggleSwitch() {
295 super.onInstallSwitchBarToggleSwitch();
296 mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
298 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
300 mSwitchBar.setCheckedInternal(false);
301 getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, false);
302 showDialog(DIALOG_ID_ENABLE_WARNING);
304 mSwitchBar.setCheckedInternal(true);
305 getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, true);
306 showDialog(DIALOG_ID_DISABLE_WARNING);
314 protected void onProcessArguments(Bundle arguments) {
315 super.onProcessArguments(arguments);
316 // Settings title and intent.
317 String settingsTitle = arguments.getString(AccessibilitySettings.EXTRA_SETTINGS_TITLE);
318 String settingsComponentName = arguments.getString(
319 AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME);
320 if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) {
321 Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(
322 ComponentName.unflattenFromString(settingsComponentName.toString()));
323 if (!getPackageManager().queryIntentActivities(settingsIntent, 0).isEmpty()) {
324 mSettingsTitle = settingsTitle;
325 mSettingsIntent = settingsIntent;
326 setHasOptionsMenu(true);
330 mComponentName = arguments.getParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME);