From fbad548bf003b8d9c780259b44b93141c05f49ce Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Wed, 8 Apr 2020 19:32:53 +0800 Subject: [PATCH 1/3] Redesign for AccessibilityShortcutChooserActivity (1/n). Analyzes the common attributes and functions in AccessibilityShortcutChooserActivity in advance and separate them into respective new component target related to Accessibility service, Accessibility activity or white listing feature. Bug: 147655054 Test: manual test Change-Id: I123d30cc4d2ada97eda6010e75cfca70e9fa4503 --- .../dialog/AccessibilityActivityTarget.java | 47 ++++++ .../dialog/AccessibilityServiceTarget.java | 49 ++++++ .../dialog/AccessibilityTarget.java | 141 ++++++++++++++++++ ...sibleToggleAccessibilityServiceTarget.java | 71 +++++++++ ...isibleToggleWhiteListingFeatureTarget.java | 36 +++++ .../dialog/OnTargetCheckedChangeListener.java | 30 ++++ .../dialog/OnTargetSelectedListener.java | 27 ++++ .../accessibility/dialog/TargetAdapter.java | 35 +++++ .../dialog/TargetOperations.java | 33 ++++ .../ToggleAccessibilityServiceTarget.java | 55 +++++++ .../ToggleWhiteListingFeatureTarget.java | 57 +++++++ ...rtcutToggleAccessibilityServiceTarget.java | 78 ++++++++++ .../util/AccessibilityUtils.java | 26 ++++ .../accessibility/util/ShortcutUtils.java | 17 +++ 14 files changed, 702 insertions(+) create mode 100644 core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java create mode 100644 core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java create mode 100644 core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java create mode 100644 core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java create mode 100644 core/java/com/android/internal/accessibility/dialog/InvisibleToggleWhiteListingFeatureTarget.java create mode 100644 core/java/com/android/internal/accessibility/dialog/OnTargetCheckedChangeListener.java create mode 100644 core/java/com/android/internal/accessibility/dialog/OnTargetSelectedListener.java create mode 100644 core/java/com/android/internal/accessibility/dialog/TargetAdapter.java create mode 100644 core/java/com/android/internal/accessibility/dialog/TargetOperations.java create mode 100644 core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java create mode 100644 core/java/com/android/internal/accessibility/dialog/ToggleWhiteListingFeatureTarget.java create mode 100644 core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java new file mode 100644 index 0000000000000..4c7d93b7446a8 --- /dev/null +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java @@ -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))); + } +} diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java new file mode 100644 index 0000000000000..e64f78a3f2070 --- /dev/null +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java @@ -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))); + } +} diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java new file mode 100644 index 0000000000000..72ebc58380b8b --- /dev/null +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java @@ -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; + } +} diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java new file mode 100644 index 0000000000000..9d5c374e98f55 --- /dev/null +++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java @@ -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"); + } + } +} diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleWhiteListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleWhiteListingFeatureTarget.java new file mode 100644 index 0000000000000..acd101bf28bac --- /dev/null +++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleWhiteListingFeatureTarget.java @@ -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); + } +} diff --git a/core/java/com/android/internal/accessibility/dialog/OnTargetCheckedChangeListener.java b/core/java/com/android/internal/accessibility/dialog/OnTargetCheckedChangeListener.java new file mode 100644 index 0000000000000..dab45e45907d6 --- /dev/null +++ b/core/java/com/android/internal/accessibility/dialog/OnTargetCheckedChangeListener.java @@ -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); +} diff --git a/core/java/com/android/internal/accessibility/dialog/OnTargetSelectedListener.java b/core/java/com/android/internal/accessibility/dialog/OnTargetSelectedListener.java new file mode 100644 index 0000000000000..b3e976f296a9e --- /dev/null +++ b/core/java/com/android/internal/accessibility/dialog/OnTargetSelectedListener.java @@ -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(); +} diff --git a/core/java/com/android/internal/accessibility/dialog/TargetAdapter.java b/core/java/com/android/internal/accessibility/dialog/TargetAdapter.java new file mode 100644 index 0000000000000..1efa17e520ae6 --- /dev/null +++ b/core/java/com/android/internal/accessibility/dialog/TargetAdapter.java @@ -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; + } +} diff --git a/core/java/com/android/internal/accessibility/dialog/TargetOperations.java b/core/java/com/android/internal/accessibility/dialog/TargetOperations.java new file mode 100644 index 0000000000000..77cc5b454ed76 --- /dev/null +++ b/core/java/com/android/internal/accessibility/dialog/TargetOperations.java @@ -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); +} diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java new file mode 100644 index 0000000000000..3a42f7e0edb89 --- /dev/null +++ b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java @@ -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())); + } +} diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleWhiteListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleWhiteListingFeatureTarget.java new file mode 100644 index 0000000000000..fcbf5eccbd9ed --- /dev/null +++ b/core/java/com/android/internal/accessibility/dialog/ToggleWhiteListingFeatureTarget.java @@ -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; + } +} diff --git a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java new file mode 100644 index 0000000000000..04f5061fbd8e8 --- /dev/null +++ b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java @@ -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(); + } + } +} diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java index a92a50d4d8325..e50b010d691ad 100644 --- a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java +++ b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java @@ -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 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; + } } diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java index 7ec80ecdb2a91..fa90b49e85afe 100644 --- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java +++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java @@ -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; /** @@ -148,6 +150,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 requiredTargets = am.getAccessibilityShortcutTargets(shortcutType); + return requiredTargets.contains(componentId); + } + /** * Converts {@link UserShortcutType} to {@link Settings.Secure} key. * From 7d9af05e13f3e8979e60f73a48f29f917aea9dcf Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Wed, 8 Apr 2020 20:16:25 +0800 Subject: [PATCH 2/3] Redesign for AccessibilityShortcutChooserActivity (2/n). Refactor and replace it with new components and functions. Bug: 147655054 Test: manual test Change-Id: I12ef647ef0524d2d2cada63c46341c0fddb828da --- .../AccessibilityShortcutChooserActivity.java | 690 ++---------------- .../dialog/AccessibilityTargetHelper.java | 201 +++++ .../dialog/ShortcutTargetAdapter.java | 92 +++ 3 files changed, 349 insertions(+), 634 deletions(-) create mode 100644 core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java create mode 100644 core/java/com/android/internal/accessibility/dialog/ShortcutTargetAdapter.java diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java index 9338c3c87217a..42b827b25dfee 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java @@ -19,62 +19,32 @@ 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.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; @@ -84,38 +54,11 @@ import java.util.Locale; */ public class AccessibilityShortcutChooserActivity extends Activity { @ShortcutType - private static int sShortcutType; - @UserShortcutType - private int mShortcutUserType; - private final List 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 mTargets = new ArrayList<>(); + private AlertDialog mMenuDialog; + private AlertDialog mPermissionDialog; + private ShortcutTargetAdapter mTargetAdapter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -126,20 +69,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 +88,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 getServiceTargets(@NonNull Context context, - @ShortcutType int shortcutType) { - final List targets = getInstalledServiceTargets(context); - final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class); - final List requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType); - targets.removeIf(target -> !requiredTargets.contains(target.getId())); - - return targets; - } - - private static List getInstalledServiceTargets( - @NonNull Context context) { - final List targets = new ArrayList<>(); - targets.addAll(getAccessibilityFilteredTargets(context)); - targets.addAll(getWhiteListingServiceTargets(context)); - - return targets; - } - - private static List getAccessibilityFilteredTargets( - @NonNull Context context) { - final List serviceTargets = - getAccessibilityServiceTargets(context); - final List 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 targets = new ArrayList<>(); - targets.addAll(serviceTargets); - targets.addAll(activityTargets); - - return targets; - } - - private static List getAccessibilityServiceTargets( - @NonNull Context context) { - final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class); - final List installedServices = - ams.getInstalledAccessibilityServiceList(); - if (installedServices == null) { - return Collections.emptyList(); - } - - final List 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 getAccessibilityActivityTargets( - @NonNull Context context) { - final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class); - final List installedServices = - ams.getInstalledAccessibilityShortcutListAsUser(context, - ActivityManager.getCurrentUser()); - if (installedServices == null) { - return Collections.emptyList(); - } - - final List targets = new ArrayList<>(installedServices.size()); - for (AccessibilityShortcutInfo info : installedServices) { - targets.add(new AccessibilityButtonTarget(context, info)); - } - - return targets; - } - - private static List getWhiteListingServiceTargets( - @NonNull Context context) { - final List 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 mButtonTargets; - - TargetAdapter(List 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 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 +144,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,38 +159,19 @@ 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 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, + AccessibilityServiceTarget target, View.OnClickListener allowListener, View.OnClickListener denyListener) { final LayoutInflater inflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE); @@ -775,21 +191,27 @@ public class AccessibilityShortcutChooserActivity extends Activity { encryptionWarningView.setVisibility(View.GONE); } - final ImageView permissionDialogIcon = content.findViewById( + final ImageView dialogIcon = content.findViewById( R.id.accessibility_permissionDialog_icon); - permissionDialogIcon.setImageDrawable(target.getDrawable()); + dialogIcon.setImageDrawable(target.getIcon()); - final TextView permissionDialogTitle = content.findViewById( + final TextView dialogTitle = content.findViewById( R.id.accessibility_permissionDialog_title); - permissionDialogTitle.setText(context.getString(R.string.accessibility_enable_service_title, + dialogTitle.setText(context.getString(R.string.accessibility_enable_service_title, getServiceName(context, target.getLabel()))); - final Button permissionAllowButton = content.findViewById( + final Button allowButton = content.findViewById( R.id.accessibility_permission_enable_allow_button); - final Button permissionDenyButton = content.findViewById( + final Button denyButton = content.findViewById( R.id.accessibility_permission_enable_deny_button); - permissionAllowButton.setOnClickListener(allowListener); - permissionDenyButton.setOnClickListener(denyListener); + allowButton.setOnClickListener((view) -> { + target.onCheckedChanged(/* isChecked= */ true); + allowListener.onClick(view); + }); + denyButton.setOnClickListener((view) -> { + target.onCheckedChanged(/* isChecked= */ false); + denyListener.onClick(view); + }); return content; } diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java new file mode 100644 index 0000000000000..2772e2ce0fd44 --- /dev/null +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java @@ -0,0 +1,201 @@ +/* + * 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.provider.Settings; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityManager.ShortcutType; + +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; + +/** + * Collection of utilities for accessibility target. + */ +final class AccessibilityTargetHelper { + private AccessibilityTargetHelper() {} + + static List getTargets(Context context, + @ShortcutType int shortcutType) { + final List targets = getInstalledTargets(context, shortcutType); + final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class); + final List requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType); + targets.removeIf(target -> !requiredTargets.contains(target.getId())); + + return targets; + } + + static List getInstalledTargets(Context context, + @ShortcutType int shortcutType) { + final List targets = new ArrayList<>(); + targets.addAll(getAccessibilityFilteredTargets(context, shortcutType)); + targets.addAll(getWhiteListingFeatureTargets(context, shortcutType)); + + return targets; + } + + private static List getAccessibilityFilteredTargets(Context context, + @ShortcutType int shortcutType) { + final List serviceTargets = + getAccessibilityServiceTargets(context, shortcutType); + final List activityTargets = + getAccessibilityActivityTargets(context, shortcutType); + + for (AccessibilityTarget activityTarget : activityTargets) { + serviceTargets.removeIf( + serviceTarget -> arePackageNameAndLabelTheSame(serviceTarget, activityTarget)); + } + + final List 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 getAccessibilityServiceTargets(Context context, + @ShortcutType int shortcutType) { + final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class); + final List installedServices = + ams.getInstalledAccessibilityServiceList(); + if (installedServices == null) { + return Collections.emptyList(); + } + + final List 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 getAccessibilityActivityTargets(Context context, + @ShortcutType int shortcutType) { + final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class); + final List installedServices = + ams.getInstalledAccessibilityShortcutListAsUser(context, + ActivityManager.getCurrentUser()); + if (installedServices == null) { + return Collections.emptyList(); + } + + final List targets = new ArrayList<>(installedServices.size()); + for (AccessibilityShortcutInfo info : installedServices) { + targets.add(new AccessibilityActivityTarget(context, shortcutType, info)); + } + + return targets; + } + + private static List getWhiteListingFeatureTargets(Context context, + @ShortcutType int shortcutType) { + final List 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"); + } + } +} diff --git a/core/java/com/android/internal/accessibility/dialog/ShortcutTargetAdapter.java b/core/java/com/android/internal/accessibility/dialog/ShortcutTargetAdapter.java new file mode 100644 index 0000000000000..b7605b7fc0119 --- /dev/null +++ b/core/java/com/android/internal/accessibility/dialog/ShortcutTargetAdapter.java @@ -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 mTargets; + + ShortcutTargetAdapter(@NonNull List 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; + } +} From fbd4bf7b34c8c92108338fedb8d011318f84504e Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Wed, 8 Apr 2020 20:20:15 +0800 Subject: [PATCH 3/3] Redesign for AccessibilityShortcutChooserActivity (3/n). 1. Migrate the functions related to permision dialog into AccessibilityTargetHelper file. 2. Remove redundant codes. Bug: 147655054 Test: manual test Change-Id: Ie052bd7c4159552a78193464602e3d1d9471554b --- .../common/ShortcutConstants.java | 41 ------------- .../AccessibilityShortcutChooserActivity.java | 61 +------------------ .../dialog/AccessibilityTargetHelper.java | 60 ++++++++++++++++++ .../accessibility/util/ShortcutUtils.java | 23 ------- 4 files changed, 61 insertions(+), 124 deletions(-) diff --git a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java index b814e3f47162a..7c9c51c2842ff 100644 --- a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java +++ b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java @@ -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; - } } diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java index 42b827b25dfee..e8d2813fe39ed 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java @@ -20,6 +20,7 @@ import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHOR import static android.view.accessibility.AccessibilityManager.ShortcutType; import static com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode; +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; @@ -27,26 +28,18 @@ import static com.android.internal.util.Preconditions.checkArgument; import android.annotation.Nullable; import android.app.Activity; import android.app.AlertDialog; -import android.content.Context; import android.content.DialogInterface; import android.content.res.TypedArray; import android.os.Bundle; -import android.os.storage.StorageManager; -import android.text.BidiFormatter; -import android.view.LayoutInflater; import android.view.View; import android.view.Window; import android.view.accessibility.AccessibilityManager; import android.widget.AdapterView; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; import com.android.internal.R; import java.util.ArrayList; import java.util.List; -import java.util.Locale; /** * Activity used to display various targets related to accessibility service, accessibility @@ -169,56 +162,4 @@ public class AccessibilityShortcutChooserActivity extends Activity { mMenuDialog.getListView().setOnItemClickListener( isEditMenuMode ? this::onTargetChecked : this::onTargetSelected); } - - private 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); - } } diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java index 2772e2ce0fd44..f63cbe0dcd9e8 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java @@ -31,9 +31,16 @@ 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; @@ -41,6 +48,7 @@ import com.android.internal.accessibility.common.ShortcutConstants.Accessibility import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; /** * Collection of utilities for accessibility target. @@ -198,4 +206,56 @@ final class AccessibilityTargetHelper { 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); + } } diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java index fa90b49e85afe..c338a292646bc 100644 --- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java +++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java @@ -98,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. *