Home | History | Annotate | Download | only in accessibility
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      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
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     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.
     15  */
     16 
     17 package com.android.settings.accessibility;
     18 
     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;
     38 
     39 import com.android.settings.R;
     40 import com.android.settings.accessibility.ToggleSwitch.OnBeforeCheckedChangeListener;
     41 
     42 import java.util.Collections;
     43 import java.util.HashSet;
     44 import java.util.List;
     45 import java.util.Set;
     46 
     47 public class ToggleAccessibilityServicePreferenceFragment
     48         extends ToggleFeaturePreferenceFragment implements DialogInterface.OnClickListener {
     49 
     50     private static final int DIALOG_ID_ENABLE_WARNING = 1;
     51     private static final int DIALOG_ID_DISABLE_WARNING = 2;
     52 
     53     private final SettingsContentObserver mSettingsContentObserver =
     54             new SettingsContentObserver(new Handler()) {
     55             @Override
     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);
     61                 }
     62             };
     63 
     64     private ComponentName mComponentName;
     65 
     66     private int mShownDialogId;
     67 
     68     @Override
     69     public void onResume() {
     70         mSettingsContentObserver.register(getContentResolver());
     71         super.onResume();
     72     }
     73 
     74     @Override
     75     public void onPause() {
     76         mSettingsContentObserver.unregister(getContentResolver());
     77         super.onPause();
     78     }
     79 
     80     @Override
     81     public void onPreferenceToggled(String preferenceKey, boolean enabled) {
     82         // Parse the enabled services.
     83         Set<ComponentName> enabledServices = AccessibilityUtils.getEnabledServicesFromSettings(
     84                 getActivity());
     85 
     86         if (enabledServices == (Set<?>) Collections.emptySet()) {
     87             enabledServices = new HashSet<ComponentName>();
     88         }
     89 
     90         // Determine enabled services and accessibility state.
     91         ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey);
     92         boolean accessibilityEnabled = false;
     93         if (enabled) {
     94             enabledServices.add(toggledService);
     95             // Enabling at least one service enables accessibility.
     96             accessibilityEnabled = true;
     97         } else {
     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;
    105                     break;
    106                 }
    107             }
    108         }
    109 
    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);
    120         }
    121         final int enabledServicesBuilderLength = enabledServicesBuilder.length();
    122         if (enabledServicesBuilderLength > 0) {
    123             enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
    124         }
    125         Settings.Secure.putString(getContentResolver(),
    126                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
    127                 enabledServicesBuilder.toString());
    128 
    129         // Update accessibility enabled.
    130         Settings.Secure.putInt(getContentResolver(),
    131                 Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0);
    132     }
    133 
    134     // IMPORTANT: Refresh the info since there are dynamically changing
    135     // capabilities. For
    136     // example, before JellyBean MR2 the user was granting the explore by touch
    137     // one.
    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)) {
    147                 return serviceInfo;
    148             }
    149         }
    150         return null;
    151     }
    152 
    153     @Override
    154     public Dialog onCreateDialog(int dialogId) {
    155         switch (dialogId) {
    156             case DIALOG_ID_ENABLE_WARNING: {
    157             mShownDialogId = DIALOG_ID_ENABLE_WARNING;
    158             AccessibilityServiceInfo info = getAccessibilityServiceInfo();
    159             if (info == null) {
    160                 return null;
    161             }
    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))
    167                     .setCancelable(true)
    168                     .setPositiveButton(android.R.string.ok, this)
    169                     .setNegativeButton(android.R.string.cancel, this)
    170                     .create();
    171         }
    172             case DIALOG_ID_DISABLE_WARNING: {
    173             mShownDialogId = DIALOG_ID_DISABLE_WARNING;
    174             AccessibilityServiceInfo info = getAccessibilityServiceInfo();
    175             if (info == null) {
    176                 return null;
    177             }
    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())))
    184                     .setCancelable(true)
    185                     .setPositiveButton(android.R.string.ok, this)
    186                     .setNegativeButton(android.R.string.cancel, this)
    187                     .create();
    188         }
    189             default: {
    190             throw new IllegalArgumentException();
    191         }
    192         }
    193     }
    194 
    195     private View createEnableDialogContentView(AccessibilityServiceInfo info) {
    196         LayoutInflater inflater = (LayoutInflater) getSystemService(
    197                 Context.LAYOUT_INFLATER_SERVICE);
    198 
    199         View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content,
    200                 null);
    201 
    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())));
    206 
    207         LinearLayout capabilitiesView = (LinearLayout) content.findViewById(R.id.capabilities);
    208 
    209         // This capability is implicit for all services.
    210         View capabilityView = inflater.inflate(
    211                 com.android.internal.R.layout.app_permission_item_old, null);
    212 
    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));
    217 
    218         TextView labelView = (TextView) capabilityView.findViewById(
    219                 com.android.internal.R.id.permission_group);
    220         labelView.setText(getString(R.string.capability_title_receiveAccessibilityEvents));
    221 
    222         TextView descriptionView = (TextView) capabilityView.findViewById(
    223                 com.android.internal.R.id.permission_list);
    224         descriptionView.setText(getString(R.string.capability_desc_receiveAccessibilityEvents));
    225 
    226         List<AccessibilityServiceInfo.CapabilityInfo> capabilities =
    227                 info.getCapabilityInfos();
    228 
    229         capabilitiesView.addView(capabilityView);
    230 
    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);
    235 
    236             capabilityView = inflater.inflate(
    237                     com.android.internal.R.layout.app_permission_item_old, null);
    238 
    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));
    243 
    244             labelView = (TextView) capabilityView.findViewById(
    245                     com.android.internal.R.id.permission_group);
    246             labelView.setText(getString(capability.titleResId));
    247 
    248             descriptionView = (TextView) capabilityView.findViewById(
    249                     com.android.internal.R.id.permission_list);
    250             descriptionView.setText(getString(capability.descResId));
    251 
    252             capabilitiesView.addView(capabilityView);
    253         }
    254 
    255         return content;
    256     }
    257 
    258     @Override
    259     public void onClick(DialogInterface dialog, int which) {
    260         final boolean checked;
    261         switch (which) {
    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);
    267                 break;
    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);
    273                 break;
    274             default:
    275                 throw new IllegalArgumentException();
    276         }
    277     }
    278 
    279     @Override
    280     protected void onInstallActionBarToggleSwitch() {
    281         super.onInstallActionBarToggleSwitch();
    282         mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
    283                 @Override
    284             public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
    285                 if (checked) {
    286                     toggleSwitch.setCheckedInternal(false);
    287                     getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, false);
    288                     showDialog(DIALOG_ID_ENABLE_WARNING);
    289                 } else {
    290                     toggleSwitch.setCheckedInternal(true);
    291                     getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, true);
    292                     showDialog(DIALOG_ID_DISABLE_WARNING);
    293                 }
    294                 return true;
    295             }
    296         });
    297     }
    298 
    299     @Override
    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);
    313             }
    314         }
    315 
    316         mComponentName = arguments.getParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME);
    317     }
    318 }
    319