Merge changes from topic "redesign shortcut chooser" into rvc-dev
* changes: Redesign for AccessibilityShortcutChooserActivity (3/n). Redesign for AccessibilityShortcutChooserActivity (2/n). Redesign for AccessibilityShortcutChooserActivity (1/n).
This commit is contained in:
@@ -78,21 +78,6 @@ public final class ShortcutConstants {
|
|||||||
int LAUNCH_ACTIVITY = 3;
|
int LAUNCH_ACTIVITY = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation for different shortcut target.
|
|
||||||
*/
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@IntDef({
|
|
||||||
TargetType.ACCESSIBILITY_SERVICE,
|
|
||||||
TargetType.ACCESSIBILITY_ACTIVITY,
|
|
||||||
TargetType.WHITE_LISTING,
|
|
||||||
})
|
|
||||||
public @interface TargetType {
|
|
||||||
int ACCESSIBILITY_SERVICE = 0;
|
|
||||||
int ACCESSIBILITY_ACTIVITY = 1;
|
|
||||||
int WHITE_LISTING = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation for different shortcut menu mode.
|
* Annotation for different shortcut menu mode.
|
||||||
*
|
*
|
||||||
@@ -108,30 +93,4 @@ public final class ShortcutConstants {
|
|||||||
int LAUNCH = 0;
|
int LAUNCH = 0;
|
||||||
int EDIT = 1;
|
int EDIT = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation for align the element index of white listing feature
|
|
||||||
* {@code WHITE_LISTING_FEATURES}.
|
|
||||||
*
|
|
||||||
* {@code COMPONENT_ID} is to get the service component name.
|
|
||||||
* {@code LABEL_ID} is to get the service label text.
|
|
||||||
* {@code ICON_ID} is to get the service icon.
|
|
||||||
* {@code FRAGMENT_TYPE} is to get the service fragment type.
|
|
||||||
* {@code SETTINGS_KEY} is to get the service settings key.
|
|
||||||
*/
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@IntDef({
|
|
||||||
WhiteListingFeatureElementIndex.COMPONENT_ID,
|
|
||||||
WhiteListingFeatureElementIndex.LABEL_ID,
|
|
||||||
WhiteListingFeatureElementIndex.ICON_ID,
|
|
||||||
WhiteListingFeatureElementIndex.FRAGMENT_TYPE,
|
|
||||||
WhiteListingFeatureElementIndex.SETTINGS_KEY,
|
|
||||||
})
|
|
||||||
public @interface WhiteListingFeatureElementIndex {
|
|
||||||
int COMPONENT_ID = 0;
|
|
||||||
int LABEL_ID = 1;
|
|
||||||
int ICON_ID = 2;
|
|
||||||
int FRAGMENT_TYPE = 3;
|
|
||||||
int SETTINGS_KEY = 4;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.internal.accessibility.dialog;
|
||||||
|
|
||||||
|
import static com.android.internal.accessibility.util.ShortcutUtils.convertToKey;
|
||||||
|
import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
|
||||||
|
import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
|
||||||
|
|
||||||
|
import android.accessibilityservice.AccessibilityShortcutInfo;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.accessibility.AccessibilityManager.ShortcutType;
|
||||||
|
|
||||||
|
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for creating accessibility activity target.
|
||||||
|
*/
|
||||||
|
class AccessibilityActivityTarget extends AccessibilityTarget {
|
||||||
|
|
||||||
|
AccessibilityActivityTarget(Context context, @ShortcutType int shortcutType,
|
||||||
|
@NonNull AccessibilityShortcutInfo shortcutInfo) {
|
||||||
|
super(context,
|
||||||
|
shortcutType,
|
||||||
|
AccessibilityFragmentType.LAUNCH_ACTIVITY,
|
||||||
|
isShortcutContained(context, shortcutType,
|
||||||
|
shortcutInfo.getComponentName().flattenToString()),
|
||||||
|
shortcutInfo.getComponentName().flattenToString(),
|
||||||
|
shortcutInfo.getActivityInfo().loadLabel(context.getPackageManager()),
|
||||||
|
shortcutInfo.getActivityInfo().loadIcon(context.getPackageManager()),
|
||||||
|
convertToKey(convertToUserType(shortcutType)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.internal.accessibility.dialog;
|
||||||
|
|
||||||
|
import static com.android.internal.accessibility.util.ShortcutUtils.convertToKey;
|
||||||
|
import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
|
||||||
|
import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
|
||||||
|
|
||||||
|
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.accessibility.AccessibilityManager.ShortcutType;
|
||||||
|
|
||||||
|
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for creating accessibility service target with various fragment types related to
|
||||||
|
* legacy type, invisible type and intuitive type.
|
||||||
|
*/
|
||||||
|
class AccessibilityServiceTarget extends AccessibilityTarget {
|
||||||
|
|
||||||
|
AccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
|
||||||
|
@AccessibilityFragmentType int fragmentType,
|
||||||
|
@NonNull AccessibilityServiceInfo serviceInfo) {
|
||||||
|
super(context,
|
||||||
|
shortcutType,
|
||||||
|
fragmentType,
|
||||||
|
isShortcutContained(context, shortcutType,
|
||||||
|
serviceInfo.getComponentName().flattenToString()),
|
||||||
|
serviceInfo.getComponentName().flattenToString(),
|
||||||
|
serviceInfo.getResolveInfo().loadLabel(context.getPackageManager()),
|
||||||
|
serviceInfo.getResolveInfo().loadIcon(context.getPackageManager()),
|
||||||
|
convertToKey(convertToUserType(shortcutType)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,64 +19,27 @@ import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTT
|
|||||||
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
|
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
|
||||||
import static android.view.accessibility.AccessibilityManager.ShortcutType;
|
import static android.view.accessibility.AccessibilityManager.ShortcutType;
|
||||||
|
|
||||||
import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
|
|
||||||
import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
|
|
||||||
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
|
|
||||||
import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
|
|
||||||
import static com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
|
import static com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
|
||||||
import static com.android.internal.accessibility.common.ShortcutConstants.TargetType;
|
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.createEnableDialogContentView;
|
||||||
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
|
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getInstalledTargets;
|
||||||
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
|
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
|
||||||
import static com.android.internal.accessibility.common.ShortcutConstants.WhiteListingFeatureElementIndex.COMPONENT_ID;
|
|
||||||
import static com.android.internal.accessibility.common.ShortcutConstants.WhiteListingFeatureElementIndex.FRAGMENT_TYPE;
|
|
||||||
import static com.android.internal.accessibility.common.ShortcutConstants.WhiteListingFeatureElementIndex.ICON_ID;
|
|
||||||
import static com.android.internal.accessibility.common.ShortcutConstants.WhiteListingFeatureElementIndex.LABEL_ID;
|
|
||||||
import static com.android.internal.accessibility.common.ShortcutConstants.WhiteListingFeatureElementIndex.SETTINGS_KEY;
|
|
||||||
import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
|
|
||||||
import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
|
|
||||||
import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
|
|
||||||
import static com.android.internal.accessibility.util.ShortcutUtils.hasValuesInSettings;
|
|
||||||
import static com.android.internal.accessibility.util.ShortcutUtils.optInValueToSettings;
|
|
||||||
import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
|
|
||||||
import static com.android.internal.util.Preconditions.checkArgument;
|
import static com.android.internal.util.Preconditions.checkArgument;
|
||||||
|
|
||||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
|
||||||
import android.accessibilityservice.AccessibilityShortcutInfo;
|
|
||||||
import android.annotation.NonNull;
|
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ActivityManager;
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.storage.StorageManager;
|
|
||||||
import android.provider.Settings;
|
|
||||||
import android.text.BidiFormatter;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.accessibility.AccessibilityManager;
|
import android.view.accessibility.AccessibilityManager;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.BaseAdapter;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.Switch;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.android.internal.R;
|
import com.android.internal.R;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity used to display various targets related to accessibility service, accessibility
|
* Activity used to display various targets related to accessibility service, accessibility
|
||||||
@@ -84,38 +47,11 @@ import java.util.Locale;
|
|||||||
*/
|
*/
|
||||||
public class AccessibilityShortcutChooserActivity extends Activity {
|
public class AccessibilityShortcutChooserActivity extends Activity {
|
||||||
@ShortcutType
|
@ShortcutType
|
||||||
private static int sShortcutType;
|
private int mShortcutType;
|
||||||
@UserShortcutType
|
private final List<AccessibilityTarget> mTargets = new ArrayList<>();
|
||||||
private int mShortcutUserType;
|
private AlertDialog mMenuDialog;
|
||||||
private final List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
|
private AlertDialog mPermissionDialog;
|
||||||
private AlertDialog mAlertDialog;
|
private ShortcutTargetAdapter mTargetAdapter;
|
||||||
private AlertDialog mEnableDialog;
|
|
||||||
private TargetAdapter mTargetAdapter;
|
|
||||||
private AccessibilityButtonTarget mCurrentCheckedTarget;
|
|
||||||
|
|
||||||
private static final String[][] WHITE_LISTING_FEATURES = {
|
|
||||||
{
|
|
||||||
COLOR_INVERSION_COMPONENT_NAME.flattenToString(),
|
|
||||||
String.valueOf(R.string.color_inversion_feature_name),
|
|
||||||
String.valueOf(R.drawable.ic_accessibility_color_inversion),
|
|
||||||
String.valueOf(AccessibilityFragmentType.TOGGLE),
|
|
||||||
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DALTONIZER_COMPONENT_NAME.flattenToString(),
|
|
||||||
String.valueOf(R.string.color_correction_feature_name),
|
|
||||||
String.valueOf(R.drawable.ic_accessibility_color_correction),
|
|
||||||
String.valueOf(AccessibilityFragmentType.TOGGLE),
|
|
||||||
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MAGNIFICATION_CONTROLLER_NAME,
|
|
||||||
String.valueOf(R.string.accessibility_magnification_chooser_text),
|
|
||||||
String.valueOf(R.drawable.ic_accessibility_magnification),
|
|
||||||
String.valueOf(AccessibilityFragmentType.INVISIBLE_TOGGLE),
|
|
||||||
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
@@ -126,20 +62,18 @@ public class AccessibilityShortcutChooserActivity extends Activity {
|
|||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
sShortcutType = getIntent().getIntExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE,
|
mShortcutType = getIntent().getIntExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE,
|
||||||
/* unexpectedShortcutType */ -1);
|
/* unexpectedShortcutType */ -1);
|
||||||
final boolean existInShortcutType = (sShortcutType == ACCESSIBILITY_BUTTON)
|
final boolean existInShortcutType = (mShortcutType == ACCESSIBILITY_BUTTON)
|
||||||
|| (sShortcutType == ACCESSIBILITY_SHORTCUT_KEY);
|
|| (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY);
|
||||||
checkArgument(existInShortcutType, "Unexpected shortcut type: " + sShortcutType);
|
checkArgument(existInShortcutType, "Unexpected shortcut type: " + mShortcutType);
|
||||||
|
|
||||||
mShortcutUserType = convertToUserType(sShortcutType);
|
mTargets.addAll(getTargets(this, mShortcutType));
|
||||||
|
|
||||||
mTargets.addAll(getServiceTargets(this, sShortcutType));
|
|
||||||
|
|
||||||
final String selectDialogTitle =
|
final String selectDialogTitle =
|
||||||
getString(R.string.accessibility_select_shortcut_menu_title);
|
getString(R.string.accessibility_select_shortcut_menu_title);
|
||||||
mTargetAdapter = new TargetAdapter(mTargets);
|
mTargetAdapter = new ShortcutTargetAdapter(mTargets);
|
||||||
mAlertDialog = new AlertDialog.Builder(this)
|
mMenuDialog = new AlertDialog.Builder(this)
|
||||||
.setTitle(selectDialogTitle)
|
.setTitle(selectDialogTitle)
|
||||||
.setAdapter(mTargetAdapter, /* listener= */ null)
|
.setAdapter(mTargetAdapter, /* listener= */ null)
|
||||||
.setPositiveButton(
|
.setPositiveButton(
|
||||||
@@ -147,561 +81,55 @@ public class AccessibilityShortcutChooserActivity extends Activity {
|
|||||||
/* listener= */ null)
|
/* listener= */ null)
|
||||||
.setOnDismissListener(dialog -> finish())
|
.setOnDismissListener(dialog -> finish())
|
||||||
.create();
|
.create();
|
||||||
mAlertDialog.setOnShowListener(dialog -> updateDialogListeners());
|
mMenuDialog.setOnShowListener(dialog -> updateDialogListeners());
|
||||||
mAlertDialog.show();
|
mMenuDialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
mAlertDialog.dismiss();
|
mMenuDialog.dismiss();
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<AccessibilityButtonTarget> getServiceTargets(@NonNull Context context,
|
|
||||||
@ShortcutType int shortcutType) {
|
|
||||||
final List<AccessibilityButtonTarget> targets = getInstalledServiceTargets(context);
|
|
||||||
final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
|
|
||||||
final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
|
|
||||||
targets.removeIf(target -> !requiredTargets.contains(target.getId()));
|
|
||||||
|
|
||||||
return targets;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<AccessibilityButtonTarget> getInstalledServiceTargets(
|
|
||||||
@NonNull Context context) {
|
|
||||||
final List<AccessibilityButtonTarget> targets = new ArrayList<>();
|
|
||||||
targets.addAll(getAccessibilityFilteredTargets(context));
|
|
||||||
targets.addAll(getWhiteListingServiceTargets(context));
|
|
||||||
|
|
||||||
return targets;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<AccessibilityButtonTarget> getAccessibilityFilteredTargets(
|
|
||||||
@NonNull Context context) {
|
|
||||||
final List<AccessibilityButtonTarget> serviceTargets =
|
|
||||||
getAccessibilityServiceTargets(context);
|
|
||||||
final List<AccessibilityButtonTarget> activityTargets =
|
|
||||||
getAccessibilityActivityTargets(context);
|
|
||||||
|
|
||||||
for (AccessibilityButtonTarget activityTarget : activityTargets) {
|
|
||||||
serviceTargets.removeIf(serviceTarget -> {
|
|
||||||
final ComponentName serviceComponentName =
|
|
||||||
ComponentName.unflattenFromString(serviceTarget.getId());
|
|
||||||
final ComponentName activityComponentName =
|
|
||||||
ComponentName.unflattenFromString(activityTarget.getId());
|
|
||||||
final boolean isSamePackageName = activityComponentName.getPackageName().equals(
|
|
||||||
serviceComponentName.getPackageName());
|
|
||||||
final boolean isSameLabel = activityTarget.getLabel().equals(
|
|
||||||
serviceTarget.getLabel());
|
|
||||||
|
|
||||||
return isSamePackageName && isSameLabel;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<AccessibilityButtonTarget> targets = new ArrayList<>();
|
|
||||||
targets.addAll(serviceTargets);
|
|
||||||
targets.addAll(activityTargets);
|
|
||||||
|
|
||||||
return targets;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<AccessibilityButtonTarget> getAccessibilityServiceTargets(
|
|
||||||
@NonNull Context context) {
|
|
||||||
final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
|
|
||||||
final List<AccessibilityServiceInfo> installedServices =
|
|
||||||
ams.getInstalledAccessibilityServiceList();
|
|
||||||
if (installedServices == null) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<AccessibilityButtonTarget> targets = new ArrayList<>(installedServices.size());
|
|
||||||
for (AccessibilityServiceInfo info : installedServices) {
|
|
||||||
final int targetSdk =
|
|
||||||
info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion;
|
|
||||||
final boolean hasRequestAccessibilityButtonFlag =
|
|
||||||
(info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
|
|
||||||
if ((targetSdk < Build.VERSION_CODES.R) && !hasRequestAccessibilityButtonFlag
|
|
||||||
&& (sShortcutType == ACCESSIBILITY_BUTTON)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
targets.add(new AccessibilityButtonTarget(context, info));
|
|
||||||
}
|
|
||||||
|
|
||||||
return targets;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<AccessibilityButtonTarget> getAccessibilityActivityTargets(
|
|
||||||
@NonNull Context context) {
|
|
||||||
final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
|
|
||||||
final List<AccessibilityShortcutInfo> installedServices =
|
|
||||||
ams.getInstalledAccessibilityShortcutListAsUser(context,
|
|
||||||
ActivityManager.getCurrentUser());
|
|
||||||
if (installedServices == null) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<AccessibilityButtonTarget> targets = new ArrayList<>(installedServices.size());
|
|
||||||
for (AccessibilityShortcutInfo info : installedServices) {
|
|
||||||
targets.add(new AccessibilityButtonTarget(context, info));
|
|
||||||
}
|
|
||||||
|
|
||||||
return targets;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<AccessibilityButtonTarget> getWhiteListingServiceTargets(
|
|
||||||
@NonNull Context context) {
|
|
||||||
final List<AccessibilityButtonTarget> targets = new ArrayList<>();
|
|
||||||
|
|
||||||
for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
|
|
||||||
final AccessibilityButtonTarget target = new AccessibilityButtonTarget(
|
|
||||||
context,
|
|
||||||
WHITE_LISTING_FEATURES[i][COMPONENT_ID],
|
|
||||||
Integer.parseInt(WHITE_LISTING_FEATURES[i][LABEL_ID]),
|
|
||||||
Integer.parseInt(WHITE_LISTING_FEATURES[i][ICON_ID]),
|
|
||||||
Integer.parseInt(WHITE_LISTING_FEATURES[i][FRAGMENT_TYPE]));
|
|
||||||
targets.add(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
return targets;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isWhiteListingServiceEnabled(@NonNull Context context,
|
|
||||||
AccessibilityButtonTarget target) {
|
|
||||||
|
|
||||||
for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
|
|
||||||
if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(target.getId())) {
|
|
||||||
return Settings.Secure.getInt(context.getContentResolver(),
|
|
||||||
WHITE_LISTING_FEATURES[i][SETTINGS_KEY],
|
|
||||||
/* settingsValueOff */ 0) == /* settingsValueOn */ 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isWhiteListingService(String componentId) {
|
|
||||||
for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
|
|
||||||
if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(componentId)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setWhiteListingServiceEnabled(String componentId, int settingsValue) {
|
|
||||||
for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
|
|
||||||
if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(componentId)) {
|
|
||||||
Settings.Secure.putInt(getContentResolver(),
|
|
||||||
WHITE_LISTING_FEATURES[i][SETTINGS_KEY], settingsValue);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setServiceEnabled(String componentId, boolean enabled) {
|
|
||||||
if (isWhiteListingService(componentId)) {
|
|
||||||
setWhiteListingServiceEnabled(componentId,
|
|
||||||
enabled ? /* settingsValueOn */ 1 : /* settingsValueOff */ 0);
|
|
||||||
} else {
|
|
||||||
final ComponentName componentName = ComponentName.unflattenFromString(componentId);
|
|
||||||
setAccessibilityServiceState(this, componentName, enabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ViewHolder {
|
|
||||||
View mItemView;
|
|
||||||
CheckBox mCheckBox;
|
|
||||||
ImageView mIconView;
|
|
||||||
TextView mLabelView;
|
|
||||||
Switch mSwitchItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class TargetAdapter extends BaseAdapter {
|
|
||||||
@ShortcutMenuMode
|
|
||||||
private int mShortcutMenuMode = ShortcutMenuMode.LAUNCH;
|
|
||||||
private List<AccessibilityButtonTarget> mButtonTargets;
|
|
||||||
|
|
||||||
TargetAdapter(List<AccessibilityButtonTarget> targets) {
|
|
||||||
this.mButtonTargets = targets;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setShortcutMenuMode(@ShortcutMenuMode int shortcutMenuMode) {
|
|
||||||
mShortcutMenuMode = shortcutMenuMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ShortcutMenuMode
|
|
||||||
int getShortcutMenuMode() {
|
|
||||||
return mShortcutMenuMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCount() {
|
|
||||||
return mButtonTargets.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getItem(int position) {
|
|
||||||
return mButtonTargets.get(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getItemId(int position) {
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
|
||||||
final Context context = parent.getContext();
|
|
||||||
ViewHolder holder;
|
|
||||||
if (convertView == null) {
|
|
||||||
convertView = LayoutInflater.from(context).inflate(
|
|
||||||
R.layout.accessibility_shortcut_chooser_item, parent, /* attachToRoot= */
|
|
||||||
false);
|
|
||||||
holder = new ViewHolder();
|
|
||||||
holder.mItemView = convertView;
|
|
||||||
holder.mCheckBox = convertView.findViewById(
|
|
||||||
R.id.accessibility_shortcut_target_checkbox);
|
|
||||||
holder.mIconView = convertView.findViewById(
|
|
||||||
R.id.accessibility_shortcut_target_icon);
|
|
||||||
holder.mLabelView = convertView.findViewById(
|
|
||||||
R.id.accessibility_shortcut_target_label);
|
|
||||||
holder.mSwitchItem = convertView.findViewById(
|
|
||||||
R.id.accessibility_shortcut_target_switch_item);
|
|
||||||
convertView.setTag(holder);
|
|
||||||
} else {
|
|
||||||
holder = (ViewHolder) convertView.getTag();
|
|
||||||
}
|
|
||||||
|
|
||||||
final AccessibilityButtonTarget target = mButtonTargets.get(position);
|
|
||||||
updateActionItem(context, holder, target);
|
|
||||||
|
|
||||||
return convertView;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateActionItem(@NonNull Context context,
|
|
||||||
@NonNull ViewHolder holder, AccessibilityButtonTarget target) {
|
|
||||||
|
|
||||||
switch (target.getFragmentType()) {
|
|
||||||
case AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE:
|
|
||||||
updateVolumeShortcutToggleTargetActionItemVisibility(holder, target);
|
|
||||||
break;
|
|
||||||
case AccessibilityFragmentType.INVISIBLE_TOGGLE:
|
|
||||||
updateInvisibleToggleTargetActionItemVisibility(holder, target);
|
|
||||||
break;
|
|
||||||
case AccessibilityFragmentType.TOGGLE:
|
|
||||||
updateToggleTargetActionItemVisibility(context, holder, target);
|
|
||||||
break;
|
|
||||||
case AccessibilityFragmentType.LAUNCH_ACTIVITY:
|
|
||||||
updateLaunchActivityTargetActionItemVisibility(holder, target);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Unexpected fragment type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateVolumeShortcutToggleTargetActionItemVisibility(
|
|
||||||
@NonNull ViewHolder holder, AccessibilityButtonTarget target) {
|
|
||||||
final boolean isLaunchMenuMode = (mShortcutMenuMode == ShortcutMenuMode.LAUNCH);
|
|
||||||
|
|
||||||
holder.mCheckBox.setChecked(!isLaunchMenuMode && target.isChecked());
|
|
||||||
holder.mCheckBox.setVisibility(isLaunchMenuMode ? View.GONE : View.VISIBLE);
|
|
||||||
holder.mIconView.setImageDrawable(target.getDrawable());
|
|
||||||
holder.mLabelView.setText(target.getLabel());
|
|
||||||
holder.mSwitchItem.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateInvisibleToggleTargetActionItemVisibility(@NonNull ViewHolder holder,
|
|
||||||
AccessibilityButtonTarget target) {
|
|
||||||
final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
|
|
||||||
|
|
||||||
holder.mCheckBox.setChecked(isEditMenuMode && target.isChecked());
|
|
||||||
holder.mCheckBox.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
|
|
||||||
holder.mIconView.setImageDrawable(target.getDrawable());
|
|
||||||
holder.mLabelView.setText(target.getLabel());
|
|
||||||
holder.mSwitchItem.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateToggleTargetActionItemVisibility(@NonNull Context context,
|
|
||||||
@NonNull ViewHolder holder, AccessibilityButtonTarget target) {
|
|
||||||
final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
|
|
||||||
final boolean isServiceEnabled = isWhiteListingService(target.getId())
|
|
||||||
? isWhiteListingServiceEnabled(context, target)
|
|
||||||
: isAccessibilityServiceEnabled(context, target);
|
|
||||||
|
|
||||||
holder.mCheckBox.setChecked(isEditMenuMode && target.isChecked());
|
|
||||||
holder.mCheckBox.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
|
|
||||||
holder.mIconView.setImageDrawable(target.getDrawable());
|
|
||||||
holder.mLabelView.setText(target.getLabel());
|
|
||||||
holder.mSwitchItem.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE);
|
|
||||||
holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateLaunchActivityTargetActionItemVisibility(@NonNull ViewHolder holder,
|
|
||||||
AccessibilityButtonTarget target) {
|
|
||||||
final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
|
|
||||||
|
|
||||||
holder.mCheckBox.setChecked(isEditMenuMode && target.isChecked());
|
|
||||||
holder.mCheckBox.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
|
|
||||||
holder.mIconView.setImageDrawable(target.getDrawable());
|
|
||||||
holder.mLabelView.setText(target.getLabel());
|
|
||||||
holder.mSwitchItem.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class AccessibilityButtonTarget {
|
|
||||||
private String mId;
|
|
||||||
@TargetType
|
|
||||||
private int mType;
|
|
||||||
private boolean mChecked;
|
|
||||||
private CharSequence mLabel;
|
|
||||||
private Drawable mDrawable;
|
|
||||||
@AccessibilityFragmentType
|
|
||||||
private int mFragmentType;
|
|
||||||
|
|
||||||
AccessibilityButtonTarget(@NonNull Context context,
|
|
||||||
@NonNull AccessibilityServiceInfo serviceInfo) {
|
|
||||||
this.mId = serviceInfo.getComponentName().flattenToString();
|
|
||||||
this.mType = TargetType.ACCESSIBILITY_SERVICE;
|
|
||||||
this.mChecked = isTargetShortcutUsed(context, mId);
|
|
||||||
this.mLabel = serviceInfo.getResolveInfo().loadLabel(context.getPackageManager());
|
|
||||||
this.mDrawable = serviceInfo.getResolveInfo().loadIcon(context.getPackageManager());
|
|
||||||
this.mFragmentType = getAccessibilityServiceFragmentType(serviceInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
AccessibilityButtonTarget(@NonNull Context context,
|
|
||||||
@NonNull AccessibilityShortcutInfo shortcutInfo) {
|
|
||||||
this.mId = shortcutInfo.getComponentName().flattenToString();
|
|
||||||
this.mType = TargetType.ACCESSIBILITY_ACTIVITY;
|
|
||||||
this.mChecked = isTargetShortcutUsed(context, mId);
|
|
||||||
this.mLabel = shortcutInfo.getActivityInfo().loadLabel(context.getPackageManager());
|
|
||||||
this.mDrawable = shortcutInfo.getActivityInfo().loadIcon(context.getPackageManager());
|
|
||||||
this.mFragmentType = AccessibilityFragmentType.LAUNCH_ACTIVITY;
|
|
||||||
}
|
|
||||||
|
|
||||||
AccessibilityButtonTarget(Context context, @NonNull String id, int labelResId,
|
|
||||||
int iconRes, @AccessibilityFragmentType int fragmentType) {
|
|
||||||
this.mId = id;
|
|
||||||
this.mType = TargetType.WHITE_LISTING;
|
|
||||||
this.mChecked = isTargetShortcutUsed(context, mId);
|
|
||||||
this.mLabel = context.getText(labelResId);
|
|
||||||
this.mDrawable = context.getDrawable(iconRes);
|
|
||||||
this.mFragmentType = fragmentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setChecked(boolean checked) {
|
|
||||||
mChecked = checked;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() {
|
|
||||||
return mId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getType() {
|
|
||||||
return mType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isChecked() {
|
|
||||||
return mChecked;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CharSequence getLabel() {
|
|
||||||
return mLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Drawable getDrawable() {
|
|
||||||
return mDrawable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getFragmentType() {
|
|
||||||
return mFragmentType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isAccessibilityServiceEnabled(@NonNull Context context,
|
|
||||||
AccessibilityButtonTarget target) {
|
|
||||||
final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
|
|
||||||
final List<AccessibilityServiceInfo> enabledServices =
|
|
||||||
ams.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
|
|
||||||
|
|
||||||
for (AccessibilityServiceInfo info : enabledServices) {
|
|
||||||
final String id = info.getComponentName().flattenToString();
|
|
||||||
if (id.equals(target.getId())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) {
|
private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
final AccessibilityButtonTarget target = mTargets.get(position);
|
final AccessibilityTarget target = mTargets.get(position);
|
||||||
switch (target.getFragmentType()) {
|
target.onSelected();
|
||||||
case AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE:
|
mMenuDialog.dismiss();
|
||||||
onVolumeShortcutToggleTargetSelected(target);
|
|
||||||
break;
|
|
||||||
case AccessibilityFragmentType.INVISIBLE_TOGGLE:
|
|
||||||
onInvisibleToggleTargetSelected(target);
|
|
||||||
break;
|
|
||||||
case AccessibilityFragmentType.TOGGLE:
|
|
||||||
onToggleTargetSelected(target);
|
|
||||||
break;
|
|
||||||
case AccessibilityFragmentType.LAUNCH_ACTIVITY:
|
|
||||||
onLaunchActivityTargetSelected(target);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Unexpected fragment type");
|
|
||||||
}
|
|
||||||
|
|
||||||
mAlertDialog.dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onVolumeShortcutToggleTargetSelected(AccessibilityButtonTarget target) {
|
|
||||||
if (sShortcutType == ACCESSIBILITY_BUTTON) {
|
|
||||||
final AccessibilityManager ams = getSystemService(AccessibilityManager.class);
|
|
||||||
ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
|
|
||||||
} else if (sShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
|
|
||||||
switchServiceState(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onInvisibleToggleTargetSelected(AccessibilityButtonTarget target) {
|
|
||||||
final AccessibilityManager ams = getSystemService(AccessibilityManager.class);
|
|
||||||
if (sShortcutType == ACCESSIBILITY_BUTTON) {
|
|
||||||
ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
|
|
||||||
} else if (sShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
|
|
||||||
ams.performAccessibilityShortcut(target.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onToggleTargetSelected(AccessibilityButtonTarget target) {
|
|
||||||
switchServiceState(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onLaunchActivityTargetSelected(AccessibilityButtonTarget target) {
|
|
||||||
final AccessibilityManager ams = getSystemService(AccessibilityManager.class);
|
|
||||||
if (sShortcutType == ACCESSIBILITY_BUTTON) {
|
|
||||||
ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
|
|
||||||
} else if (sShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
|
|
||||||
ams.performAccessibilityShortcut(target.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void switchServiceState(AccessibilityButtonTarget target) {
|
|
||||||
final ComponentName componentName =
|
|
||||||
ComponentName.unflattenFromString(target.getId());
|
|
||||||
final String componentId = componentName.flattenToString();
|
|
||||||
|
|
||||||
if (isWhiteListingService(componentId)) {
|
|
||||||
setWhiteListingServiceEnabled(componentId,
|
|
||||||
isWhiteListingServiceEnabled(this, target)
|
|
||||||
? /* settingsValueOff */ 0
|
|
||||||
: /* settingsValueOn */ 1);
|
|
||||||
} else {
|
|
||||||
setAccessibilityServiceState(this, componentName,
|
|
||||||
/* enabled= */!isAccessibilityServiceEnabled(this, target));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onTargetChecked(AdapterView<?> parent, View view, int position, long id) {
|
private void onTargetChecked(AdapterView<?> parent, View view, int position, long id) {
|
||||||
mCurrentCheckedTarget = mTargets.get(position);
|
final AccessibilityTarget target = mTargets.get(position);
|
||||||
|
|
||||||
if ((mCurrentCheckedTarget.getType() == TargetType.ACCESSIBILITY_SERVICE)
|
if ((target instanceof AccessibilityServiceTarget) && !target.isShortcutEnabled()) {
|
||||||
&& !mCurrentCheckedTarget.isChecked()) {
|
mPermissionDialog = new AlertDialog.Builder(this)
|
||||||
mEnableDialog = new AlertDialog.Builder(this)
|
.setView(createEnableDialogContentView(this,
|
||||||
.setView(createEnableDialogContentView(this, mCurrentCheckedTarget,
|
(AccessibilityServiceTarget) target,
|
||||||
this::onPermissionAllowButtonClicked,
|
v -> {
|
||||||
this::onPermissionDenyButtonClicked))
|
mPermissionDialog.dismiss();
|
||||||
|
mTargetAdapter.notifyDataSetChanged();
|
||||||
|
},
|
||||||
|
v -> mPermissionDialog.dismiss()))
|
||||||
.create();
|
.create();
|
||||||
mEnableDialog.show();
|
mPermissionDialog.show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onTargetChecked(mCurrentCheckedTarget, !mCurrentCheckedTarget.isChecked());
|
target.onCheckedChanged(!target.isShortcutEnabled());
|
||||||
}
|
|
||||||
|
|
||||||
private void onTargetChecked(AccessibilityButtonTarget target, boolean checked) {
|
|
||||||
switch (target.getFragmentType()) {
|
|
||||||
case AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE:
|
|
||||||
onVolumeShortcutToggleTargetChecked(checked);
|
|
||||||
break;
|
|
||||||
case AccessibilityFragmentType.INVISIBLE_TOGGLE:
|
|
||||||
onInvisibleToggleTargetChecked(checked);
|
|
||||||
break;
|
|
||||||
case AccessibilityFragmentType.TOGGLE:
|
|
||||||
onToggleTargetChecked(checked);
|
|
||||||
break;
|
|
||||||
case AccessibilityFragmentType.LAUNCH_ACTIVITY:
|
|
||||||
onLaunchActivityTargetChecked(checked);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Unexpected fragment type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onVolumeShortcutToggleTargetChecked(boolean checked) {
|
|
||||||
if (sShortcutType == ACCESSIBILITY_BUTTON) {
|
|
||||||
setServiceEnabled(mCurrentCheckedTarget.getId(), checked);
|
|
||||||
if (!checked) {
|
|
||||||
optOutValueFromSettings(this, HARDWARE, mCurrentCheckedTarget.getId());
|
|
||||||
final String warningText =
|
|
||||||
getString(R.string.accessibility_uncheck_legacy_item_warning,
|
|
||||||
mCurrentCheckedTarget.getLabel());
|
|
||||||
Toast.makeText(this, warningText, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
} else if (sShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
|
|
||||||
updateValueToSettings(mCurrentCheckedTarget.getId(), checked);
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("Unexpected shortcut type");
|
|
||||||
}
|
|
||||||
|
|
||||||
mCurrentCheckedTarget.setChecked(checked);
|
|
||||||
mTargetAdapter.notifyDataSetChanged();
|
mTargetAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onInvisibleToggleTargetChecked(boolean checked) {
|
|
||||||
final int shortcutTypes = UserShortcutType.SOFTWARE | HARDWARE;
|
|
||||||
if (!hasValuesInSettings(this, shortcutTypes, mCurrentCheckedTarget.getId())) {
|
|
||||||
setServiceEnabled(mCurrentCheckedTarget.getId(), checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateValueToSettings(mCurrentCheckedTarget.getId(), checked);
|
|
||||||
mCurrentCheckedTarget.setChecked(checked);
|
|
||||||
mTargetAdapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onToggleTargetChecked(boolean checked) {
|
|
||||||
updateValueToSettings(mCurrentCheckedTarget.getId(), checked);
|
|
||||||
mCurrentCheckedTarget.setChecked(checked);
|
|
||||||
mTargetAdapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onLaunchActivityTargetChecked(boolean checked) {
|
|
||||||
updateValueToSettings(mCurrentCheckedTarget.getId(), checked);
|
|
||||||
mCurrentCheckedTarget.setChecked(checked);
|
|
||||||
mTargetAdapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateValueToSettings(String componentId, boolean checked) {
|
|
||||||
if (checked) {
|
|
||||||
optInValueToSettings(this, mShortcutUserType, componentId);
|
|
||||||
} else {
|
|
||||||
optOutValueFromSettings(this, mShortcutUserType, componentId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onDoneButtonClicked() {
|
private void onDoneButtonClicked() {
|
||||||
mTargets.clear();
|
mTargets.clear();
|
||||||
mTargets.addAll(getServiceTargets(this, sShortcutType));
|
mTargets.addAll(getTargets(this, mShortcutType));
|
||||||
if (mTargets.isEmpty()) {
|
if (mTargets.isEmpty()) {
|
||||||
mAlertDialog.dismiss();
|
mMenuDialog.dismiss();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH);
|
mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH);
|
||||||
mTargetAdapter.notifyDataSetChanged();
|
mTargetAdapter.notifyDataSetChanged();
|
||||||
|
|
||||||
mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
|
mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
|
||||||
getString(R.string.edit_accessibility_shortcut_menu_button));
|
getString(R.string.edit_accessibility_shortcut_menu_button));
|
||||||
|
|
||||||
updateDialogListeners();
|
updateDialogListeners();
|
||||||
@@ -709,11 +137,11 @@ public class AccessibilityShortcutChooserActivity extends Activity {
|
|||||||
|
|
||||||
private void onEditButtonClicked() {
|
private void onEditButtonClicked() {
|
||||||
mTargets.clear();
|
mTargets.clear();
|
||||||
mTargets.addAll(getInstalledServiceTargets(this));
|
mTargets.addAll(getInstalledTargets(this, mShortcutType));
|
||||||
mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.EDIT);
|
mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.EDIT);
|
||||||
mTargetAdapter.notifyDataSetChanged();
|
mTargetAdapter.notifyDataSetChanged();
|
||||||
|
|
||||||
mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
|
mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
|
||||||
getString(R.string.done_accessibility_shortcut_menu_button));
|
getString(R.string.done_accessibility_shortcut_menu_button));
|
||||||
|
|
||||||
updateDialogListeners();
|
updateDialogListeners();
|
||||||
@@ -724,79 +152,14 @@ public class AccessibilityShortcutChooserActivity extends Activity {
|
|||||||
(mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT);
|
(mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT);
|
||||||
final int selectDialogTitleId = R.string.accessibility_select_shortcut_menu_title;
|
final int selectDialogTitleId = R.string.accessibility_select_shortcut_menu_title;
|
||||||
final int editDialogTitleId =
|
final int editDialogTitleId =
|
||||||
(sShortcutType == ACCESSIBILITY_BUTTON)
|
(mShortcutType == ACCESSIBILITY_BUTTON)
|
||||||
? R.string.accessibility_edit_shortcut_menu_button_title
|
? R.string.accessibility_edit_shortcut_menu_button_title
|
||||||
: R.string.accessibility_edit_shortcut_menu_volume_title;
|
: R.string.accessibility_edit_shortcut_menu_volume_title;
|
||||||
|
|
||||||
mAlertDialog.setTitle(getString(isEditMenuMode ? editDialogTitleId : selectDialogTitleId));
|
mMenuDialog.setTitle(getString(isEditMenuMode ? editDialogTitleId : selectDialogTitleId));
|
||||||
mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(
|
mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(
|
||||||
isEditMenuMode ? view -> onDoneButtonClicked() : view -> onEditButtonClicked());
|
isEditMenuMode ? view -> onDoneButtonClicked() : view -> onEditButtonClicked());
|
||||||
mAlertDialog.getListView().setOnItemClickListener(
|
mMenuDialog.getListView().setOnItemClickListener(
|
||||||
isEditMenuMode ? this::onTargetChecked : this::onTargetSelected);
|
isEditMenuMode ? this::onTargetChecked : this::onTargetSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isTargetShortcutUsed(@NonNull Context context, String id) {
|
|
||||||
final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
|
|
||||||
final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(sShortcutType);
|
|
||||||
return requiredTargets.contains(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onPermissionAllowButtonClicked(View view) {
|
|
||||||
if (mCurrentCheckedTarget.getFragmentType()
|
|
||||||
!= AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE) {
|
|
||||||
updateValueToSettings(mCurrentCheckedTarget.getId(), /* checked= */ true);
|
|
||||||
}
|
|
||||||
onTargetChecked(mCurrentCheckedTarget, /* checked= */ true);
|
|
||||||
mEnableDialog.dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onPermissionDenyButtonClicked(View view) {
|
|
||||||
mEnableDialog.dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static View createEnableDialogContentView(Context context,
|
|
||||||
AccessibilityButtonTarget target, View.OnClickListener allowListener,
|
|
||||||
View.OnClickListener denyListener) {
|
|
||||||
final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
|
|
||||||
Context.LAYOUT_INFLATER_SERVICE);
|
|
||||||
|
|
||||||
final View content = inflater.inflate(
|
|
||||||
R.layout.accessibility_enable_service_encryption_warning, /* root= */ null);
|
|
||||||
|
|
||||||
final TextView encryptionWarningView = (TextView) content.findViewById(
|
|
||||||
R.id.accessibility_encryption_warning);
|
|
||||||
if (StorageManager.isNonDefaultBlockEncrypted()) {
|
|
||||||
final String text = context.getString(
|
|
||||||
R.string.accessibility_enable_service_encryption_warning,
|
|
||||||
getServiceName(context, target.getLabel()));
|
|
||||||
encryptionWarningView.setText(text);
|
|
||||||
encryptionWarningView.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
encryptionWarningView.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
final ImageView permissionDialogIcon = content.findViewById(
|
|
||||||
R.id.accessibility_permissionDialog_icon);
|
|
||||||
permissionDialogIcon.setImageDrawable(target.getDrawable());
|
|
||||||
|
|
||||||
final TextView permissionDialogTitle = content.findViewById(
|
|
||||||
R.id.accessibility_permissionDialog_title);
|
|
||||||
permissionDialogTitle.setText(context.getString(R.string.accessibility_enable_service_title,
|
|
||||||
getServiceName(context, target.getLabel())));
|
|
||||||
|
|
||||||
final Button permissionAllowButton = content.findViewById(
|
|
||||||
R.id.accessibility_permission_enable_allow_button);
|
|
||||||
final Button permissionDenyButton = content.findViewById(
|
|
||||||
R.id.accessibility_permission_enable_deny_button);
|
|
||||||
permissionAllowButton.setOnClickListener(allowListener);
|
|
||||||
permissionDenyButton.setOnClickListener(denyListener);
|
|
||||||
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets the service name and bidi wrap it to protect from bidi side effects.
|
|
||||||
private static CharSequence getServiceName(Context context, CharSequence label) {
|
|
||||||
final Locale locale = context.getResources().getConfiguration().getLocales().get(0);
|
|
||||||
return BidiFormatter.getInstance(locale).unicodeWrap(label);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.internal.accessibility.dialog;
|
||||||
|
|
||||||
|
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
|
||||||
|
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
|
||||||
|
|
||||||
|
import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
|
||||||
|
import static com.android.internal.accessibility.util.ShortcutUtils.optInValueToSettings;
|
||||||
|
import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.accessibility.AccessibilityManager;
|
||||||
|
import android.view.accessibility.AccessibilityManager.ShortcutType;
|
||||||
|
|
||||||
|
import com.android.internal.accessibility.common.ShortcutConstants;
|
||||||
|
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
|
||||||
|
import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for creating various target related to accessibility service,
|
||||||
|
* accessibility activity, and white listing feature.
|
||||||
|
*/
|
||||||
|
abstract class AccessibilityTarget implements TargetOperations, OnTargetSelectedListener,
|
||||||
|
OnTargetCheckedChangeListener {
|
||||||
|
private Context mContext;
|
||||||
|
@ShortcutType
|
||||||
|
private int mShortcutType;
|
||||||
|
@AccessibilityFragmentType
|
||||||
|
private int mFragmentType;
|
||||||
|
private boolean mShortcutEnabled;
|
||||||
|
private String mId;
|
||||||
|
private CharSequence mLabel;
|
||||||
|
private Drawable mIcon;
|
||||||
|
private String mKey;
|
||||||
|
|
||||||
|
AccessibilityTarget(Context context, @ShortcutType int shortcutType,
|
||||||
|
@AccessibilityFragmentType int fragmentType, boolean isShortcutSwitched, String id,
|
||||||
|
CharSequence label, Drawable icon, String key) {
|
||||||
|
mContext = context;
|
||||||
|
mShortcutType = shortcutType;
|
||||||
|
mFragmentType = fragmentType;
|
||||||
|
mShortcutEnabled = isShortcutSwitched;
|
||||||
|
mId = id;
|
||||||
|
mLabel = label;
|
||||||
|
mIcon = icon;
|
||||||
|
mKey = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateActionItem(@NonNull ViewHolder holder,
|
||||||
|
@ShortcutConstants.ShortcutMenuMode int shortcutMenuMode) {
|
||||||
|
final boolean isEditMenuMode =
|
||||||
|
shortcutMenuMode == ShortcutConstants.ShortcutMenuMode.EDIT;
|
||||||
|
|
||||||
|
holder.mCheckBoxView.setChecked(isEditMenuMode && isShortcutEnabled());
|
||||||
|
holder.mCheckBoxView.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
|
||||||
|
holder.mIconView.setImageDrawable(getIcon());
|
||||||
|
holder.mLabelView.setText(getLabel());
|
||||||
|
holder.mSwitchItem.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSelected() {
|
||||||
|
final AccessibilityManager am =
|
||||||
|
getContext().getSystemService(AccessibilityManager.class);
|
||||||
|
switch (getShortcutType()) {
|
||||||
|
case ACCESSIBILITY_BUTTON:
|
||||||
|
am.notifyAccessibilityButtonClicked(getContext().getDisplayId(), getId());
|
||||||
|
return;
|
||||||
|
case ACCESSIBILITY_SHORTCUT_KEY:
|
||||||
|
am.performAccessibilityShortcut(getId());
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unexpected shortcut type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(boolean isChecked) {
|
||||||
|
setShortcutEnabled(isChecked);
|
||||||
|
if (isChecked) {
|
||||||
|
optInValueToSettings(getContext(), convertToUserType(getShortcutType()), getId());
|
||||||
|
} else {
|
||||||
|
optOutValueFromSettings(getContext(), convertToUserType(getShortcutType()), getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShortcutEnabled(boolean enabled) {
|
||||||
|
mShortcutEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context getContext() {
|
||||||
|
return mContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @ShortcutType int getShortcutType() {
|
||||||
|
return mShortcutType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @AccessibilityFragmentType int getFragmentType() {
|
||||||
|
return mFragmentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isShortcutEnabled() {
|
||||||
|
return mShortcutEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return mId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence getLabel() {
|
||||||
|
return mLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Drawable getIcon() {
|
||||||
|
return mIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return mKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.internal.accessibility.dialog;
|
||||||
|
|
||||||
|
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
|
||||||
|
|
||||||
|
import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
|
||||||
|
import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
|
||||||
|
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
|
||||||
|
import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
|
||||||
|
import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
|
||||||
|
|
||||||
|
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||||
|
import android.accessibilityservice.AccessibilityShortcutInfo;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.app.ActivityManager;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.storage.StorageManager;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.text.BidiFormatter;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.accessibility.AccessibilityManager;
|
||||||
|
import android.view.accessibility.AccessibilityManager.ShortcutType;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.android.internal.R;
|
||||||
|
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of utilities for accessibility target.
|
||||||
|
*/
|
||||||
|
final class AccessibilityTargetHelper {
|
||||||
|
private AccessibilityTargetHelper() {}
|
||||||
|
|
||||||
|
static List<AccessibilityTarget> getTargets(Context context,
|
||||||
|
@ShortcutType int shortcutType) {
|
||||||
|
final List<AccessibilityTarget> targets = getInstalledTargets(context, shortcutType);
|
||||||
|
final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
|
||||||
|
final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
|
||||||
|
targets.removeIf(target -> !requiredTargets.contains(target.getId()));
|
||||||
|
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<AccessibilityTarget> getInstalledTargets(Context context,
|
||||||
|
@ShortcutType int shortcutType) {
|
||||||
|
final List<AccessibilityTarget> targets = new ArrayList<>();
|
||||||
|
targets.addAll(getAccessibilityFilteredTargets(context, shortcutType));
|
||||||
|
targets.addAll(getWhiteListingFeatureTargets(context, shortcutType));
|
||||||
|
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<AccessibilityTarget> getAccessibilityFilteredTargets(Context context,
|
||||||
|
@ShortcutType int shortcutType) {
|
||||||
|
final List<AccessibilityTarget> serviceTargets =
|
||||||
|
getAccessibilityServiceTargets(context, shortcutType);
|
||||||
|
final List<AccessibilityTarget> activityTargets =
|
||||||
|
getAccessibilityActivityTargets(context, shortcutType);
|
||||||
|
|
||||||
|
for (AccessibilityTarget activityTarget : activityTargets) {
|
||||||
|
serviceTargets.removeIf(
|
||||||
|
serviceTarget -> arePackageNameAndLabelTheSame(serviceTarget, activityTarget));
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<AccessibilityTarget> targets = new ArrayList<>();
|
||||||
|
targets.addAll(serviceTargets);
|
||||||
|
targets.addAll(activityTargets);
|
||||||
|
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean arePackageNameAndLabelTheSame(@NonNull AccessibilityTarget serviceTarget,
|
||||||
|
@NonNull AccessibilityTarget activityTarget) {
|
||||||
|
final ComponentName serviceComponentName =
|
||||||
|
ComponentName.unflattenFromString(serviceTarget.getId());
|
||||||
|
final ComponentName activityComponentName =
|
||||||
|
ComponentName.unflattenFromString(activityTarget.getId());
|
||||||
|
final boolean isSamePackageName = activityComponentName.getPackageName().equals(
|
||||||
|
serviceComponentName.getPackageName());
|
||||||
|
final boolean isSameLabel = activityTarget.getLabel().equals(
|
||||||
|
serviceTarget.getLabel());
|
||||||
|
|
||||||
|
return isSamePackageName && isSameLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<AccessibilityTarget> getAccessibilityServiceTargets(Context context,
|
||||||
|
@ShortcutType int shortcutType) {
|
||||||
|
final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
|
||||||
|
final List<AccessibilityServiceInfo> installedServices =
|
||||||
|
ams.getInstalledAccessibilityServiceList();
|
||||||
|
if (installedServices == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<AccessibilityTarget> targets = new ArrayList<>(installedServices.size());
|
||||||
|
for (AccessibilityServiceInfo info : installedServices) {
|
||||||
|
final int targetSdk =
|
||||||
|
info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion;
|
||||||
|
final boolean hasRequestAccessibilityButtonFlag =
|
||||||
|
(info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
|
||||||
|
if ((targetSdk <= Build.VERSION_CODES.Q) && !hasRequestAccessibilityButtonFlag
|
||||||
|
&& (shortcutType == ACCESSIBILITY_BUTTON)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
targets.add(createAccessibilityServiceTarget(context, shortcutType, info));
|
||||||
|
}
|
||||||
|
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<AccessibilityTarget> getAccessibilityActivityTargets(Context context,
|
||||||
|
@ShortcutType int shortcutType) {
|
||||||
|
final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
|
||||||
|
final List<AccessibilityShortcutInfo> installedServices =
|
||||||
|
ams.getInstalledAccessibilityShortcutListAsUser(context,
|
||||||
|
ActivityManager.getCurrentUser());
|
||||||
|
if (installedServices == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<AccessibilityTarget> targets = new ArrayList<>(installedServices.size());
|
||||||
|
for (AccessibilityShortcutInfo info : installedServices) {
|
||||||
|
targets.add(new AccessibilityActivityTarget(context, shortcutType, info));
|
||||||
|
}
|
||||||
|
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<AccessibilityTarget> getWhiteListingFeatureTargets(Context context,
|
||||||
|
@ShortcutType int shortcutType) {
|
||||||
|
final List<AccessibilityTarget> targets = new ArrayList<>();
|
||||||
|
|
||||||
|
final InvisibleToggleWhiteListingFeatureTarget magnification =
|
||||||
|
new InvisibleToggleWhiteListingFeatureTarget(context,
|
||||||
|
shortcutType,
|
||||||
|
isShortcutContained(context, shortcutType, MAGNIFICATION_CONTROLLER_NAME),
|
||||||
|
MAGNIFICATION_CONTROLLER_NAME,
|
||||||
|
context.getString(R.string.accessibility_magnification_chooser_text),
|
||||||
|
context.getDrawable(R.drawable.ic_accessibility_magnification),
|
||||||
|
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
|
||||||
|
|
||||||
|
final ToggleWhiteListingFeatureTarget daltonizer =
|
||||||
|
new ToggleWhiteListingFeatureTarget(context,
|
||||||
|
shortcutType,
|
||||||
|
isShortcutContained(context, shortcutType,
|
||||||
|
DALTONIZER_COMPONENT_NAME.flattenToString()),
|
||||||
|
DALTONIZER_COMPONENT_NAME.flattenToString(),
|
||||||
|
context.getString(R.string.color_correction_feature_name),
|
||||||
|
context.getDrawable(R.drawable.ic_accessibility_color_correction),
|
||||||
|
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED);
|
||||||
|
|
||||||
|
final ToggleWhiteListingFeatureTarget colorInversion =
|
||||||
|
new ToggleWhiteListingFeatureTarget(context,
|
||||||
|
shortcutType,
|
||||||
|
isShortcutContained(context, shortcutType,
|
||||||
|
COLOR_INVERSION_COMPONENT_NAME.flattenToString()),
|
||||||
|
COLOR_INVERSION_COMPONENT_NAME.flattenToString(),
|
||||||
|
context.getString(R.string.color_inversion_feature_name),
|
||||||
|
context.getDrawable(R.drawable.ic_accessibility_color_inversion),
|
||||||
|
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
|
||||||
|
|
||||||
|
targets.add(magnification);
|
||||||
|
targets.add(daltonizer);
|
||||||
|
targets.add(colorInversion);
|
||||||
|
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AccessibilityTarget createAccessibilityServiceTarget(Context context,
|
||||||
|
@ShortcutType int shortcutType, @NonNull AccessibilityServiceInfo info) {
|
||||||
|
switch (getAccessibilityServiceFragmentType(info)) {
|
||||||
|
case AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE:
|
||||||
|
return new VolumeShortcutToggleAccessibilityServiceTarget(context, shortcutType,
|
||||||
|
info);
|
||||||
|
case AccessibilityFragmentType.INVISIBLE_TOGGLE:
|
||||||
|
return new InvisibleToggleAccessibilityServiceTarget(context, shortcutType, info);
|
||||||
|
case AccessibilityFragmentType.TOGGLE:
|
||||||
|
return new ToggleAccessibilityServiceTarget(context, shortcutType, info);
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unexpected fragment type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static View createEnableDialogContentView(Context context,
|
||||||
|
AccessibilityServiceTarget target, View.OnClickListener allowListener,
|
||||||
|
View.OnClickListener denyListener) {
|
||||||
|
final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
|
||||||
|
Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
|
||||||
|
final View content = inflater.inflate(
|
||||||
|
R.layout.accessibility_enable_service_encryption_warning, /* root= */ null);
|
||||||
|
|
||||||
|
final TextView encryptionWarningView = (TextView) content.findViewById(
|
||||||
|
R.id.accessibility_encryption_warning);
|
||||||
|
if (StorageManager.isNonDefaultBlockEncrypted()) {
|
||||||
|
final String text = context.getString(
|
||||||
|
R.string.accessibility_enable_service_encryption_warning,
|
||||||
|
getServiceName(context, target.getLabel()));
|
||||||
|
encryptionWarningView.setText(text);
|
||||||
|
encryptionWarningView.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
encryptionWarningView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ImageView dialogIcon = content.findViewById(
|
||||||
|
R.id.accessibility_permissionDialog_icon);
|
||||||
|
dialogIcon.setImageDrawable(target.getIcon());
|
||||||
|
|
||||||
|
final TextView dialogTitle = content.findViewById(
|
||||||
|
R.id.accessibility_permissionDialog_title);
|
||||||
|
dialogTitle.setText(context.getString(R.string.accessibility_enable_service_title,
|
||||||
|
getServiceName(context, target.getLabel())));
|
||||||
|
|
||||||
|
final Button allowButton = content.findViewById(
|
||||||
|
R.id.accessibility_permission_enable_allow_button);
|
||||||
|
final Button denyButton = content.findViewById(
|
||||||
|
R.id.accessibility_permission_enable_deny_button);
|
||||||
|
allowButton.setOnClickListener((view) -> {
|
||||||
|
target.onCheckedChanged(/* isChecked= */ true);
|
||||||
|
allowListener.onClick(view);
|
||||||
|
});
|
||||||
|
denyButton.setOnClickListener((view) -> {
|
||||||
|
target.onCheckedChanged(/* isChecked= */ false);
|
||||||
|
denyListener.onClick(view);
|
||||||
|
});
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the service name and bidi wrap it to protect from bidi side effects.
|
||||||
|
private static CharSequence getServiceName(Context context, CharSequence label) {
|
||||||
|
final Locale locale = context.getResources().getConfiguration().getLocales().get(0);
|
||||||
|
return BidiFormatter.getInstance(locale).unicodeWrap(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.internal.accessibility.dialog;
|
||||||
|
|
||||||
|
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
|
||||||
|
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
|
||||||
|
|
||||||
|
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
|
||||||
|
import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
|
||||||
|
import static com.android.internal.accessibility.util.ShortcutUtils.isComponentIdExistingInSettings;
|
||||||
|
|
||||||
|
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.accessibility.AccessibilityManager.ShortcutType;
|
||||||
|
|
||||||
|
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension for {@link AccessibilityServiceTarget} with
|
||||||
|
* {@link AccessibilityFragmentType#INVISIBLE_TOGGLE} type.
|
||||||
|
*/
|
||||||
|
class InvisibleToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
|
||||||
|
|
||||||
|
InvisibleToggleAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
|
||||||
|
@NonNull AccessibilityServiceInfo serviceInfo) {
|
||||||
|
super(context,
|
||||||
|
shortcutType,
|
||||||
|
AccessibilityFragmentType.INVISIBLE_TOGGLE,
|
||||||
|
serviceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(boolean isChecked) {
|
||||||
|
final ComponentName componentName = ComponentName.unflattenFromString(getId());
|
||||||
|
|
||||||
|
if (!isComponentIdExistingInOtherShortcut()) {
|
||||||
|
setAccessibilityServiceState(getContext(), componentName, isChecked);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onCheckedChanged(isChecked);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isComponentIdExistingInOtherShortcut() {
|
||||||
|
switch (getShortcutType()) {
|
||||||
|
case ACCESSIBILITY_BUTTON:
|
||||||
|
return isComponentIdExistingInSettings(getContext(), UserShortcutType.HARDWARE,
|
||||||
|
getId());
|
||||||
|
case ACCESSIBILITY_SHORTCUT_KEY:
|
||||||
|
return isComponentIdExistingInSettings(getContext(), UserShortcutType.SOFTWARE,
|
||||||
|
getId());
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unexpected shortcut type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.internal.accessibility.dialog;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.view.accessibility.AccessibilityManager.ShortcutType;
|
||||||
|
|
||||||
|
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension for {@link AccessibilityTarget} with {@link AccessibilityFragmentType#INVISIBLE_TOGGLE}
|
||||||
|
* type.
|
||||||
|
*/
|
||||||
|
class InvisibleToggleWhiteListingFeatureTarget extends AccessibilityTarget {
|
||||||
|
|
||||||
|
InvisibleToggleWhiteListingFeatureTarget(Context context, @ShortcutType int shortcutType,
|
||||||
|
boolean isShortcutSwitched, String id, CharSequence label, Drawable icon, String key) {
|
||||||
|
super(context, shortcutType, AccessibilityFragmentType.INVISIBLE_TOGGLE,
|
||||||
|
isShortcutSwitched, id, label, icon, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.internal.accessibility.dialog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface definition for a callback to be invoked when the checked state
|
||||||
|
* of a accessibility target changed.
|
||||||
|
*/
|
||||||
|
interface OnTargetCheckedChangeListener {
|
||||||
|
/**
|
||||||
|
* Called when the checked state of a accessibility target has changed.
|
||||||
|
*
|
||||||
|
* @param isChecked The new checked state of accessibility target.
|
||||||
|
*/
|
||||||
|
void onCheckedChanged(boolean isChecked);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.internal.accessibility.dialog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface definition for a callback to be invoked when a accessibility target is selected.
|
||||||
|
*/
|
||||||
|
interface OnTargetSelectedListener {
|
||||||
|
/**
|
||||||
|
* Called when a accessibility target has been selected.
|
||||||
|
*/
|
||||||
|
void onSelected();
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.internal.accessibility.dialog;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.android.internal.R;
|
||||||
|
import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension for {@link TargetAdapter} and used for AccessibilityShortcutChooserActivity.
|
||||||
|
*/
|
||||||
|
class ShortcutTargetAdapter extends TargetAdapter {
|
||||||
|
@ShortcutMenuMode
|
||||||
|
private int mShortcutMenuMode = ShortcutMenuMode.LAUNCH;
|
||||||
|
private final List<AccessibilityTarget> mTargets;
|
||||||
|
|
||||||
|
ShortcutTargetAdapter(@NonNull List<AccessibilityTarget> targets) {
|
||||||
|
mTargets = targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return mTargets.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getItem(int position) {
|
||||||
|
return mTargets.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
final Context context = parent.getContext();
|
||||||
|
ViewHolder holder;
|
||||||
|
if (convertView == null) {
|
||||||
|
convertView = LayoutInflater.from(context).inflate(
|
||||||
|
R.layout.accessibility_shortcut_chooser_item, parent, /* attachToRoot= */
|
||||||
|
false);
|
||||||
|
holder = new ViewHolder();
|
||||||
|
holder.mCheckBoxView = convertView.findViewById(
|
||||||
|
R.id.accessibility_shortcut_target_checkbox);
|
||||||
|
holder.mIconView = convertView.findViewById(R.id.accessibility_shortcut_target_icon);
|
||||||
|
holder.mLabelView = convertView.findViewById(
|
||||||
|
R.id.accessibility_shortcut_target_label);
|
||||||
|
holder.mSwitchItem = convertView.findViewById(
|
||||||
|
R.id.accessibility_shortcut_target_switch_item);
|
||||||
|
convertView.setTag(holder);
|
||||||
|
} else {
|
||||||
|
holder = (ViewHolder) convertView.getTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
final AccessibilityTarget target = mTargets.get(position);
|
||||||
|
target.updateActionItem(holder, mShortcutMenuMode);
|
||||||
|
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setShortcutMenuMode(@ShortcutMenuMode int shortcutMenuMode) {
|
||||||
|
mShortcutMenuMode = shortcutMenuMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ShortcutMenuMode
|
||||||
|
int getShortcutMenuMode() {
|
||||||
|
return mShortcutMenuMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.internal.accessibility.dialog;
|
||||||
|
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.Switch;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for creating target adapter for chooser activity.
|
||||||
|
*/
|
||||||
|
abstract class TargetAdapter extends BaseAdapter {
|
||||||
|
static class ViewHolder{
|
||||||
|
CheckBox mCheckBoxView;
|
||||||
|
ImageView mIconView;
|
||||||
|
TextView mLabelView;
|
||||||
|
Switch mSwitchItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.internal.accessibility.dialog;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
|
||||||
|
import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface definition for operations with a accessibility target that was invoked.
|
||||||
|
*/
|
||||||
|
interface TargetOperations {
|
||||||
|
/**
|
||||||
|
* Called when a accessibility target has been invoked and notified to update latest status.
|
||||||
|
*/
|
||||||
|
void updateActionItem(@NonNull ViewHolder holder,
|
||||||
|
@ShortcutMenuMode int shortcutMenuMode);
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.internal.accessibility.dialog;
|
||||||
|
|
||||||
|
import static com.android.internal.accessibility.util.AccessibilityUtils.isAccessibilityServiceEnabled;
|
||||||
|
|
||||||
|
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.accessibility.AccessibilityManager.ShortcutType;
|
||||||
|
|
||||||
|
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
|
||||||
|
import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
|
||||||
|
import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension for {@link AccessibilityServiceTarget} with {@link AccessibilityFragmentType#TOGGLE}
|
||||||
|
* type.
|
||||||
|
*/
|
||||||
|
class ToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
|
||||||
|
|
||||||
|
ToggleAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
|
||||||
|
@NonNull AccessibilityServiceInfo serviceInfo) {
|
||||||
|
super(context,
|
||||||
|
shortcutType,
|
||||||
|
AccessibilityFragmentType.TOGGLE,
|
||||||
|
serviceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateActionItem(@NonNull ViewHolder holder,
|
||||||
|
@ShortcutMenuMode int shortcutMenuMode) {
|
||||||
|
super.updateActionItem(holder, shortcutMenuMode);
|
||||||
|
|
||||||
|
final boolean isEditMenuMode =
|
||||||
|
shortcutMenuMode == ShortcutMenuMode.EDIT;
|
||||||
|
holder.mSwitchItem.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE);
|
||||||
|
holder.mSwitchItem.setChecked(isAccessibilityServiceEnabled(getContext(), getId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.internal.accessibility.dialog;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.accessibility.AccessibilityManager.ShortcutType;
|
||||||
|
|
||||||
|
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
|
||||||
|
import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
|
||||||
|
import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension for {@link AccessibilityTarget} with {@link AccessibilityFragmentType#TOGGLE}
|
||||||
|
* type.
|
||||||
|
*/
|
||||||
|
class ToggleWhiteListingFeatureTarget extends AccessibilityTarget {
|
||||||
|
|
||||||
|
ToggleWhiteListingFeatureTarget(Context context, @ShortcutType int shortcutType,
|
||||||
|
boolean isShortcutSwitched, String id, CharSequence label, Drawable icon, String key) {
|
||||||
|
super(context, shortcutType, AccessibilityFragmentType.TOGGLE,
|
||||||
|
isShortcutSwitched, id, label, icon, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateActionItem(@NonNull ViewHolder holder,
|
||||||
|
@ShortcutMenuMode int shortcutMenuMode) {
|
||||||
|
super.updateActionItem(holder, shortcutMenuMode);
|
||||||
|
|
||||||
|
final boolean isEditMenuMode =
|
||||||
|
shortcutMenuMode == ShortcutMenuMode.EDIT;
|
||||||
|
holder.mSwitchItem.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE);
|
||||||
|
holder.mSwitchItem.setChecked(isFeatureEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isFeatureEnabled() {
|
||||||
|
return Settings.Secure.getInt(getContext().getContentResolver(),
|
||||||
|
getKey(), /* settingsValueOff */ 0) == /* settingsValueOn */ 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.internal.accessibility.dialog;
|
||||||
|
|
||||||
|
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
|
||||||
|
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
|
||||||
|
|
||||||
|
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
|
||||||
|
import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
|
||||||
|
import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
|
||||||
|
|
||||||
|
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.accessibility.AccessibilityManager.ShortcutType;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.android.internal.R;
|
||||||
|
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension for {@link AccessibilityServiceTarget} with
|
||||||
|
* {@link AccessibilityFragmentType#VOLUME_SHORTCUT_TOGGLE} type.
|
||||||
|
*/
|
||||||
|
class VolumeShortcutToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
|
||||||
|
|
||||||
|
VolumeShortcutToggleAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
|
||||||
|
@NonNull AccessibilityServiceInfo serviceInfo) {
|
||||||
|
super(context,
|
||||||
|
shortcutType,
|
||||||
|
AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE,
|
||||||
|
serviceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(boolean isChecked) {
|
||||||
|
switch (getShortcutType()) {
|
||||||
|
case ACCESSIBILITY_BUTTON:
|
||||||
|
onCheckedFromAccessibilityButton(isChecked);
|
||||||
|
return;
|
||||||
|
case ACCESSIBILITY_SHORTCUT_KEY:
|
||||||
|
super.onCheckedChanged(isChecked);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unexpected shortcut type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCheckedFromAccessibilityButton(boolean isChecked) {
|
||||||
|
setShortcutEnabled(isChecked);
|
||||||
|
final ComponentName componentName = ComponentName.unflattenFromString(getId());
|
||||||
|
setAccessibilityServiceState(getContext(), componentName, isChecked);
|
||||||
|
|
||||||
|
if (!isChecked) {
|
||||||
|
optOutValueFromSettings(getContext(), UserShortcutType.HARDWARE, getId());
|
||||||
|
|
||||||
|
final String warningText =
|
||||||
|
getContext().getString(R.string.accessibility_uncheck_legacy_item_warning,
|
||||||
|
getLabel());
|
||||||
|
Toast.makeText(getContext(), warningText, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.internal.accessibility.util;
|
package com.android.internal.accessibility.util;
|
||||||
|
|
||||||
import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
|
import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
|
||||||
import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
|
import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
|
||||||
|
|
||||||
@@ -27,9 +28,11 @@ import android.os.UserHandle;
|
|||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
|
import android.view.accessibility.AccessibilityManager;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,4 +132,27 @@ public final class AccessibilityUtils {
|
|||||||
? AccessibilityFragmentType.INVISIBLE_TOGGLE
|
? AccessibilityFragmentType.INVISIBLE_TOGGLE
|
||||||
: AccessibilityFragmentType.TOGGLE;
|
: AccessibilityFragmentType.TOGGLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if a {@code componentId} service is enabled.
|
||||||
|
*
|
||||||
|
* @param context The current context.
|
||||||
|
* @param componentId The component id that need to be checked.
|
||||||
|
* @return {@code true} if a {@code componentId} service is enabled.
|
||||||
|
*/
|
||||||
|
public static boolean isAccessibilityServiceEnabled(Context context,
|
||||||
|
@NonNull String componentId) {
|
||||||
|
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
|
||||||
|
final List<AccessibilityServiceInfo> enabledServices =
|
||||||
|
am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
|
||||||
|
|
||||||
|
for (AccessibilityServiceInfo info : enabledServices) {
|
||||||
|
final String id = info.getComponentName().flattenToString();
|
||||||
|
if (id.equals(componentId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ import android.annotation.NonNull;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.view.accessibility.AccessibilityManager;
|
||||||
import android.view.accessibility.AccessibilityManager.ShortcutType;
|
import android.view.accessibility.AccessibilityManager.ShortcutType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.StringJoiner;
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,29 +98,6 @@ public final class ShortcutUtils {
|
|||||||
Settings.Secure.putString(context.getContentResolver(), targetsKey, joiner.toString());
|
Settings.Secure.putString(context.getContentResolver(), targetsKey, joiner.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns if component id existed in one of {@link UserShortcutType} string from Settings.
|
|
||||||
*
|
|
||||||
* @param context The current context.
|
|
||||||
* @param shortcutTypes A combination of {@link UserShortcutType}.
|
|
||||||
* @param componentId The component id that need to be checked existed in Settings.
|
|
||||||
* @return {@code true} if component id existed in Settings.
|
|
||||||
*/
|
|
||||||
public static boolean hasValuesInSettings(Context context, @UserShortcutType int shortcutTypes,
|
|
||||||
@NonNull String componentId) {
|
|
||||||
boolean exist = false;
|
|
||||||
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
|
|
||||||
exist = isComponentIdExistingInSettings(context, UserShortcutType.SOFTWARE,
|
|
||||||
componentId);
|
|
||||||
}
|
|
||||||
if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
|
|
||||||
exist |= isComponentIdExistingInSettings(context, UserShortcutType.HARDWARE,
|
|
||||||
componentId);
|
|
||||||
}
|
|
||||||
return exist;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if component id existed in Settings.
|
* Returns if component id existed in Settings.
|
||||||
*
|
*
|
||||||
@@ -148,6 +127,21 @@ public final class ShortcutUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if a {@code shortcutType} shortcut contains {@code componentId}.
|
||||||
|
*
|
||||||
|
* @param context The current context.
|
||||||
|
* @param shortcutType The preferred shortcut type user selected.
|
||||||
|
* @param componentId The component id that need to be checked.
|
||||||
|
* @return {@code true} if a component id is contained.
|
||||||
|
*/
|
||||||
|
public static boolean isShortcutContained(Context context, @ShortcutType int shortcutType,
|
||||||
|
@NonNull String componentId) {
|
||||||
|
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
|
||||||
|
final List<String> requiredTargets = am.getAccessibilityShortcutTargets(shortcutType);
|
||||||
|
return requiredTargets.contains(componentId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts {@link UserShortcutType} to {@link Settings.Secure} key.
|
* Converts {@link UserShortcutType} to {@link Settings.Secure} key.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user