/* * Copyright (C) 2019 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 static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME; import static com.android.settings.accessibility.AccessibilityDialogUtils.CustomButton; import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; import static com.android.settings.accessibility.AccessibilityUtil.State.ON; import android.app.Dialog; import android.app.settings.SettingsEnums; import android.content.Context; 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.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 androidx.preference.PreferenceScreen; import com.android.settings.DialogCreatable; import com.android.settings.R; import com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode; import com.android.settings.core.BasePreferenceController; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnCreate; import com.android.settingslib.core.lifecycle.events.OnResume; import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState; import java.util.ArrayList; import java.util.List; import java.util.StringJoiner; /** Controller that shows the magnification area mode summary and the preference click behavior. */ public class MagnificationModePreferenceController extends BasePreferenceController implements DialogCreatable, LifecycleObserver, OnCreate, OnResume, OnSaveInstanceState { static final String PREF_KEY = "screen_magnification_mode"; private static final int DIALOG_ID_BASE = 10; @VisibleForTesting static final int DIALOG_MAGNIFICATION_MODE = DIALOG_ID_BASE + 1; @VisibleForTesting static final int DIALOG_MAGNIFICATION_SWITCH_SHORTCUT = DIALOG_ID_BASE + 2; @VisibleForTesting static final String EXTRA_MODE = "mode"; private static final String TAG = "MagnificationModePreferenceController"; private static final char COMPONENT_NAME_SEPARATOR = ':'; private DialogHelper mDialogHelper; // The magnification mode in the dialog. private int mMode = MagnificationMode.NONE; private Preference mModePreference; @VisibleForTesting ListView mMagnificationModesListView; private final List mModeInfos = new ArrayList<>(); public MagnificationModePreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); initModeInfos(); } private void initModeInfos() { mModeInfos.add(new MagnificationModeInfo(mContext.getText( R.string.accessibility_magnification_mode_dialog_option_full_screen), null, R.drawable.ic_illustration_fullscreen, MagnificationMode.FULLSCREEN)); mModeInfos.add(new MagnificationModeInfo( mContext.getText(R.string.accessibility_magnification_mode_dialog_option_window), null, R.drawable.ic_illustration_window, MagnificationMode.WINDOW)); mModeInfos.add(new MagnificationModeInfo( mContext.getText(R.string.accessibility_magnification_mode_dialog_option_switch), mContext.getText( R.string.accessibility_magnification_area_settings_mode_switch_summary), R.drawable.ic_illustration_switch, MagnificationMode.ALL)); } @Override public int getAvailabilityStatus() { return AVAILABLE; } @Override public CharSequence getSummary() { final int capabilities = MagnificationCapabilities.getCapabilities(mContext); return MagnificationCapabilities.getSummary(mContext, capabilities); } @Override public void onCreate(Bundle savedInstanceState) { if (savedInstanceState != null) { mMode = savedInstanceState.getInt(EXTRA_MODE, MagnificationMode.NONE); } } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mModePreference = screen.findPreference(getPreferenceKey()); mModePreference.setOnPreferenceClickListener(preference -> { mMode = MagnificationCapabilities.getCapabilities(mContext); mDialogHelper.showDialog(DIALOG_MAGNIFICATION_MODE); return true; }); } @Override public void onSaveInstanceState(Bundle outState) { outState.putInt(EXTRA_MODE, mMode); } /** * Sets {@link DialogHelper} used to show the dialog. */ public void setDialogHelper(DialogHelper dialogHelper) { mDialogHelper = dialogHelper; mDialogHelper.setDialogDelegate(this); } @Override public Dialog onCreateDialog(int dialogId) { switch (dialogId) { case DIALOG_MAGNIFICATION_MODE: return createMagnificationModeDialog(); case DIALOG_MAGNIFICATION_SWITCH_SHORTCUT: return createMagnificationShortCutConfirmDialog(); } return null; } @Override public int getDialogMetricsCategory(int dialogId) { switch (dialogId) { case DIALOG_MAGNIFICATION_MODE: return SettingsEnums.DIALOG_MAGNIFICATION_CAPABILITY; case DIALOG_MAGNIFICATION_SWITCH_SHORTCUT: return SettingsEnums.DIALOG_MAGNIFICATION_SWITCH_SHORTCUT; default: return 0; } } private Dialog createMagnificationModeDialog() { mMagnificationModesListView = AccessibilityDialogUtils.createSingleChoiceListView( mContext, mModeInfos, this::onMagnificationModeSelected); final View headerView = LayoutInflater.from(mContext).inflate( R.layout.accessibility_magnification_mode_header, mMagnificationModesListView, false); mMagnificationModesListView.addHeaderView(headerView, /* data= */ null, /* isSelectable= */ false); mMagnificationModesListView.setItemChecked(computeSelectionIndex(), true); final CharSequence title = mContext.getString( R.string.accessibility_magnification_mode_dialog_title); return AccessibilityDialogUtils.createCustomDialog(mContext, title, mMagnificationModesListView, this::onMagnificationModeDialogPositiveButtonClicked); } private void onMagnificationModeDialogPositiveButtonClicked(DialogInterface dialogInterface, int which) { final int selectedIndex = mMagnificationModesListView.getCheckedItemPosition(); if (selectedIndex != AdapterView.INVALID_POSITION) { final MagnificationModeInfo modeInfo = (MagnificationModeInfo) mMagnificationModesListView.getItemAtPosition( selectedIndex); setMode(modeInfo.mMagnificationMode); } else { Log.w(TAG, "invalid index"); } } private void setMode(int mode) { mMode = mode; MagnificationCapabilities.setCapabilities(mContext, mMode); mModePreference.setSummary( MagnificationCapabilities.getSummary(mContext, mMode)); } private void onMagnificationModeSelected(AdapterView parent, View view, int position, long id) { final MagnificationModeInfo modeInfo = (MagnificationModeInfo) mMagnificationModesListView.getItemAtPosition( position); if (modeInfo.mMagnificationMode == mMode) { return; } mMode = modeInfo.mMagnificationMode; if (isTripleTapEnabled(mContext) && mMode != MagnificationMode.FULLSCREEN) { mDialogHelper.showDialog(DIALOG_MAGNIFICATION_SWITCH_SHORTCUT); } } private int computeSelectionIndex() { final int modesSize = mModeInfos.size(); for (int i = 0; i < modesSize; i++) { if (mModeInfos.get(i).mMagnificationMode == mMode) { return i + mMagnificationModesListView.getHeaderViewsCount(); } } Log.w(TAG, "computeSelectionIndex failed"); return 0; } @VisibleForTesting static boolean isTripleTapEnabled(Context context) { return Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF) == ON; } private Dialog createMagnificationShortCutConfirmDialog() { return AccessibilityDialogUtils.createMagnificationSwitchShortcutDialog(mContext, this::onSwitchShortcutDialogButtonClicked); } @VisibleForTesting void onSwitchShortcutDialogButtonClicked(@CustomButton int which) { optOutMagnificationFromTripleTap(); //TODO(b/147990389): Merge this function into AccessibilityUtils after the format of // magnification target is changed to ComponentName. optInMagnificationToAccessibilityButton(); } private void optOutMagnificationFromTripleTap() { Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF); } private void optInMagnificationToAccessibilityButton() { final String targetKey = Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS; final String targetString = Settings.Secure.getString(mContext.getContentResolver(), targetKey); if (targetString != null && targetString.contains(MAGNIFICATION_CONTROLLER_NAME)) { return; } final StringJoiner joiner = new StringJoiner(String.valueOf(COMPONENT_NAME_SEPARATOR)); if (!TextUtils.isEmpty(targetString)) { joiner.add(targetString); } joiner.add(MAGNIFICATION_CONTROLLER_NAME); Settings.Secure.putString(mContext.getContentResolver(), targetKey, joiner.toString()); } // TODO(b/186731461): Remove it when this controller is used in DashBoardFragment only. @Override public void onResume() { updateState(mModePreference); } /** * An interface to help the delegate to show the dialog. It will be injected to the delegate. */ interface DialogHelper extends DialogCreatable { void showDialog(int dialogId); void setDialogDelegate(DialogCreatable delegate); } @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; } } }