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.Context; 26 import android.content.DialogInterface; 27 import android.content.Intent; 28 import android.content.pm.ResolveInfo; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.UserHandle; 33 import android.os.storage.StorageManager; 34 import android.provider.Settings; 35 import android.text.TextUtils; 36 import android.view.LayoutInflater; 37 import android.view.MotionEvent; 38 import android.view.View; 39 import android.view.accessibility.AccessibilityManager; 40 import android.widget.ImageView; 41 import android.widget.LinearLayout; 42 import android.widget.TextView; 43 import android.widget.Toast; 44 45 import com.android.internal.logging.MetricsProto.MetricsEvent; 46 import com.android.internal.widget.LockPatternUtils; 47 import com.android.settings.ConfirmDeviceCredentialActivity; 48 import com.android.settings.R; 49 import com.android.settings.widget.ToggleSwitch; 50 import com.android.settings.widget.ToggleSwitch.OnBeforeCheckedChangeListener; 51 import com.android.settingslib.accessibility.AccessibilityUtils; 52 53 import java.util.List; 54 55 public class ToggleAccessibilityServicePreferenceFragment 56 extends ToggleFeaturePreferenceFragment implements DialogInterface.OnClickListener { 57 58 private static final int DIALOG_ID_ENABLE_WARNING = 1; 59 private static final int DIALOG_ID_DISABLE_WARNING = 2; 60 61 public static final int ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION = 1; 62 63 private LockPatternUtils mLockPatternUtils; 64 65 private final SettingsContentObserver mSettingsContentObserver = 66 new SettingsContentObserver(new Handler()) { 67 @Override 68 public void onChange(boolean selfChange, Uri uri) { 69 updateSwitchBarToggleSwitch(); 70 } 71 }; 72 73 private ComponentName mComponentName; 74 75 private int mShownDialogId; 76 77 @Override getMetricsCategory()78 protected int getMetricsCategory() { 79 return MetricsEvent.ACCESSIBILITY_SERVICE; 80 } 81 82 @Override onCreate(Bundle savedInstanceState)83 public void onCreate(Bundle savedInstanceState) { 84 super.onCreate(savedInstanceState); 85 mLockPatternUtils = new LockPatternUtils(getActivity()); 86 } 87 88 @Override onResume()89 public void onResume() { 90 mSettingsContentObserver.register(getContentResolver()); 91 updateSwitchBarToggleSwitch(); 92 super.onResume(); 93 } 94 95 @Override onPause()96 public void onPause() { 97 mSettingsContentObserver.unregister(getContentResolver()); 98 super.onPause(); 99 } 100 101 @Override onPreferenceToggled(String preferenceKey, boolean enabled)102 public void onPreferenceToggled(String preferenceKey, boolean enabled) { 103 ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey); 104 AccessibilityUtils.setAccessibilityServiceState(getActivity(), toggledService, enabled); 105 } 106 107 // IMPORTANT: Refresh the info since there are dynamically changing 108 // capabilities. For 109 // example, before JellyBean MR2 the user was granting the explore by touch 110 // one. getAccessibilityServiceInfo()111 private AccessibilityServiceInfo getAccessibilityServiceInfo() { 112 List<AccessibilityServiceInfo> serviceInfos = AccessibilityManager.getInstance( 113 getActivity()).getInstalledAccessibilityServiceList(); 114 final int serviceInfoCount = serviceInfos.size(); 115 for (int i = 0; i < serviceInfoCount; i++) { 116 AccessibilityServiceInfo serviceInfo = serviceInfos.get(i); 117 ResolveInfo resolveInfo = serviceInfo.getResolveInfo(); 118 if (mComponentName.getPackageName().equals(resolveInfo.serviceInfo.packageName) 119 && mComponentName.getClassName().equals(resolveInfo.serviceInfo.name)) { 120 return serviceInfo; 121 } 122 } 123 return null; 124 } 125 126 @Override onCreateDialog(int dialogId)127 public Dialog onCreateDialog(int dialogId) { 128 switch (dialogId) { 129 case DIALOG_ID_ENABLE_WARNING: { 130 mShownDialogId = DIALOG_ID_ENABLE_WARNING; 131 132 final AccessibilityServiceInfo info = getAccessibilityServiceInfo(); 133 if (info == null) { 134 return null; 135 } 136 137 final AlertDialog ad = new AlertDialog.Builder(getActivity()) 138 .setTitle(getString(R.string.enable_service_title, 139 info.getResolveInfo().loadLabel(getPackageManager()))) 140 .setView(createEnableDialogContentView(info)) 141 .setCancelable(true) 142 .setPositiveButton(android.R.string.ok, this) 143 .setNegativeButton(android.R.string.cancel, this) 144 .create(); 145 146 final View.OnTouchListener filterTouchListener = new View.OnTouchListener() { 147 @Override 148 public boolean onTouch(View v, MotionEvent event) { 149 // Filter obscured touches by consuming them. 150 if ((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) { 151 if (event.getAction() == MotionEvent.ACTION_UP) { 152 Toast.makeText(v.getContext(), R.string.touch_filtered_warning, 153 Toast.LENGTH_SHORT).show(); 154 } 155 return true; 156 } 157 return false; 158 } 159 }; 160 161 ad.create(); 162 ad.getButton(AlertDialog.BUTTON_POSITIVE).setOnTouchListener(filterTouchListener); 163 return ad; 164 } 165 case DIALOG_ID_DISABLE_WARNING: { 166 mShownDialogId = DIALOG_ID_DISABLE_WARNING; 167 AccessibilityServiceInfo info = getAccessibilityServiceInfo(); 168 if (info == null) { 169 return null; 170 } 171 return new AlertDialog.Builder(getActivity()) 172 .setTitle(getString(R.string.disable_service_title, 173 info.getResolveInfo().loadLabel(getPackageManager()))) 174 .setMessage(getString(R.string.disable_service_message, 175 info.getResolveInfo().loadLabel(getPackageManager()))) 176 .setCancelable(true) 177 .setPositiveButton(android.R.string.ok, this) 178 .setNegativeButton(android.R.string.cancel, this) 179 .create(); 180 } 181 default: { 182 throw new IllegalArgumentException(); 183 } 184 } 185 } 186 updateSwitchBarToggleSwitch()187 private void updateSwitchBarToggleSwitch() { 188 final String settingValue = Settings.Secure.getString(getContentResolver(), 189 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 190 final boolean checked = settingValue != null 191 && settingValue.contains(mComponentName.flattenToString()); 192 mSwitchBar.setCheckedInternal(checked); 193 } 194 195 /** 196 * Return whether the device is encrypted with legacy full disk encryption. Newer devices 197 * should be using File Based Encryption. 198 * 199 * @return true if device is encrypted 200 */ isFullDiskEncrypted()201 private boolean isFullDiskEncrypted() { 202 return StorageManager.isNonDefaultBlockEncrypted(); 203 } 204 createEnableDialogContentView(AccessibilityServiceInfo info)205 private View createEnableDialogContentView(AccessibilityServiceInfo info) { 206 LayoutInflater inflater = (LayoutInflater) getSystemService( 207 Context.LAYOUT_INFLATER_SERVICE); 208 209 View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content, 210 null); 211 212 TextView encryptionWarningView = (TextView) content.findViewById( 213 R.id.encryption_warning); 214 if (isFullDiskEncrypted()) { 215 String text = getString(R.string.enable_service_encryption_warning, 216 info.getResolveInfo().loadLabel(getPackageManager())); 217 encryptionWarningView.setText(text); 218 encryptionWarningView.setVisibility(View.VISIBLE); 219 } else { 220 encryptionWarningView.setVisibility(View.GONE); 221 } 222 223 TextView capabilitiesHeaderView = (TextView) content.findViewById( 224 R.id.capabilities_header); 225 capabilitiesHeaderView.setText(getString(R.string.capabilities_list_title, 226 info.getResolveInfo().loadLabel(getPackageManager()))); 227 228 LinearLayout capabilitiesView = (LinearLayout) content.findViewById(R.id.capabilities); 229 230 // This capability is implicit for all services. 231 View capabilityView = inflater.inflate( 232 com.android.internal.R.layout.app_permission_item_old, null); 233 234 ImageView imageView = (ImageView) capabilityView.findViewById( 235 com.android.internal.R.id.perm_icon); 236 imageView.setImageDrawable(getActivity().getDrawable( 237 com.android.internal.R.drawable.ic_text_dot)); 238 239 TextView labelView = (TextView) capabilityView.findViewById( 240 com.android.internal.R.id.permission_group); 241 labelView.setText(getString(R.string.capability_title_receiveAccessibilityEvents)); 242 243 TextView descriptionView = (TextView) capabilityView.findViewById( 244 com.android.internal.R.id.permission_list); 245 descriptionView.setText(getString(R.string.capability_desc_receiveAccessibilityEvents)); 246 247 List<AccessibilityServiceInfo.CapabilityInfo> capabilities = 248 info.getCapabilityInfos(); 249 250 capabilitiesView.addView(capabilityView); 251 252 // Service specific capabilities. 253 final int capabilityCount = capabilities.size(); 254 for (int i = 0; i < capabilityCount; i++) { 255 AccessibilityServiceInfo.CapabilityInfo capability = capabilities.get(i); 256 257 capabilityView = inflater.inflate( 258 com.android.internal.R.layout.app_permission_item_old, null); 259 260 imageView = (ImageView) capabilityView.findViewById( 261 com.android.internal.R.id.perm_icon); 262 imageView.setImageDrawable(getActivity().getDrawable( 263 com.android.internal.R.drawable.ic_text_dot)); 264 265 labelView = (TextView) capabilityView.findViewById( 266 com.android.internal.R.id.permission_group); 267 labelView.setText(getString(capability.titleResId)); 268 269 descriptionView = (TextView) capabilityView.findViewById( 270 com.android.internal.R.id.permission_list); 271 descriptionView.setText(getString(capability.descResId)); 272 273 capabilitiesView.addView(capabilityView); 274 } 275 276 return content; 277 } 278 279 @Override onActivityResult(int requestCode, int resultCode, Intent data)280 public void onActivityResult(int requestCode, int resultCode, Intent data) { 281 if (requestCode == ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION) { 282 if (resultCode == Activity.RESULT_OK) { 283 handleConfirmServiceEnabled(true); 284 // The user confirmed that they accept weaker encryption when 285 // enabling the accessibility service, so change encryption. 286 // Since we came here asynchronously, check encryption again. 287 if (isFullDiskEncrypted()) { 288 mLockPatternUtils.clearEncryptionPassword(); 289 Settings.Global.putInt(getContentResolver(), 290 Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT, 0); 291 } 292 } else { 293 handleConfirmServiceEnabled(false); 294 } 295 } 296 } 297 298 @Override onClick(DialogInterface dialog, int which)299 public void onClick(DialogInterface dialog, int which) { 300 final boolean checked; 301 switch (which) { 302 case DialogInterface.BUTTON_POSITIVE: 303 if (mShownDialogId == DIALOG_ID_ENABLE_WARNING) { 304 if (isFullDiskEncrypted()) { 305 String title = createConfirmCredentialReasonMessage(); 306 Intent intent = ConfirmDeviceCredentialActivity.createIntent(title, null); 307 startActivityForResult(intent, 308 ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION); 309 } else { 310 handleConfirmServiceEnabled(true); 311 } 312 } else { 313 handleConfirmServiceEnabled(false); 314 } 315 break; 316 case DialogInterface.BUTTON_NEGATIVE: 317 checked = (mShownDialogId == DIALOG_ID_DISABLE_WARNING); 318 handleConfirmServiceEnabled(checked); 319 break; 320 default: 321 throw new IllegalArgumentException(); 322 } 323 } 324 handleConfirmServiceEnabled(boolean confirmed)325 private void handleConfirmServiceEnabled(boolean confirmed) { 326 mSwitchBar.setCheckedInternal(confirmed); 327 getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, confirmed); 328 onPreferenceToggled(mPreferenceKey, confirmed); 329 } 330 createConfirmCredentialReasonMessage()331 private String createConfirmCredentialReasonMessage() { 332 int resId = R.string.enable_service_password_reason; 333 switch (mLockPatternUtils.getKeyguardStoredPasswordQuality(UserHandle.myUserId())) { 334 case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: { 335 resId = R.string.enable_service_pattern_reason; 336 } break; 337 case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: 338 case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: { 339 resId = R.string.enable_service_pin_reason; 340 } break; 341 } 342 return getString(resId, getAccessibilityServiceInfo().getResolveInfo() 343 .loadLabel(getPackageManager())); 344 } 345 346 @Override onInstallSwitchBarToggleSwitch()347 protected void onInstallSwitchBarToggleSwitch() { 348 super.onInstallSwitchBarToggleSwitch(); 349 mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() { 350 @Override 351 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) { 352 if (checked) { 353 mSwitchBar.setCheckedInternal(false); 354 getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, false); 355 showDialog(DIALOG_ID_ENABLE_WARNING); 356 } else { 357 mSwitchBar.setCheckedInternal(true); 358 getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, true); 359 showDialog(DIALOG_ID_DISABLE_WARNING); 360 } 361 return true; 362 } 363 }); 364 } 365 366 @Override onProcessArguments(Bundle arguments)367 protected void onProcessArguments(Bundle arguments) { 368 super.onProcessArguments(arguments); 369 // Settings title and intent. 370 String settingsTitle = arguments.getString(AccessibilitySettings.EXTRA_SETTINGS_TITLE); 371 String settingsComponentName = arguments.getString( 372 AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME); 373 if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) { 374 Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent( 375 ComponentName.unflattenFromString(settingsComponentName.toString())); 376 if (!getPackageManager().queryIntentActivities(settingsIntent, 0).isEmpty()) { 377 mSettingsTitle = settingsTitle; 378 mSettingsIntent = settingsIntent; 379 setHasOptionsMenu(true); 380 } 381 } 382 383 mComponentName = arguments.getParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME); 384 } 385 } 386