diff --git a/src/com/android/settings/notification/SoundSettings.java b/src/com/android/settings/notification/SoundSettings.java index b0d5d2a6d86..4575708bbbd 100644 --- a/src/com/android/settings/notification/SoundSettings.java +++ b/src/com/android/settings/notification/SoundSettings.java @@ -40,11 +40,11 @@ import com.android.settings.dashboard.DashboardFragment; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.sound.HandsFreeProfileOutputPreferenceController; import com.android.settings.widget.PreferenceCategoryController; +import com.android.settings.widget.UpdatableListPreferenceDialogFragment; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.instrumentation.Instrumentable; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.search.SearchIndexable; -import com.android.settingslib.widget.UpdatableListPreferenceDialogFragment; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/com/android/settings/widget/UpdatableListPreferenceDialogFragment.java b/src/com/android/settings/widget/UpdatableListPreferenceDialogFragment.java new file mode 100644 index 00000000000..749b2e39af1 --- /dev/null +++ b/src/com/android/settings/widget/UpdatableListPreferenceDialogFragment.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2023 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.res.TypedArray; +import android.os.Bundle; +import android.widget.ArrayAdapter; + +import androidx.annotation.VisibleForTesting; +import androidx.appcompat.app.AlertDialog.Builder; +import androidx.preference.ListPreference; +import androidx.preference.PreferenceDialogFragmentCompat; + +import com.android.settingslib.core.instrumentation.Instrumentable; + +import java.util.ArrayList; + +/** + * {@link PreferenceDialogFragmentCompat} that updates the available options + * when {@code onListPreferenceUpdated} is called." + */ +public class UpdatableListPreferenceDialogFragment extends PreferenceDialogFragmentCompat implements + Instrumentable { + + private static final String SAVE_STATE_INDEX = "UpdatableListPreferenceDialogFragment.index"; + private static final String SAVE_STATE_ENTRIES = + "UpdatableListPreferenceDialogFragment.entries"; + private static final String SAVE_STATE_ENTRY_VALUES = + "UpdatableListPreferenceDialogFragment.entryValues"; + private static final String METRICS_CATEGORY_KEY = "metrics_category_key"; + private ArrayAdapter mAdapter; + private int mClickedDialogEntryIndex; + private ArrayList mEntries; + private CharSequence[] mEntryValues; + private int mMetricsCategory = METRICS_CATEGORY_UNKNOWN; + + /** + * Creates a new instance of {@link UpdatableListPreferenceDialogFragment}. + */ + public static UpdatableListPreferenceDialogFragment newInstance( + String key, int metricsCategory) { + UpdatableListPreferenceDialogFragment fragment = + new UpdatableListPreferenceDialogFragment(); + Bundle args = new Bundle(1); + args.putString(ARG_KEY, key); + args.putInt(METRICS_CATEGORY_KEY, metricsCategory); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final Bundle bundle = getArguments(); + mMetricsCategory = + bundle.getInt(METRICS_CATEGORY_KEY, METRICS_CATEGORY_UNKNOWN); + if (savedInstanceState == null) { + mEntries = new ArrayList<>(); + setPreferenceData(getListPreference()); + } else { + mClickedDialogEntryIndex = savedInstanceState.getInt(SAVE_STATE_INDEX, 0); + mEntries = savedInstanceState.getCharSequenceArrayList(SAVE_STATE_ENTRIES); + mEntryValues = + savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(SAVE_STATE_INDEX, mClickedDialogEntryIndex); + outState.putCharSequenceArrayList(SAVE_STATE_ENTRIES, mEntries); + outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues); + } + + @Override + public void onDialogClosed(boolean positiveResult) { + if (positiveResult && mClickedDialogEntryIndex >= 0) { + final ListPreference preference = getListPreference(); + final String value = mEntryValues[mClickedDialogEntryIndex].toString(); + if (preference.callChangeListener(value)) { + preference.setValue(value); + } + } + } + + @VisibleForTesting + void setAdapter(ArrayAdapter adapter) { + mAdapter = adapter; + } + + @VisibleForTesting + void setEntries(ArrayList entries) { + mEntries = entries; + } + + @VisibleForTesting + ArrayAdapter getAdapter() { + return mAdapter; + } + + @VisibleForTesting + void setMetricsCategory(Bundle bundle) { + mMetricsCategory = + bundle.getInt(METRICS_CATEGORY_KEY, METRICS_CATEGORY_UNKNOWN); + } + + @Override + protected void onPrepareDialogBuilder(Builder builder) { + super.onPrepareDialogBuilder(builder); + final TypedArray a = getContext().obtainStyledAttributes( + null, + com.android.internal.R.styleable.AlertDialog, + com.android.internal.R.attr.alertDialogStyle, 0); + + mAdapter = new ArrayAdapter<>( + getContext(), + a.getResourceId( + com.android.internal.R.styleable.AlertDialog_singleChoiceItemLayout, + com.android.internal.R.layout.select_dialog_singlechoice), + mEntries); + + builder.setSingleChoiceItems(mAdapter, mClickedDialogEntryIndex, + (dialog, which) -> { + mClickedDialogEntryIndex = which; + onClick(dialog, -1); + dialog.dismiss(); + }); + builder.setPositiveButton(null, null); + a.recycle(); + } + + @Override + public int getMetricsCategory() { + return mMetricsCategory; + } + + @VisibleForTesting + ListPreference getListPreference() { + return (ListPreference) getPreference(); + } + + private void setPreferenceData(ListPreference preference) { + mEntries.clear(); + mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue()); + for (CharSequence entry : preference.getEntries()) { + mEntries.add(entry); + } + mEntryValues = preference.getEntryValues(); + } + + /** + * Update new data set for list preference. + */ + public void onListPreferenceUpdated(ListPreference preference) { + if (mAdapter != null) { + setPreferenceData(preference); + mAdapter.notifyDataSetChanged(); + } + } +} diff --git a/tests/robotests/src/com/android/settings/widget/UpdatableListPreferenceDialogFragmentTest.java b/tests/robotests/src/com/android/settings/widget/UpdatableListPreferenceDialogFragmentTest.java new file mode 100644 index 00000000000..1c2625350c6 --- /dev/null +++ b/tests/robotests/src/com/android/settings/widget/UpdatableListPreferenceDialogFragmentTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2023 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.widget.ArrayAdapter; + +import androidx.preference.ListPreference; + +import com.android.internal.logging.nano.MetricsProto; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.util.ArrayList; + +@RunWith(RobolectricTestRunner.class) +public class UpdatableListPreferenceDialogFragmentTest { + + private static final String KEY = "Test_Key"; + @Mock + private UpdatableListPreferenceDialogFragment mUpdatableListPrefDlgFragment; + private Context mContext; + private ArrayAdapter mAdapter; + private ArrayList mEntries; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + + mUpdatableListPrefDlgFragment = spy(UpdatableListPreferenceDialogFragment + .newInstance(KEY, MetricsProto.MetricsEvent.DIALOG_SWITCH_A2DP_DEVICES)); + mEntries = new ArrayList<>(); + mUpdatableListPrefDlgFragment.setEntries(mEntries); + mUpdatableListPrefDlgFragment + .setMetricsCategory(mUpdatableListPrefDlgFragment.getArguments()); + initAdapter(); + } + + private void initAdapter() { + mAdapter = spy(new ArrayAdapter<>( + mContext, + com.android.internal.R.layout.select_dialog_singlechoice, + mEntries)); + mUpdatableListPrefDlgFragment.setAdapter(mAdapter); + } + + @Test + public void getMetricsCategory() { + assertThat(mUpdatableListPrefDlgFragment.getMetricsCategory()) + .isEqualTo(MetricsProto.MetricsEvent.DIALOG_SWITCH_A2DP_DEVICES); + } + + @Test + public void onListPreferenceUpdated_verifyAdapterCanBeUpdate() { + assertThat(mUpdatableListPrefDlgFragment.getAdapter().getCount()).isEqualTo(0); + + ListPreference listPreference = new ListPreference(mContext); + final CharSequence[] charSequences = {"Test_DEVICE_1", "Test_DEVICE_2"}; + listPreference.setEntries(charSequences); + mUpdatableListPrefDlgFragment.onListPreferenceUpdated(listPreference); + + assertThat(mUpdatableListPrefDlgFragment.getAdapter().getCount()).isEqualTo(2); + } + + @Test + public void onDialogClosed_emptyPreference() { + mUpdatableListPrefDlgFragment.onDialogClosed(false); + + verify(mUpdatableListPrefDlgFragment, never()).getListPreference(); + } +}