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