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.Activity; 21 import android.app.AlertDialog; 22 import android.app.Dialog; 23 import android.app.admin.DevicePolicyManager; 24 import android.content.ComponentName; 25 import android.content.DialogInterface; 26 import android.content.Intent; 27 import android.content.pm.ResolveInfo; 28 import android.net.Uri; 29 import android.os.Binder; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.IBinder; 33 import android.os.UserHandle; 34 import android.os.storage.StorageManager; 35 import android.provider.Settings; 36 import android.text.TextUtils; 37 import android.view.Menu; 38 import android.view.MenuInflater; 39 import android.view.accessibility.AccessibilityManager; 40 41 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 42 import com.android.internal.widget.LockPatternUtils; 43 import com.android.settings.R; 44 import com.android.settings.password.ConfirmDeviceCredentialActivity; 45 import com.android.settings.widget.ToggleSwitch; 46 import com.android.settings.widget.ToggleSwitch.OnBeforeCheckedChangeListener; 47 import com.android.settingslib.accessibility.AccessibilityUtils; 48 49 import java.util.List; 50 51 public class ToggleAccessibilityServicePreferenceFragment 52 extends ToggleFeaturePreferenceFragment implements DialogInterface.OnClickListener { 53 54 private static final int DIALOG_ID_ENABLE_WARNING = 1; 55 private static final int DIALOG_ID_DISABLE_WARNING = 2; 56 57 public static final int ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION = 1; 58 59 private LockPatternUtils mLockPatternUtils; 60 61 private final SettingsContentObserver mSettingsContentObserver = 62 new SettingsContentObserver(new Handler()) { 63 @Override 64 public void onChange(boolean selfChange, Uri uri) { 65 updateSwitchBarToggleSwitch(); 66 } 67 }; 68 69 private ComponentName mComponentName; 70 71 private int mShownDialogId; 72 73 @Override getMetricsCategory()74 public int getMetricsCategory() { 75 return MetricsEvent.ACCESSIBILITY_SERVICE; 76 } 77 78 @Override onCreateOptionsMenu(Menu menu, MenuInflater infalter)79 public void onCreateOptionsMenu(Menu menu, MenuInflater infalter) { 80 // Do not call super. We don't want to see the "Help & feedback" option on this page so as 81 // not to confuse users who think they might be able to send feedback about a specific 82 // accessibility service from this page. 83 } 84 85 @Override onCreate(Bundle savedInstanceState)86 public void onCreate(Bundle savedInstanceState) { 87 super.onCreate(savedInstanceState); 88 mLockPatternUtils = new LockPatternUtils(getActivity()); 89 } 90 91 @Override onResume()92 public void onResume() { 93 mSettingsContentObserver.register(getContentResolver()); 94 updateSwitchBarToggleSwitch(); 95 super.onResume(); 96 } 97 98 @Override onPause()99 public void onPause() { 100 mSettingsContentObserver.unregister(getContentResolver()); 101 super.onPause(); 102 } 103 104 @Override onPreferenceToggled(String preferenceKey, boolean enabled)105 public void onPreferenceToggled(String preferenceKey, boolean enabled) { 106 ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey); 107 AccessibilityUtils.setAccessibilityServiceState(getActivity(), toggledService, enabled); 108 } 109 110 // IMPORTANT: Refresh the info since there are dynamically changing 111 // capabilities. For 112 // example, before JellyBean MR2 the user was granting the explore by touch 113 // one. getAccessibilityServiceInfo()114 private AccessibilityServiceInfo getAccessibilityServiceInfo() { 115 List<AccessibilityServiceInfo> serviceInfos = AccessibilityManager.getInstance( 116 getActivity()).getInstalledAccessibilityServiceList(); 117 final int serviceInfoCount = serviceInfos.size(); 118 for (int i = 0; i < serviceInfoCount; i++) { 119 AccessibilityServiceInfo serviceInfo = serviceInfos.get(i); 120 ResolveInfo resolveInfo = serviceInfo.getResolveInfo(); 121 if (mComponentName.getPackageName().equals(resolveInfo.serviceInfo.packageName) 122 && mComponentName.getClassName().equals(resolveInfo.serviceInfo.name)) { 123 return serviceInfo; 124 } 125 } 126 return null; 127 } 128 129 @Override onCreateDialog(int dialogId)130 public Dialog onCreateDialog(int dialogId) { 131 switch (dialogId) { 132 case DIALOG_ID_ENABLE_WARNING: { 133 mShownDialogId = DIALOG_ID_ENABLE_WARNING; 134 final AccessibilityServiceInfo info = getAccessibilityServiceInfo(); 135 if (info == null) { 136 return null; 137 } 138 139 return AccessibilityServiceWarning 140 .createCapabilitiesDialog(getActivity(), info, this); 141 } 142 case DIALOG_ID_DISABLE_WARNING: { 143 mShownDialogId = DIALOG_ID_DISABLE_WARNING; 144 AccessibilityServiceInfo info = getAccessibilityServiceInfo(); 145 if (info == null) { 146 return null; 147 } 148 return new AlertDialog.Builder(getActivity()) 149 .setTitle(getString(R.string.disable_service_title, 150 info.getResolveInfo().loadLabel(getPackageManager()))) 151 .setMessage(getString(R.string.disable_service_message, 152 info.getResolveInfo().loadLabel(getPackageManager()))) 153 .setCancelable(true) 154 .setPositiveButton(android.R.string.ok, this) 155 .setNegativeButton(android.R.string.cancel, this) 156 .create(); 157 } 158 default: { 159 throw new IllegalArgumentException(); 160 } 161 } 162 } 163 164 @Override getDialogMetricsCategory(int dialogId)165 public int getDialogMetricsCategory(int dialogId) { 166 if (dialogId == DIALOG_ID_ENABLE_WARNING) { 167 return MetricsEvent.DIALOG_ACCESSIBILITY_SERVICE_ENABLE; 168 } else { 169 return MetricsEvent.DIALOG_ACCESSIBILITY_SERVICE_DISABLE; 170 } 171 } 172 updateSwitchBarToggleSwitch()173 private void updateSwitchBarToggleSwitch() { 174 final boolean checked = AccessibilityUtils.getEnabledServicesFromSettings(getActivity()) 175 .contains(mComponentName); 176 mSwitchBar.setCheckedInternal(checked); 177 } 178 179 /** 180 * Return whether the device is encrypted with legacy full disk encryption. Newer devices 181 * should be using File Based Encryption. 182 * 183 * @return true if device is encrypted 184 */ isFullDiskEncrypted()185 private boolean isFullDiskEncrypted() { 186 return StorageManager.isNonDefaultBlockEncrypted(); 187 } 188 189 @Override onActivityResult(int requestCode, int resultCode, Intent data)190 public void onActivityResult(int requestCode, int resultCode, Intent data) { 191 if (requestCode == ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION) { 192 if (resultCode == Activity.RESULT_OK) { 193 handleConfirmServiceEnabled(true); 194 // The user confirmed that they accept weaker encryption when 195 // enabling the accessibility service, so change encryption. 196 // Since we came here asynchronously, check encryption again. 197 if (isFullDiskEncrypted()) { 198 mLockPatternUtils.clearEncryptionPassword(); 199 Settings.Global.putInt(getContentResolver(), 200 Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT, 0); 201 } 202 } else { 203 handleConfirmServiceEnabled(false); 204 } 205 } 206 } 207 208 @Override onClick(DialogInterface dialog, int which)209 public void onClick(DialogInterface dialog, int which) { 210 final boolean checked; 211 switch (which) { 212 case DialogInterface.BUTTON_POSITIVE: 213 if (mShownDialogId == DIALOG_ID_ENABLE_WARNING) { 214 if (isFullDiskEncrypted()) { 215 String title = createConfirmCredentialReasonMessage(); 216 Intent intent = ConfirmDeviceCredentialActivity.createIntent(title, null); 217 startActivityForResult(intent, 218 ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION); 219 } else { 220 handleConfirmServiceEnabled(true); 221 } 222 } else { 223 handleConfirmServiceEnabled(false); 224 } 225 break; 226 case DialogInterface.BUTTON_NEGATIVE: 227 checked = (mShownDialogId == DIALOG_ID_DISABLE_WARNING); 228 handleConfirmServiceEnabled(checked); 229 break; 230 default: 231 throw new IllegalArgumentException(); 232 } 233 } 234 handleConfirmServiceEnabled(boolean confirmed)235 private void handleConfirmServiceEnabled(boolean confirmed) { 236 mSwitchBar.setCheckedInternal(confirmed); 237 getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, confirmed); 238 onPreferenceToggled(mPreferenceKey, confirmed); 239 } 240 createConfirmCredentialReasonMessage()241 private String createConfirmCredentialReasonMessage() { 242 int resId = R.string.enable_service_password_reason; 243 switch (mLockPatternUtils.getKeyguardStoredPasswordQuality(UserHandle.myUserId())) { 244 case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: { 245 resId = R.string.enable_service_pattern_reason; 246 } break; 247 case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: 248 case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: { 249 resId = R.string.enable_service_pin_reason; 250 } break; 251 } 252 return getString(resId, getAccessibilityServiceInfo().getResolveInfo() 253 .loadLabel(getPackageManager())); 254 } 255 256 @Override onInstallSwitchBarToggleSwitch()257 protected void onInstallSwitchBarToggleSwitch() { 258 super.onInstallSwitchBarToggleSwitch(); 259 mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() { 260 @Override 261 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) { 262 if (checked) { 263 mSwitchBar.setCheckedInternal(false); 264 getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, false); 265 showDialog(DIALOG_ID_ENABLE_WARNING); 266 } else { 267 mSwitchBar.setCheckedInternal(true); 268 getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, true); 269 showDialog(DIALOG_ID_DISABLE_WARNING); 270 } 271 return true; 272 } 273 }); 274 } 275 276 @Override onProcessArguments(Bundle arguments)277 protected void onProcessArguments(Bundle arguments) { 278 super.onProcessArguments(arguments); 279 // Settings title and intent. 280 String settingsTitle = arguments.getString(AccessibilitySettings.EXTRA_SETTINGS_TITLE); 281 String settingsComponentName = arguments.getString( 282 AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME); 283 if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) { 284 Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent( 285 ComponentName.unflattenFromString(settingsComponentName.toString())); 286 if (!getPackageManager().queryIntentActivities(settingsIntent, 0).isEmpty()) { 287 mSettingsTitle = settingsTitle; 288 mSettingsIntent = settingsIntent; 289 setHasOptionsMenu(true); 290 } 291 } 292 293 mComponentName = arguments.getParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME); 294 } 295 } 296