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