diff --git a/res/drawable/accessibility_magnification_full_screen.png b/res/drawable/accessibility_magnification_full_screen.png deleted file mode 100644 index 2e87ab874bc..00000000000 Binary files a/res/drawable/accessibility_magnification_full_screen.png and /dev/null differ diff --git a/res/drawable/accessibility_magnification_full_screen.xml b/res/drawable/accessibility_magnification_full_screen.xml new file mode 100644 index 00000000000..09d1a7e2cc4 --- /dev/null +++ b/res/drawable/accessibility_magnification_full_screen.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + diff --git a/res/drawable/accessibility_magnification_switch.xml b/res/drawable/accessibility_magnification_switch.xml new file mode 100644 index 00000000000..21e0cef53b4 --- /dev/null +++ b/res/drawable/accessibility_magnification_switch.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + diff --git a/res/drawable/accessibility_magnification_window_screen.png b/res/drawable/accessibility_magnification_window_screen.png deleted file mode 100644 index a7f2a2556e6..00000000000 Binary files a/res/drawable/accessibility_magnification_window_screen.png and /dev/null differ diff --git a/res/drawable/accessibility_magnification_window_screen.xml b/res/drawable/accessibility_magnification_window_screen.xml new file mode 100644 index 00000000000..d7e164c4953 --- /dev/null +++ b/res/drawable/accessibility_magnification_window_screen.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + diff --git a/res/layout/accessibility_edit_magnification_mode.xml b/res/layout/accessibility_edit_magnification_mode.xml deleted file mode 100644 index e4f31328fae..00000000000 --- a/res/layout/accessibility_edit_magnification_mode.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/res/layout/accessibility_edit_shortcut_component.xml b/res/layout/accessibility_edit_shortcut_component.xml index 0ccc88d3d24..0d3324f9b54 100644 --- a/res/layout/accessibility_edit_shortcut_component.xml +++ b/res/layout/accessibility_edit_shortcut_component.xml @@ -55,8 +55,8 @@ diff --git a/res/layout/accessibility_magnification_mode_header.xml b/res/layout/accessibility_magnification_mode_header.xml new file mode 100644 index 00000000000..e4765535f27 --- /dev/null +++ b/res/layout/accessibility_magnification_mode_header.xml @@ -0,0 +1,30 @@ + + + + + + + \ No newline at end of file diff --git a/res/layout/dialog_single_radio_choice_list_item.xml b/res/layout/dialog_single_radio_choice_list_item.xml new file mode 100644 index 00000000000..5842528935b --- /dev/null +++ b/res/layout/dialog_single_radio_choice_list_item.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/dimens.xml b/res/values/dimens.xml index dbf21fc8626..0ee39cdfb52 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -295,6 +295,7 @@ 16dp 18dp + 176dp 16dp diff --git a/res/values/strings.xml b/res/values/strings.xml index 46bd3519ecb..0e29ea21976 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5039,7 +5039,7 @@ Magnification - Choose how to magnify + Magnification type Magnify your full screen, a specific area, or switch between both options @@ -5048,10 +5048,16 @@ Partial screen Switch between full and partial screen - - Magnify full screen - - Magnify part of screen + + Choose how to magnify + + Magnify full screen + + Magnify part of screen + + Switch between full and partial screen + + Tap the switch button to move between both options Switch to accessibility button? diff --git a/src/com/android/settings/accessibility/AccessibilityEditDialogUtils.java b/src/com/android/settings/accessibility/AccessibilityEditDialogUtils.java index 6b31988ecff..5d2a3fa9d0a 100644 --- a/src/com/android/settings/accessibility/AccessibilityEditDialogUtils.java +++ b/src/com/android/settings/accessibility/AccessibilityEditDialogUtils.java @@ -16,6 +16,8 @@ package com.android.settings.accessibility; +import static com.android.settings.accessibility.ItemInfoArrayAdapter.ItemInfo; + import android.app.Dialog; import android.app.settings.SettingsEnums; import android.content.Context; @@ -29,15 +31,20 @@ import android.text.method.LinkMovementMethod; import android.text.style.ImageSpan; import android.view.LayoutInflater; import android.view.View; +import android.widget.AbsListView; +import android.widget.AdapterView; import android.widget.Button; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.ListView; import android.widget.ScrollView; import android.widget.TextView; import androidx.annotation.ColorInt; import androidx.annotation.IntDef; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; @@ -47,6 +54,8 @@ import com.android.settings.utils.AnnotationSpan; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.List; + /** * Utility class for creating the edit dialog. @@ -61,15 +70,13 @@ public class AccessibilityEditDialogUtils { @IntDef({ DialogType.EDIT_SHORTCUT_GENERIC, DialogType.EDIT_SHORTCUT_MAGNIFICATION, - DialogType.EDIT_MAGNIFICATION_MODE, DialogType.EDIT_MAGNIFICATION_SWITCH_SHORTCUT, }) private @interface DialogType { int EDIT_SHORTCUT_GENERIC = 0; int EDIT_SHORTCUT_MAGNIFICATION = 1; - int EDIT_MAGNIFICATION_MODE = 2; - int EDIT_MAGNIFICATION_SWITCH_SHORTCUT = 3; + int EDIT_MAGNIFICATION_SWITCH_SHORTCUT = 2; } /** @@ -106,23 +113,6 @@ public class AccessibilityEditDialogUtils { return alertDialog; } - /** - * Method to show the magnification mode dialog in Magnification. - * - * @param context A valid context - * @param dialogTitle The title of magnify mode dialog - * @param listener The listener to determine the action of magnify mode dialog - * @return A magnification mode dialog in Magnification - */ - public static AlertDialog showMagnificationModeDialog(Context context, - CharSequence dialogTitle, DialogInterface.OnClickListener listener) { - final AlertDialog alertDialog = createDialog(context, - DialogType.EDIT_MAGNIFICATION_MODE, dialogTitle, listener); - alertDialog.show(); - setScrollIndicators(alertDialog); - return alertDialog; - } - /** * Method to show the magnification edit shortcut dialog in Magnification. * @@ -163,11 +153,21 @@ public class AccessibilityEditDialogUtils { */ private static void setScrollIndicators(AlertDialog dialog) { final ScrollView scrollView = dialog.findViewById(R.id.container_layout); - scrollView.setScrollIndicators( + setScrollIndicators(scrollView); + } + + /** + * Sets the scroll indicators for dialog view. The indicators appear while content view is + * out of vision for vertical scrolling. + * + * @param view The view contains customized dialog content. Usually it is {@link ScrollView} or + * {@link AbsListView} + */ + private static void setScrollIndicators(@NonNull View view) { + view.setScrollIndicators( View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM, View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM); } - private static void setEditShortcutButtonsListener(AlertDialog dialog, View.OnClickListener listener) { final View contentView = dialog.findViewById(R.id.container_layout); @@ -212,12 +212,6 @@ public class AccessibilityEditDialogUtils { initMagnifyShortcut(context, contentView); initAdvancedWidget(contentView); break; - case DialogType.EDIT_MAGNIFICATION_MODE: - contentView = inflater.inflate( - R.layout.accessibility_edit_magnification_mode, null); - initMagnifyFullScreen(context, contentView); - initMagnifyWindowScreen(context, contentView); - break; case DialogType.EDIT_MAGNIFICATION_SWITCH_SHORTCUT: contentView = inflater.inflate( R.layout.accessibility_edit_magnification_shortcut, null); @@ -229,25 +223,6 @@ public class AccessibilityEditDialogUtils { return contentView; } - private static void initMagnifyFullScreen(Context context, View view) { - final View dialogView = view.findViewById(R.id.magnify_full_screen); - final CharSequence title = context.getText( - R.string.accessibility_magnification_area_settings_full_screen); - setupShortcutWidget(dialogView, title, R.drawable.accessibility_magnification_full_screen); - } - - private static void initMagnifyWindowScreen(Context context, View view) { - final View dialogView = view.findViewById(R.id.magnify_window_screen); - final CharSequence title = context.getText( - R.string.accessibility_magnification_area_settings_window_screen); - setupShortcutWidget(dialogView, title, - R.drawable.accessibility_magnification_window_screen); - } - - private static void setupShortcutWidget(View view, CharSequence titleText, int imageResId) { - setupShortcutWidget(view, titleText, null, imageResId); - } - private static void setupShortcutWidget(View view, CharSequence titleText, CharSequence summaryText, int imageResId) { final CheckBox checkBox = view.findViewById(R.id.checkbox); @@ -345,7 +320,6 @@ public class AccessibilityEditDialogUtils { spannableMessage.setSpan( imageSpan, indexIconStart, indexIconEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - return spannableMessage; } @@ -368,4 +342,52 @@ public class AccessibilityEditDialogUtils { typedArray.recycle(); return colorResId; } + + /** + * Creates a dialog with the given view. + * + * @param context A valid context + * @param dialogTitle The title of the dialog + * @param customView The customized view + * @param listener This listener will be invoked when the positive button in the dialog is + * clicked + * @return the {@link Dialog} with the given view + */ + public static Dialog createCustomDialog(Context context, CharSequence dialogTitle, + View customView, DialogInterface.OnClickListener listener) { + final AlertDialog alertDialog = new AlertDialog.Builder(context) + .setView(customView) + .setTitle(dialogTitle) + .setCancelable(true) + .setPositiveButton(R.string.save, listener) + .setNegativeButton(R.string.cancel, null) + .create(); + if (customView instanceof ScrollView || customView instanceof AbsListView) { + setScrollIndicators(customView); + } + return alertDialog; + } + + /** + * Creates a single choice {@link ListView} with given {@link ItemInfo} list. + * + * @param context A context. + * @param itemInfoList A {@link ItemInfo} list. + * @param itemListener The listener will be invoked when the item is clicked. + */ + @NonNull + public static ListView createSingleChoiceListView(@NonNull Context context, + @NonNull List itemInfoList, + @Nullable AdapterView.OnItemClickListener itemListener) { + final ListView list = new ListView(context); + // Set an id to save its state. + list.setId(android.R.id.list); + list.setDivider(/* divider= */ null); + list.setChoiceMode(ListView.CHOICE_MODE_SINGLE); + final ItemInfoArrayAdapter + adapter = new ItemInfoArrayAdapter(context, itemInfoList); + list.setAdapter(adapter); + list.setOnItemClickListener(itemListener); + return list; + } } diff --git a/src/com/android/settings/accessibility/ItemInfoArrayAdapter.java b/src/com/android/settings/accessibility/ItemInfoArrayAdapter.java new file mode 100644 index 00000000000..edb16a2a4e4 --- /dev/null +++ b/src/com/android/settings/accessibility/ItemInfoArrayAdapter.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import android.content.Context; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.settings.R; + +import java.util.List; + +/** + * An {@link ArrayAdapter} to fill the information of {@link ItemInfo} in the item view. The item + * view must have textview to set the title. + * + * @param the type of elements in the array, inherited from {@link ItemInfo}. + */ +public class ItemInfoArrayAdapter extends ArrayAdapter { + + public ItemInfoArrayAdapter(@NonNull Context context, @NonNull List items) { + super(context, R.layout.dialog_single_radio_choice_list_item, R.id.title, items); + } + + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + final View root = super.getView(position, convertView, parent); + + final ItemInfo item = getItem(position); + final TextView title = root.findViewById(R.id.title); + title.setText(item.mTitle); + final TextView summary = root.findViewById(R.id.summary); + if (!TextUtils.isEmpty(item.mSummary)) { + summary.setVisibility(View.VISIBLE); + summary.setText(item.mSummary); + } else { + summary.setVisibility(View.GONE); + } + final ImageView image = root.findViewById(R.id.image); + image.setImageResource(item.mDrawableId); + return root; + } + + /** + * Presents a data structure shown in the item view. + */ + public static class ItemInfo { + @NonNull + public final CharSequence mTitle; + @Nullable + public final CharSequence mSummary; + @DrawableRes + public final int mDrawableId; + + public ItemInfo(@NonNull CharSequence title, @Nullable CharSequence summary, + @DrawableRes int drawableId) { + mTitle = title; + mSummary = summary; + mDrawableId = drawableId; + } + } +} diff --git a/src/com/android/settings/accessibility/MagnificationSettingsFragment.java b/src/com/android/settings/accessibility/MagnificationSettingsFragment.java index 6003a6d1c51..c4d6fd5c752 100644 --- a/src/com/android/settings/accessibility/MagnificationSettingsFragment.java +++ b/src/com/android/settings/accessibility/MagnificationSettingsFragment.java @@ -26,20 +26,26 @@ import android.content.DialogInterface; import android.os.Bundle; import android.provider.Settings; import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; +import android.widget.AdapterView; +import android.widget.ListView; +import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import com.android.settings.R; +import com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.search.SearchIndexable; +import java.util.ArrayList; +import java.util.List; import java.util.StringJoiner; /** Settings page for magnification. */ @@ -52,6 +58,7 @@ public class MagnificationSettingsFragment extends DashboardFragment { static final int DIALOG_MAGNIFICATION_CAPABILITY = 1; @VisibleForTesting static final int DIALOG_MAGNIFICATION_SWITCH_SHORTCUT = 2; + @VisibleForTesting static final String EXTRA_CAPABILITY = "capability"; private static final int NONE = 0; @@ -60,13 +67,13 @@ public class MagnificationSettingsFragment extends DashboardFragment { private Preference mModePreference; @VisibleForTesting Dialog mDialog; - @VisibleForTesting - CheckBox mMagnifyFullScreenCheckBox; - @VisibleForTesting - CheckBox mMagnifyWindowCheckBox; + @VisibleForTesting + ListView mMagnificationModesListView; private int mCapabilities = NONE; + private final List mModeInfos = new ArrayList<>(); + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -77,6 +84,7 @@ public class MagnificationSettingsFragment extends DashboardFragment { if (mCapabilities == NONE) { mCapabilities = MagnificationCapabilities.getCapabilities(getPrefContext()); } + initModeInfos(); } @Override @@ -121,13 +129,10 @@ public class MagnificationSettingsFragment extends DashboardFragment { @Override public Dialog onCreateDialog(int dialogId) { final CharSequence title; + switch (dialogId) { case DIALOG_MAGNIFICATION_CAPABILITY: - title = getPrefContext().getString( - R.string.accessibility_magnification_mode_title); - mDialog = AccessibilityEditDialogUtils.showMagnificationModeDialog(getPrefContext(), - title, this::callOnAlertDialogCheckboxClicked); - initializeDialogCheckBox(mDialog); + mDialog = createMagnificationModeDialog(); return mDialog; case DIALOG_MAGNIFICATION_SWITCH_SHORTCUT: title = getPrefContext().getString( @@ -136,10 +141,97 @@ public class MagnificationSettingsFragment extends DashboardFragment { getPrefContext(), title, this::onSwitchShortcutDialogPositiveButtonClicked); return mDialog; } - throw new IllegalArgumentException("Unsupported dialogId " + dialogId); } + private Dialog createMagnificationModeDialog() { + mMagnificationModesListView = AccessibilityEditDialogUtils.createSingleChoiceListView( + getPrefContext(), mModeInfos, this::onMagnificationModeSelected); + + final View headerView = LayoutInflater.from(getPrefContext()).inflate( + R.layout.accessibility_magnification_mode_header, mMagnificationModesListView, + false); + mMagnificationModesListView.addHeaderView(headerView, null, /* isSelectable= */false); + + mMagnificationModesListView.setItemChecked(computeSelectedMagnificationModeIndex(), true); + final CharSequence title = getPrefContext().getString( + R.string.accessibility_magnification_mode_dialog_title); + + return AccessibilityEditDialogUtils.createCustomDialog(getPrefContext(), title, + mMagnificationModesListView, this::onMagnificationModeDialogPositiveButtonClicked); + } + + private int computeSelectedMagnificationModeIndex() { + final int size = mModeInfos.size(); + for (int i = 0; i < size; i++) { + if (mModeInfos.get(i).mMagnificationMode == mCapabilities) { + return i + mMagnificationModesListView.getHeaderViewsCount(); + } + } + Log.w(TAG, "chosen mode" + mCapabilities + "is not in the list"); + return 0; + } + + private void onMagnificationModeSelected(AdapterView parent, View view, int position, + long id) { + final MagnificationModeInfo modeInfo = + (MagnificationModeInfo) mMagnificationModesListView.getItemAtPosition(position); + if (modeInfo.mMagnificationMode == mCapabilities) { + return; + } + mCapabilities = modeInfo.mMagnificationMode; + if (isTripleTapEnabled() && mCapabilities != MagnificationMode.FULLSCREEN) { + showDialog(DIALOG_MAGNIFICATION_SWITCH_SHORTCUT); + } + } + + private void onMagnificationModeDialogPositiveButtonClicked(DialogInterface dialogInterface, + int which) { + final int selectedIndex = mMagnificationModesListView.getCheckedItemPosition(); + if (selectedIndex != AdapterView.INVALID_POSITION) { + final MagnificationModeInfo modeInfo = + (MagnificationModeInfo) mMagnificationModesListView.getItemAtPosition( + selectedIndex); + updateCapabilities(modeInfo.mMagnificationMode); + } else { + Log.w(TAG, "no checked item in the list"); + } + } + + private void updateCapabilities(int mode) { + mCapabilities = mode; + MagnificationCapabilities.setCapabilities(getPrefContext(), mCapabilities); + mModePreference.setSummary( + MagnificationCapabilities.getSummary(getPrefContext(), mCapabilities)); + } + + private void initModeInfos() { + mModeInfos.clear(); + mModeInfos.add(new MagnificationModeInfo(getPrefContext().getText( + R.string.accessibility_magnification_mode_dialog_option_full_screen), null, + R.drawable.accessibility_magnification_full_screen, MagnificationMode.FULLSCREEN)); + mModeInfos.add(new MagnificationModeInfo(getPrefContext().getText( + R.string.accessibility_magnification_mode_dialog_option_window), null, + R.drawable.accessibility_magnification_window_screen, MagnificationMode.WINDOW)); + mModeInfos.add(new MagnificationModeInfo(getPrefContext().getText( + R.string.accessibility_magnification_mode_dialog_option_switch), + getPrefContext().getText( + R.string.accessibility_magnification_area_settings_mode_switch_summary), + R.drawable.accessibility_magnification_switch, MagnificationMode.ALL)); + } + + @VisibleForTesting + static class MagnificationModeInfo extends ItemInfoArrayAdapter.ItemInfo { + @MagnificationMode + public final int mMagnificationMode; + + MagnificationModeInfo(@NonNull CharSequence title, @Nullable CharSequence summary, + @DrawableRes int drawableId, @MagnificationMode int magnificationMode) { + super(title, summary, drawableId); + mMagnificationMode = magnificationMode; + } + } + private void initModePreference() { mModePreference = findPreference(PREF_KEY_MODE); mModePreference.setOnPreferenceClickListener(preference -> { @@ -149,12 +241,6 @@ public class MagnificationSettingsFragment extends DashboardFragment { }); } - private void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) { - updateCapabilities(true); - mModePreference.setSummary( - MagnificationCapabilities.getSummary(getPrefContext(), mCapabilities)); - } - private void onSwitchShortcutDialogPositiveButtonClicked(View view) { //TODO(b/147990389): Merge this function into util until magnification change format to // Component. @@ -188,95 +274,6 @@ public class MagnificationSettingsFragment extends DashboardFragment { joiner.toString()); } - private void initializeDialogCheckBox(Dialog dialog) { - final View dialogFullScreenView = dialog.findViewById(R.id.magnify_full_screen); - final View dialogFullScreenTextArea = dialogFullScreenView.findViewById(R.id.container); - mMagnifyFullScreenCheckBox = dialogFullScreenView.findViewById(R.id.checkbox); - - final View dialogWidowView = dialog.findViewById(R.id.magnify_window_screen); - final View dialogWindowTextArea = dialogWidowView.findViewById(R.id.container); - mMagnifyWindowCheckBox = dialogWidowView.findViewById(R.id.checkbox); - - updateAlertDialogCheckState(); - updateAlertDialogEnableState(dialogFullScreenTextArea, dialogWindowTextArea); - - setTextAreasClickListener(dialogFullScreenTextArea, mMagnifyFullScreenCheckBox, - dialogWindowTextArea, mMagnifyWindowCheckBox); - } - - private void setTextAreasClickListener(View fullScreenTextArea, CheckBox fullScreenCheckBox, - View windowTextArea, CheckBox windowCheckBox) { - fullScreenTextArea.setOnClickListener(v -> { - fullScreenCheckBox.toggle(); - updateCapabilities(false); - updateAlertDialogEnableState(fullScreenTextArea, windowTextArea); - }); - - windowTextArea.setOnClickListener(v -> { - windowCheckBox.toggle(); - updateCapabilities(false); - updateAlertDialogEnableState(fullScreenTextArea, windowTextArea); - - if (isTripleTapEnabled() && windowCheckBox.isChecked()) { - showDialog(DIALOG_MAGNIFICATION_SWITCH_SHORTCUT); - } - }); - } - - private void updateAlertDialogCheckState() { - updateCheckStatus(mMagnifyWindowCheckBox, - Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); - updateCheckStatus(mMagnifyFullScreenCheckBox, - Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); - - } - - private void updateCheckStatus(CheckBox checkBox, int mode) { - checkBox.setChecked((mode & mCapabilities) != 0); - } - - private void updateAlertDialogEnableState(View fullScreenTextArea, View windowTextArea) { - switch (mCapabilities) { - case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN: - setViewAndChildrenEnabled(fullScreenTextArea, false); - break; - case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW: - setViewAndChildrenEnabled(windowTextArea, false); - break; - case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL: - setViewAndChildrenEnabled(fullScreenTextArea, true); - setViewAndChildrenEnabled(windowTextArea, true); - break; - default: - throw new IllegalArgumentException( - "Unsupported ACCESSIBILITY_MAGNIFICATION_CAPABILITY " + mCapabilities); - } - } - - private void setViewAndChildrenEnabled(View view, boolean enabled) { - view.setEnabled(enabled); - if (view instanceof ViewGroup) { - final ViewGroup viewGroup = (ViewGroup) view; - for (int i = 0; i < viewGroup.getChildCount(); i++) { - View child = viewGroup.getChildAt(i); - setViewAndChildrenEnabled(child, enabled); - } - } - } - - private void updateCapabilities(boolean saveToDB) { - int capabilities = 0; - capabilities |= - mMagnifyFullScreenCheckBox.isChecked() - ? Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN : 0; - capabilities |= mMagnifyWindowCheckBox.isChecked() - ? Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW : 0; - mCapabilities = capabilities; - if (saveToDB) { - MagnificationCapabilities.setCapabilities(getPrefContext(), mCapabilities); - } - } - private boolean isTripleTapEnabled() { return Settings.Secure.getInt(getPrefContext().getContentResolver(), Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF) == ON; diff --git a/src/com/android/settings/widget/CheckableRelativeLayout.java b/src/com/android/settings/widget/CheckableRelativeLayout.java new file mode 100644 index 00000000000..d26c042dc29 --- /dev/null +++ b/src/com/android/settings/widget/CheckableRelativeLayout.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.Checkable; +import android.widget.RelativeLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * A RelativeLayout which implements {@link Checkable}. With this implementation, it could be used + * in the list item layout for {@link android.widget.AbsListView} to change UI after item click. + * Its checked state would be propagated to the checkable child. + * + *

+ * To support accessibility, the state description is from the checkable view and is + * changed with {@link #setChecked(boolean)}. We make the checkable child unclickable, unfocusable + * and non-important for accessibility, so that the announcement wouldn't include + * the checkable view. + * < + */ +public class CheckableRelativeLayout extends RelativeLayout implements Checkable { + + private Checkable mCheckable; + private View mCheckableChild; + private boolean mChecked; + + public CheckableRelativeLayout(Context context) { + super(context); + } + + public CheckableRelativeLayout(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + mCheckableChild = findFirstCheckableView(this); + if (mCheckableChild != null) { + mCheckableChild.setClickable(false); + mCheckableChild.setFocusable(false); + mCheckableChild.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); + mCheckable = (Checkable) mCheckableChild; + mCheckable.setChecked(isChecked()); + setStateDescriptionIfNeeded(); + } + super.onFinishInflate(); + } + + @Nullable + private static View findFirstCheckableView(@NonNull ViewGroup viewGroup) { + final int childCount = viewGroup.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = viewGroup.getChildAt(i); + if (child instanceof Checkable) { + return child; + } + if (child instanceof ViewGroup) { + findFirstCheckableView((ViewGroup) child); + } + } + return null; + } + + @Override + public void setChecked(boolean checked) { + if (mChecked != checked) { + mChecked = checked; + if (mCheckable != null) { + mCheckable.setChecked(checked); + } + } + setStateDescriptionIfNeeded(); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + } + + private void setStateDescriptionIfNeeded() { + if (mCheckableChild == null) { + return; + } + setStateDescription(mCheckableChild.getStateDescription()); + } + + @Override + public boolean isChecked() { + return mChecked; + } + + @Override + public void toggle() { + setChecked(!mChecked); + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setChecked(mChecked); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setChecked(mChecked); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/MagnificationSettingsFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/MagnificationSettingsFragmentTest.java index d49213c8eb4..11128e421e7 100644 --- a/tests/robotests/src/com/android/settings/accessibility/MagnificationSettingsFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/MagnificationSettingsFragmentTest.java @@ -16,12 +16,15 @@ package com.android.settings.accessibility; +import static com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode; import static com.android.settings.accessibility.MagnificationPreferenceFragment.ON; +import static com.android.settings.accessibility.MagnificationSettingsFragment.MagnificationModeInfo; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -29,7 +32,8 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.os.Bundle; import android.provider.Settings; -import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceManager; @@ -77,7 +81,7 @@ public class MagnificationSettingsFragmentTest { } @Test - public void onCreateDialog_capabilitiesInBundle_matchCheckBoxStatus() { + public void onCreateDialog_capabilitiesInBundle_checkedModeInDialogIsExpected() { final Bundle windowModeSavedInstanceState = new Bundle(); windowModeSavedInstanceState.putInt(EXTRA_CAPABILITY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); @@ -85,23 +89,21 @@ public class MagnificationSettingsFragmentTest { mFragment.onCreate(windowModeSavedInstanceState); mFragment.onCreateDialog(MagnificationSettingsFragment.DIALOG_MAGNIFICATION_CAPABILITY); - assertThat(mFragment.mMagnifyFullScreenCheckBox.isChecked()).isFalse(); - assertThat(mFragment.mMagnifyWindowCheckBox.isChecked()).isTrue(); + assertThat(getChoseModeFromDialog()).isEqualTo(MagnificationMode.WINDOW); } @Test - public void onCreateDialog_capabilitiesInSettings_matchCheckBoxStatus() { + public void onCreateDialog_capabilitiesInSetting_checkedModeInDialogIsExpected() { MagnificationCapabilities.setCapabilities(mContext, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); mFragment.onCreate(Bundle.EMPTY); mFragment.onCreateDialog(MagnificationSettingsFragment.DIALOG_MAGNIFICATION_CAPABILITY); - assertThat(mFragment.mMagnifyFullScreenCheckBox.isChecked()).isTrue(); - assertThat(mFragment.mMagnifyWindowCheckBox.isChecked()).isFalse(); + assertThat(getChoseModeFromDialog()).isEqualTo(MagnificationMode.FULLSCREEN); } @Test - public void onCreateDialog_capabilitiesInSettingsAndBundle_matchBundleValueCheckBoxStatus() { + public void onCreateDialog_choseModeIsDifferentFromInSettings_ShowUsersChoseModeInDialog() { final Bundle allModeSavedInstanceState = new Bundle(); allModeSavedInstanceState.putInt(EXTRA_CAPABILITY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL); @@ -111,37 +113,93 @@ public class MagnificationSettingsFragmentTest { mFragment.onCreate(allModeSavedInstanceState); mFragment.onCreateDialog(MagnificationSettingsFragment.DIALOG_MAGNIFICATION_CAPABILITY); - assertThat(mFragment.mMagnifyFullScreenCheckBox.isChecked()).isTrue(); - assertThat(mFragment.mMagnifyWindowCheckBox.isChecked()).isTrue(); + assertThat(getChoseModeFromDialog()).isEqualTo(MagnificationMode.ALL); } @Test - public void onCreateDialog_emptySettingsAndBundle_matchDefaultValueCheckBoxStatus() { + public void onCreateDialog_emptySettingsAndBundle_checkedModeInDialogIsDefaultValue() { mFragment.onCreate(Bundle.EMPTY); mFragment.onCreateDialog(MagnificationSettingsFragment.DIALOG_MAGNIFICATION_CAPABILITY); - // Compare to default Capabilities - assertThat(mFragment.mMagnifyFullScreenCheckBox.isChecked()).isTrue(); - assertThat(mFragment.mMagnifyWindowCheckBox.isChecked()).isFalse(); + assertThat(getChoseModeFromDialog()).isEqualTo(MagnificationMode.FULLSCREEN); } @Test - public void checkWindowModeCheckBox_tripleTapEnabled_showSwitchShortcutDialog() { + public void chooseWindowMode_tripleTapEnabled_showSwitchShortcutDialog() { + enableTripleTap(); final Bundle fullScreenModeSavedInstanceState = new Bundle(); fullScreenModeSavedInstanceState.putInt(EXTRA_CAPABILITY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); mFragment.onCreate(fullScreenModeSavedInstanceState); mFragment.onCreateDialog(MagnificationSettingsFragment.DIALOG_MAGNIFICATION_CAPABILITY); - enableTripleTap(); - final View dialogWidowView = mFragment.mDialog.findViewById(R.id.magnify_window_screen); - final View dialogWindowTextArea = dialogWidowView.findViewById(R.id.container); - dialogWindowTextArea.performClick(); + performItemClickWith(MagnificationMode.WINDOW); verify(mFragment).showDialog( MagnificationSettingsFragment.DIALOG_MAGNIFICATION_SWITCH_SHORTCUT); } + @Test + public void chooseModeAll_tripleTapEnabled_showSwitchShortcutDialog() { + enableTripleTap(); + final Bundle fullScreenModeSavedInstanceState = new Bundle(); + fullScreenModeSavedInstanceState.putInt(EXTRA_CAPABILITY, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + mFragment.onCreate(fullScreenModeSavedInstanceState); + mFragment.onCreateDialog(MagnificationSettingsFragment.DIALOG_MAGNIFICATION_CAPABILITY); + + performItemClickWith(MagnificationMode.ALL); + + verify(mFragment).showDialog( + MagnificationSettingsFragment.DIALOG_MAGNIFICATION_SWITCH_SHORTCUT); + } + + @Test + public void chooseWindowMode_WindowModeInSettingsAndTripleTapEnabled_notShowShortCutDialog() { + enableTripleTap(); + MagnificationCapabilities.setCapabilities(mContext, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); + mFragment.onCreate(Bundle.EMPTY); + mFragment.onCreateDialog(MagnificationSettingsFragment.DIALOG_MAGNIFICATION_CAPABILITY); + + performItemClickWith(MagnificationMode.WINDOW); + + verify(mFragment, never()).showDialog( + MagnificationSettingsFragment.DIALOG_MAGNIFICATION_SWITCH_SHORTCUT); + } + + private int getChoseModeFromDialog() { + final ListView listView = mFragment.mMagnificationModesListView; + assertThat(listView).isNotNull(); + + final int checkedPosition = listView.getCheckedItemPosition(); + final MagnificationModeInfo modeInfo = + (MagnificationModeInfo) listView.getAdapter().getItem( + checkedPosition); + return modeInfo.mMagnificationMode; + } + + private void performItemClickWith(@MagnificationMode int mode) { + final ListView listView = mFragment.mMagnificationModesListView; + assertThat(listView).isNotNull(); + + int modeIndex = AdapterView.NO_ID; + // Index 0 is header. + for (int i = 1; i < listView.getAdapter().getCount(); i++) { + final MagnificationModeInfo modeInfo = + (MagnificationModeInfo) listView.getAdapter().getItem(i); + if (modeInfo.mMagnificationMode == mode) { + modeIndex = i; + break; + } + } + if (modeIndex == AdapterView.NO_ID) { + throw new RuntimeException("The mode is not in the list."); + } + + listView.performItemClick(listView.getChildAt(modeIndex), modeIndex, modeIndex); + } + private void enableTripleTap() { Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, ON);