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:
PETER LIANG
2020-04-17 12:19:58 +00:00
committed by Android (Google) Code Review
18 changed files with 1097 additions and 743 deletions

View File

@@ -78,21 +78,6 @@ public final class ShortcutConstants {
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.
*
@@ -108,30 +93,4 @@ public final class ShortcutConstants {
int LAUNCH = 0;
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;
}
}

View File

@@ -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)));
}
}

View File

@@ -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)));
}
}

View File

@@ -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.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.TargetType;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
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.accessibility.dialog.AccessibilityTargetHelper.createEnableDialogContentView;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getInstalledTargets;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
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.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
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.ViewGroup;
import android.view.Window;
import android.view.accessibility.AccessibilityManager;
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 java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
/**
* Activity used to display various targets related to accessibility service, accessibility
@@ -84,38 +47,11 @@ import java.util.Locale;
*/
public class AccessibilityShortcutChooserActivity extends Activity {
@ShortcutType
private static int sShortcutType;
@UserShortcutType
private int mShortcutUserType;
private final List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
private AlertDialog mAlertDialog;
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,
},
};
private int mShortcutType;
private final List<AccessibilityTarget> mTargets = new ArrayList<>();
private AlertDialog mMenuDialog;
private AlertDialog mPermissionDialog;
private ShortcutTargetAdapter mTargetAdapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -126,20 +62,18 @@ public class AccessibilityShortcutChooserActivity extends Activity {
requestWindowFeature(Window.FEATURE_NO_TITLE);
}
sShortcutType = getIntent().getIntExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE,
mShortcutType = getIntent().getIntExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE,
/* unexpectedShortcutType */ -1);
final boolean existInShortcutType = (sShortcutType == ACCESSIBILITY_BUTTON)
|| (sShortcutType == ACCESSIBILITY_SHORTCUT_KEY);
checkArgument(existInShortcutType, "Unexpected shortcut type: " + sShortcutType);
final boolean existInShortcutType = (mShortcutType == ACCESSIBILITY_BUTTON)
|| (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY);
checkArgument(existInShortcutType, "Unexpected shortcut type: " + mShortcutType);
mShortcutUserType = convertToUserType(sShortcutType);
mTargets.addAll(getServiceTargets(this, sShortcutType));
mTargets.addAll(getTargets(this, mShortcutType));
final String selectDialogTitle =
getString(R.string.accessibility_select_shortcut_menu_title);
mTargetAdapter = new TargetAdapter(mTargets);
mAlertDialog = new AlertDialog.Builder(this)
mTargetAdapter = new ShortcutTargetAdapter(mTargets);
mMenuDialog = new AlertDialog.Builder(this)
.setTitle(selectDialogTitle)
.setAdapter(mTargetAdapter, /* listener= */ null)
.setPositiveButton(
@@ -147,561 +81,55 @@ public class AccessibilityShortcutChooserActivity extends Activity {
/* listener= */ null)
.setOnDismissListener(dialog -> finish())
.create();
mAlertDialog.setOnShowListener(dialog -> updateDialogListeners());
mAlertDialog.show();
mMenuDialog.setOnShowListener(dialog -> updateDialogListeners());
mMenuDialog.show();
}
@Override
protected void onDestroy() {
mAlertDialog.dismiss();
mMenuDialog.dismiss();
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) {
final AccessibilityButtonTarget target = mTargets.get(position);
switch (target.getFragmentType()) {
case AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE:
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));
}
final AccessibilityTarget target = mTargets.get(position);
target.onSelected();
mMenuDialog.dismiss();
}
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)
&& !mCurrentCheckedTarget.isChecked()) {
mEnableDialog = new AlertDialog.Builder(this)
.setView(createEnableDialogContentView(this, mCurrentCheckedTarget,
this::onPermissionAllowButtonClicked,
this::onPermissionDenyButtonClicked))
if ((target instanceof AccessibilityServiceTarget) && !target.isShortcutEnabled()) {
mPermissionDialog = new AlertDialog.Builder(this)
.setView(createEnableDialogContentView(this,
(AccessibilityServiceTarget) target,
v -> {
mPermissionDialog.dismiss();
mTargetAdapter.notifyDataSetChanged();
},
v -> mPermissionDialog.dismiss()))
.create();
mEnableDialog.show();
mPermissionDialog.show();
return;
}
onTargetChecked(mCurrentCheckedTarget, !mCurrentCheckedTarget.isChecked());
}
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);
target.onCheckedChanged(!target.isShortcutEnabled());
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() {
mTargets.clear();
mTargets.addAll(getServiceTargets(this, sShortcutType));
mTargets.addAll(getTargets(this, mShortcutType));
if (mTargets.isEmpty()) {
mAlertDialog.dismiss();
mMenuDialog.dismiss();
return;
}
mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH);
mTargetAdapter.notifyDataSetChanged();
mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
getString(R.string.edit_accessibility_shortcut_menu_button));
updateDialogListeners();
@@ -709,11 +137,11 @@ public class AccessibilityShortcutChooserActivity extends Activity {
private void onEditButtonClicked() {
mTargets.clear();
mTargets.addAll(getInstalledServiceTargets(this));
mTargets.addAll(getInstalledTargets(this, mShortcutType));
mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.EDIT);
mTargetAdapter.notifyDataSetChanged();
mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
getString(R.string.done_accessibility_shortcut_menu_button));
updateDialogListeners();
@@ -724,79 +152,14 @@ public class AccessibilityShortcutChooserActivity extends Activity {
(mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT);
final int selectDialogTitleId = R.string.accessibility_select_shortcut_menu_title;
final int editDialogTitleId =
(sShortcutType == ACCESSIBILITY_BUTTON)
(mShortcutType == ACCESSIBILITY_BUTTON)
? R.string.accessibility_edit_shortcut_menu_button_title
: R.string.accessibility_edit_shortcut_menu_volume_title;
mAlertDialog.setTitle(getString(isEditMenuMode ? editDialogTitleId : selectDialogTitleId));
mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(
mMenuDialog.setTitle(getString(isEditMenuMode ? editDialogTitleId : selectDialogTitleId));
mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(
isEditMenuMode ? view -> onDoneButtonClicked() : view -> onEditButtonClicked());
mAlertDialog.getListView().setOnItemClickListener(
mMenuDialog.getListView().setOnItemClickListener(
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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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()));
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}

View File

@@ -15,6 +15,7 @@
*/
package com.android.internal.accessibility.util;
import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
@@ -27,9 +28,11 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.view.accessibility.AccessibilityManager;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
@@ -129,4 +132,27 @@ public final class AccessibilityUtils {
? AccessibilityFragmentType.INVISIBLE_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;
}
}

View File

@@ -25,8 +25,10 @@ import android.annotation.NonNull;
import android.content.Context;
import android.provider.Settings;
import android.text.TextUtils;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.ShortcutType;
import java.util.List;
import java.util.StringJoiner;
/**
@@ -96,29 +98,6 @@ public final class ShortcutUtils {
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.
*
@@ -148,6 +127,21 @@ public final class ShortcutUtils {
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.
*