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