1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.settings.widget;
17 
18 import android.content.res.TypedArray;
19 import android.os.Bundle;
20 import android.widget.ArrayAdapter;
21 
22 import androidx.annotation.VisibleForTesting;
23 import androidx.appcompat.app.AlertDialog.Builder;
24 import androidx.preference.ListPreference;
25 import androidx.preference.PreferenceDialogFragmentCompat;
26 
27 import com.android.settingslib.core.instrumentation.Instrumentable;
28 
29 import java.util.ArrayList;
30 
31 /**
32  * {@link PreferenceDialogFragmentCompat} that updates the available options
33  * when {@code onListPreferenceUpdated} is called."
34  */
35 public class UpdatableListPreferenceDialogFragment extends PreferenceDialogFragmentCompat implements
36         Instrumentable {
37 
38     private static final String SAVE_STATE_INDEX = "UpdatableListPreferenceDialogFragment.index";
39     private static final String SAVE_STATE_ENTRIES =
40             "UpdatableListPreferenceDialogFragment.entries";
41     private static final String SAVE_STATE_ENTRY_VALUES =
42             "UpdatableListPreferenceDialogFragment.entryValues";
43     private static final String METRICS_CATEGORY_KEY = "metrics_category_key";
44     private ArrayAdapter mAdapter;
45     private int mClickedDialogEntryIndex;
46     private ArrayList<CharSequence> mEntries;
47     private CharSequence[] mEntryValues;
48     private int mMetricsCategory = METRICS_CATEGORY_UNKNOWN;
49 
50     /**
51      * Creates a new instance of {@link UpdatableListPreferenceDialogFragment}.
52      */
newInstance( String key, int metricsCategory)53     public static UpdatableListPreferenceDialogFragment newInstance(
54             String key, int metricsCategory) {
55         UpdatableListPreferenceDialogFragment fragment =
56                 new UpdatableListPreferenceDialogFragment();
57         Bundle args = new Bundle(1);
58         args.putString(ARG_KEY, key);
59         args.putInt(METRICS_CATEGORY_KEY, metricsCategory);
60         fragment.setArguments(args);
61         return fragment;
62     }
63 
64     @Override
onCreate(Bundle savedInstanceState)65     public void onCreate(Bundle savedInstanceState) {
66         super.onCreate(savedInstanceState);
67         final Bundle bundle = getArguments();
68         mMetricsCategory =
69                 bundle.getInt(METRICS_CATEGORY_KEY, METRICS_CATEGORY_UNKNOWN);
70         if (savedInstanceState == null) {
71             mEntries = new ArrayList<>();
72             setPreferenceData(getListPreference());
73         } else {
74             mClickedDialogEntryIndex = savedInstanceState.getInt(SAVE_STATE_INDEX, 0);
75             mEntries = savedInstanceState.getCharSequenceArrayList(SAVE_STATE_ENTRIES);
76             mEntryValues =
77                     savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES);
78         }
79     }
80 
81     @Override
onSaveInstanceState(Bundle outState)82     public void onSaveInstanceState(Bundle outState) {
83         super.onSaveInstanceState(outState);
84         outState.putInt(SAVE_STATE_INDEX, mClickedDialogEntryIndex);
85         outState.putCharSequenceArrayList(SAVE_STATE_ENTRIES, mEntries);
86         outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues);
87     }
88 
89     @Override
onDialogClosed(boolean positiveResult)90     public void onDialogClosed(boolean positiveResult) {
91         if (positiveResult && mClickedDialogEntryIndex >= 0) {
92             final ListPreference preference = getListPreference();
93             final String value = mEntryValues[mClickedDialogEntryIndex].toString();
94             if (preference.callChangeListener(value)) {
95                 preference.setValue(value);
96             }
97         }
98     }
99 
100     @VisibleForTesting
setAdapter(ArrayAdapter adapter)101     void setAdapter(ArrayAdapter adapter) {
102         mAdapter = adapter;
103     }
104 
105     @VisibleForTesting
setEntries(ArrayList<CharSequence> entries)106     void setEntries(ArrayList<CharSequence> entries) {
107         mEntries = entries;
108     }
109 
110     @VisibleForTesting
getAdapter()111     ArrayAdapter getAdapter() {
112         return mAdapter;
113     }
114 
115     @VisibleForTesting
setMetricsCategory(Bundle bundle)116     void setMetricsCategory(Bundle bundle) {
117         mMetricsCategory =
118                 bundle.getInt(METRICS_CATEGORY_KEY, METRICS_CATEGORY_UNKNOWN);
119     }
120 
121     @Override
onPrepareDialogBuilder(Builder builder)122     protected void onPrepareDialogBuilder(Builder builder) {
123         super.onPrepareDialogBuilder(builder);
124         final TypedArray a = getContext().obtainStyledAttributes(
125                 null,
126                 com.android.internal.R.styleable.AlertDialog,
127                 com.android.internal.R.attr.alertDialogStyle, 0);
128 
129         mAdapter = new ArrayAdapter<>(
130                 getContext(),
131                 a.getResourceId(
132                         com.android.internal.R.styleable.AlertDialog_singleChoiceItemLayout,
133                         com.android.internal.R.layout.select_dialog_singlechoice),
134                 mEntries);
135 
136         builder.setSingleChoiceItems(mAdapter, mClickedDialogEntryIndex,
137                 (dialog, which) -> {
138                     mClickedDialogEntryIndex = which;
139                     onClick(dialog, -1);
140                     dialog.dismiss();
141                 });
142         builder.setPositiveButton(null, null);
143         a.recycle();
144     }
145 
146     @Override
getMetricsCategory()147     public int getMetricsCategory() {
148         return mMetricsCategory;
149     }
150 
151     @VisibleForTesting
getListPreference()152     ListPreference getListPreference() {
153         return (ListPreference) getPreference();
154     }
155 
setPreferenceData(ListPreference preference)156     private void setPreferenceData(ListPreference preference) {
157         mEntries.clear();
158         mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue());
159         for (CharSequence entry : preference.getEntries()) {
160             mEntries.add(entry);
161         }
162         mEntryValues = preference.getEntryValues();
163     }
164 
165     /**
166      * Update new data set for list preference.
167      */
onListPreferenceUpdated(ListPreference preference)168     public void onListPreferenceUpdated(ListPreference preference) {
169         if (mAdapter != null) {
170             setPreferenceData(preference);
171             mAdapter.notifyDataSetChanged();
172         }
173     }
174 }
175