Updating the accessibility layer behavior to reflect the new model where accessibility no longer overrides strong encryption. Now enabling an accessibility service lowers the encryption level but the user can bump it up in settings if desired. bug:17881324 Change-Id: Iaf46cbabf1c19c193ea39b35add27aaa4ff509e4
392 lines
17 KiB
Java
392 lines
17 KiB
Java
/*
|
|
* Copyright (C) 2013 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.settings.accessibility;
|
|
|
|
import android.accessibilityservice.AccessibilityServiceInfo;
|
|
import android.app.Activity;
|
|
import android.app.AlertDialog;
|
|
import android.app.Dialog;
|
|
import android.app.admin.DevicePolicyManager;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.content.pm.ResolveInfo;
|
|
import android.net.Uri;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.provider.Settings;
|
|
import android.text.TextUtils;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.accessibility.AccessibilityManager;
|
|
import android.widget.ImageView;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.TextView;
|
|
|
|
import com.android.internal.widget.LockPatternUtils;
|
|
import com.android.settings.ConfirmDeviceCredentialActivity;
|
|
import com.android.settings.R;
|
|
import com.android.settings.widget.ToggleSwitch;
|
|
import com.android.settings.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
|
|
|
|
import java.util.Collections;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
public class ToggleAccessibilityServicePreferenceFragment
|
|
extends ToggleFeaturePreferenceFragment implements DialogInterface.OnClickListener {
|
|
|
|
private static final int DIALOG_ID_ENABLE_WARNING = 1;
|
|
private static final int DIALOG_ID_DISABLE_WARNING = 2;
|
|
|
|
public static final int ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION = 1;
|
|
|
|
private LockPatternUtils mLockPatternUtils;
|
|
|
|
private final SettingsContentObserver mSettingsContentObserver =
|
|
new SettingsContentObserver(new Handler()) {
|
|
@Override
|
|
public void onChange(boolean selfChange, Uri uri) {
|
|
String settingValue = Settings.Secure.getString(getContentResolver(),
|
|
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
|
|
final boolean enabled = settingValue.contains(mComponentName.flattenToString());
|
|
mSwitchBar.setCheckedInternal(enabled);
|
|
}
|
|
};
|
|
|
|
private ComponentName mComponentName;
|
|
|
|
private int mShownDialogId;
|
|
|
|
@Override
|
|
public void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
mLockPatternUtils = new LockPatternUtils(getActivity());
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
mSettingsContentObserver.register(getContentResolver());
|
|
super.onResume();
|
|
}
|
|
|
|
@Override
|
|
public void onPause() {
|
|
mSettingsContentObserver.unregister(getContentResolver());
|
|
super.onPause();
|
|
}
|
|
|
|
@Override
|
|
public void onPreferenceToggled(String preferenceKey, boolean enabled) {
|
|
// Parse the enabled services.
|
|
Set<ComponentName> enabledServices = AccessibilityUtils.getEnabledServicesFromSettings(
|
|
getActivity());
|
|
|
|
if (enabledServices == (Set<?>) Collections.emptySet()) {
|
|
enabledServices = new HashSet<ComponentName>();
|
|
}
|
|
|
|
// Determine enabled services and accessibility state.
|
|
ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey);
|
|
boolean accessibilityEnabled = false;
|
|
if (enabled) {
|
|
enabledServices.add(toggledService);
|
|
// Enabling at least one service enables accessibility.
|
|
accessibilityEnabled = true;
|
|
} else {
|
|
enabledServices.remove(toggledService);
|
|
// Check how many enabled and installed services are present.
|
|
Set<ComponentName> installedServices = AccessibilitySettings.sInstalledServices;
|
|
for (ComponentName enabledService : enabledServices) {
|
|
if (installedServices.contains(enabledService)) {
|
|
// Disabling the last service disables accessibility.
|
|
accessibilityEnabled = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the enabled services setting.
|
|
StringBuilder enabledServicesBuilder = new StringBuilder();
|
|
// Keep the enabled services even if they are not installed since we
|
|
// have no way to know whether the application restore process has
|
|
// completed. In general the system should be responsible for the
|
|
// clean up not settings.
|
|
for (ComponentName enabledService : enabledServices) {
|
|
enabledServicesBuilder.append(enabledService.flattenToString());
|
|
enabledServicesBuilder.append(
|
|
AccessibilitySettings.ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
|
|
}
|
|
final int enabledServicesBuilderLength = enabledServicesBuilder.length();
|
|
if (enabledServicesBuilderLength > 0) {
|
|
enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
|
|
}
|
|
Settings.Secure.putString(getContentResolver(),
|
|
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
|
|
enabledServicesBuilder.toString());
|
|
|
|
// Update accessibility enabled.
|
|
Settings.Secure.putInt(getContentResolver(),
|
|
Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0);
|
|
}
|
|
|
|
// IMPORTANT: Refresh the info since there are dynamically changing
|
|
// capabilities. For
|
|
// example, before JellyBean MR2 the user was granting the explore by touch
|
|
// one.
|
|
private AccessibilityServiceInfo getAccessibilityServiceInfo() {
|
|
List<AccessibilityServiceInfo> serviceInfos = AccessibilityManager.getInstance(
|
|
getActivity()).getInstalledAccessibilityServiceList();
|
|
final int serviceInfoCount = serviceInfos.size();
|
|
for (int i = 0; i < serviceInfoCount; i++) {
|
|
AccessibilityServiceInfo serviceInfo = serviceInfos.get(i);
|
|
ResolveInfo resolveInfo = serviceInfo.getResolveInfo();
|
|
if (mComponentName.getPackageName().equals(resolveInfo.serviceInfo.packageName)
|
|
&& mComponentName.getClassName().equals(resolveInfo.serviceInfo.name)) {
|
|
return serviceInfo;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Dialog onCreateDialog(int dialogId) {
|
|
switch (dialogId) {
|
|
case DIALOG_ID_ENABLE_WARNING: {
|
|
mShownDialogId = DIALOG_ID_ENABLE_WARNING;
|
|
AccessibilityServiceInfo info = getAccessibilityServiceInfo();
|
|
if (info == null) {
|
|
return null;
|
|
}
|
|
AlertDialog ad = new AlertDialog.Builder(getActivity())
|
|
.setTitle(getString(R.string.enable_service_title,
|
|
info.getResolveInfo().loadLabel(getPackageManager())))
|
|
.setView(createEnableDialogContentView(info))
|
|
.setCancelable(true)
|
|
.setPositiveButton(android.R.string.ok, this)
|
|
.setNegativeButton(android.R.string.cancel, this)
|
|
.create();
|
|
ad.create();
|
|
ad.getButton(AlertDialog.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
|
|
return ad;
|
|
}
|
|
case DIALOG_ID_DISABLE_WARNING: {
|
|
mShownDialogId = DIALOG_ID_DISABLE_WARNING;
|
|
AccessibilityServiceInfo info = getAccessibilityServiceInfo();
|
|
if (info == null) {
|
|
return null;
|
|
}
|
|
return new AlertDialog.Builder(getActivity())
|
|
.setTitle(getString(R.string.disable_service_title,
|
|
info.getResolveInfo().loadLabel(getPackageManager())))
|
|
.setMessage(getString(R.string.disable_service_message,
|
|
info.getResolveInfo().loadLabel(getPackageManager())))
|
|
.setCancelable(true)
|
|
.setPositiveButton(android.R.string.ok, this)
|
|
.setNegativeButton(android.R.string.cancel, this)
|
|
.create();
|
|
}
|
|
default: {
|
|
throw new IllegalArgumentException();
|
|
}
|
|
}
|
|
}
|
|
|
|
private View createEnableDialogContentView(AccessibilityServiceInfo info) {
|
|
LayoutInflater inflater = (LayoutInflater) getSystemService(
|
|
Context.LAYOUT_INFLATER_SERVICE);
|
|
|
|
View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content,
|
|
null);
|
|
|
|
TextView encryptionWarningView = (TextView) content.findViewById(
|
|
R.id.encryption_warning);
|
|
if (LockPatternUtils.isDeviceEncrypted()) {
|
|
String text = getString(R.string.enable_service_encryption_warning,
|
|
info.getResolveInfo().loadLabel(getPackageManager()));
|
|
encryptionWarningView.setText(text);
|
|
encryptionWarningView.setVisibility(View.VISIBLE);
|
|
} else {
|
|
encryptionWarningView.setVisibility(View.GONE);
|
|
}
|
|
|
|
TextView capabilitiesHeaderView = (TextView) content.findViewById(
|
|
R.id.capabilities_header);
|
|
capabilitiesHeaderView.setText(getString(R.string.capabilities_list_title,
|
|
info.getResolveInfo().loadLabel(getPackageManager())));
|
|
|
|
LinearLayout capabilitiesView = (LinearLayout) content.findViewById(R.id.capabilities);
|
|
|
|
// This capability is implicit for all services.
|
|
View capabilityView = inflater.inflate(
|
|
com.android.internal.R.layout.app_permission_item_old, null);
|
|
|
|
ImageView imageView = (ImageView) capabilityView.findViewById(
|
|
com.android.internal.R.id.perm_icon);
|
|
imageView.setImageDrawable(getResources().getDrawable(
|
|
com.android.internal.R.drawable.ic_text_dot));
|
|
|
|
TextView labelView = (TextView) capabilityView.findViewById(
|
|
com.android.internal.R.id.permission_group);
|
|
labelView.setText(getString(R.string.capability_title_receiveAccessibilityEvents));
|
|
|
|
TextView descriptionView = (TextView) capabilityView.findViewById(
|
|
com.android.internal.R.id.permission_list);
|
|
descriptionView.setText(getString(R.string.capability_desc_receiveAccessibilityEvents));
|
|
|
|
List<AccessibilityServiceInfo.CapabilityInfo> capabilities =
|
|
info.getCapabilityInfos();
|
|
|
|
capabilitiesView.addView(capabilityView);
|
|
|
|
// Service specific capabilities.
|
|
final int capabilityCount = capabilities.size();
|
|
for (int i = 0; i < capabilityCount; i++) {
|
|
AccessibilityServiceInfo.CapabilityInfo capability = capabilities.get(i);
|
|
|
|
capabilityView = inflater.inflate(
|
|
com.android.internal.R.layout.app_permission_item_old, null);
|
|
|
|
imageView = (ImageView) capabilityView.findViewById(
|
|
com.android.internal.R.id.perm_icon);
|
|
imageView.setImageDrawable(getResources().getDrawable(
|
|
com.android.internal.R.drawable.ic_text_dot));
|
|
|
|
labelView = (TextView) capabilityView.findViewById(
|
|
com.android.internal.R.id.permission_group);
|
|
labelView.setText(getString(capability.titleResId));
|
|
|
|
descriptionView = (TextView) capabilityView.findViewById(
|
|
com.android.internal.R.id.permission_list);
|
|
descriptionView.setText(getString(capability.descResId));
|
|
|
|
capabilitiesView.addView(capabilityView);
|
|
}
|
|
|
|
return content;
|
|
}
|
|
|
|
@Override
|
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
if (requestCode == ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION) {
|
|
if (resultCode == Activity.RESULT_OK) {
|
|
handleConfirmServiceEnabled(true);
|
|
// The user confirmed that they accept weaker encryption when
|
|
// enabling the accessibility service, so change encryption.
|
|
// Since we came here asynchronously, check encryption again.
|
|
if (LockPatternUtils.isDeviceEncrypted()) {
|
|
mLockPatternUtils.clearEncryptionPassword();
|
|
Settings.Global.putInt(getContentResolver(),
|
|
Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT, 0);
|
|
}
|
|
} else {
|
|
handleConfirmServiceEnabled(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
final boolean checked;
|
|
switch (which) {
|
|
case DialogInterface.BUTTON_POSITIVE:
|
|
if (mShownDialogId == DIALOG_ID_ENABLE_WARNING) {
|
|
if (LockPatternUtils.isDeviceEncrypted()) {
|
|
String title = createConfirmCredentialReasonMessage();
|
|
Intent intent = ConfirmDeviceCredentialActivity.createIntent(title, null);
|
|
startActivityForResult(intent,
|
|
ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION);
|
|
} else {
|
|
handleConfirmServiceEnabled(true);
|
|
}
|
|
} else {
|
|
handleConfirmServiceEnabled(false);
|
|
}
|
|
break;
|
|
case DialogInterface.BUTTON_NEGATIVE:
|
|
checked = (mShownDialogId == DIALOG_ID_DISABLE_WARNING);
|
|
handleConfirmServiceEnabled(checked);
|
|
break;
|
|
default:
|
|
throw new IllegalArgumentException();
|
|
}
|
|
}
|
|
|
|
private void handleConfirmServiceEnabled(boolean confirmed) {
|
|
mSwitchBar.setCheckedInternal(confirmed);
|
|
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, confirmed);
|
|
onPreferenceToggled(mPreferenceKey, confirmed);
|
|
}
|
|
|
|
private String createConfirmCredentialReasonMessage() {
|
|
int resId = R.string.enable_service_password_reason;
|
|
switch (mLockPatternUtils.getKeyguardStoredPasswordQuality()) {
|
|
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: {
|
|
resId = R.string.enable_service_pattern_reason;
|
|
} break;
|
|
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
|
|
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: {
|
|
resId = R.string.enable_service_pin_reason;
|
|
} break;
|
|
}
|
|
return getString(resId, getAccessibilityServiceInfo().getResolveInfo()
|
|
.loadLabel(getPackageManager()));
|
|
}
|
|
|
|
@Override
|
|
protected void onInstallSwitchBarToggleSwitch() {
|
|
super.onInstallSwitchBarToggleSwitch();
|
|
mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
|
|
@Override
|
|
public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
|
|
if (checked) {
|
|
mSwitchBar.setCheckedInternal(false);
|
|
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, false);
|
|
showDialog(DIALOG_ID_ENABLE_WARNING);
|
|
} else {
|
|
mSwitchBar.setCheckedInternal(true);
|
|
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, true);
|
|
showDialog(DIALOG_ID_DISABLE_WARNING);
|
|
}
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
protected void onProcessArguments(Bundle arguments) {
|
|
super.onProcessArguments(arguments);
|
|
// Settings title and intent.
|
|
String settingsTitle = arguments.getString(AccessibilitySettings.EXTRA_SETTINGS_TITLE);
|
|
String settingsComponentName = arguments.getString(
|
|
AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME);
|
|
if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) {
|
|
Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(
|
|
ComponentName.unflattenFromString(settingsComponentName.toString()));
|
|
if (!getPackageManager().queryIntentActivities(settingsIntent, 0).isEmpty()) {
|
|
mSettingsTitle = settingsTitle;
|
|
mSettingsIntent = settingsIntent;
|
|
setHasOptionsMenu(true);
|
|
}
|
|
}
|
|
|
|
mComponentName = arguments.getParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME);
|
|
}
|
|
}
|