1 /*
2  * Copyright (C) 2013 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 
17 package com.android.settings.accessibility;
18 
19 import android.app.AlertDialog;
20 import android.app.Dialog;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.content.res.TypedArray;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.util.AttributeSet;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.widget.AbsListView;
31 import android.widget.AdapterView;
32 import android.widget.BaseAdapter;
33 
34 import com.android.settings.CustomDialogPreference;
35 
36 /**
37  * Abstract dialog preference that displays a set of values and optional titles.
38  */
39 public abstract class ListDialogPreference extends CustomDialogPreference {
40     private CharSequence[] mEntryTitles;
41     private int[] mEntryValues;
42 
43     private OnValueChangedListener mOnValueChangedListener;
44 
45     /** The layout resource to use for grid items. */
46     private int mListItemLayout;
47 
48     /** The current value of this preference. */
49     private int mValue;
50 
51     /** The index within the value set of the current value. */
52     private int mValueIndex;
53 
54     /** Whether the value had been set using {@link #setValue}. */
55     private boolean mValueSet;
56 
ListDialogPreference(Context context, AttributeSet attrs)57     public ListDialogPreference(Context context, AttributeSet attrs) {
58         super(context, attrs);
59     }
60 
61     /**
62      * Sets a listened to invoke when the value of this preference changes.
63      *
64      * @param listener the listener to invoke
65      */
setOnValueChangedListener(OnValueChangedListener listener)66     public void setOnValueChangedListener(OnValueChangedListener listener) {
67         mOnValueChangedListener = listener;
68     }
69 
70     /**
71      * Sets the layout to use for grid items.
72      *
73      * @param layoutResId the layout to use for displaying grid items
74      */
setListItemLayoutResource(int layoutResId)75     public void setListItemLayoutResource(int layoutResId) {
76         mListItemLayout = layoutResId;
77     }
78 
79     /**
80      * Sets the list of item values. Values must be distinct.
81      *
82      * @param values the list of item values
83      */
setValues(int[] values)84     public void setValues(int[] values) {
85         mEntryValues = values;
86 
87         if (mValueSet && mValueIndex == AbsListView.INVALID_POSITION) {
88             mValueIndex = getIndexForValue(mValue);
89         }
90     }
91 
92     /**
93      * Sets the list of item titles. May be null if no titles are specified, or
94      * may be shorter than the list of values to leave some titles unspecified.
95      *
96      * @param titles the list of item titles
97      */
setTitles(CharSequence[] titles)98     public void setTitles(CharSequence[] titles) {
99         mEntryTitles = titles;
100     }
101 
102     /**
103      * Populates a list item view with data for the specified index.
104      *
105      * @param view the view to populate
106      * @param index the index for which to populate the view
107      * @see #setListItemLayoutResource(int)
108      * @see #getValueAt(int)
109      * @see #getTitleAt(int)
110      */
onBindListItem(View view, int index)111     protected abstract void onBindListItem(View view, int index);
112 
113     /**
114      * @return the title at the specified index, or null if none specified
115      */
getTitleAt(int index)116     protected CharSequence getTitleAt(int index) {
117         if (mEntryTitles == null || mEntryTitles.length <= index) {
118             return null;
119         }
120 
121         return mEntryTitles[index];
122     }
123 
124     /**
125      * @return the value at the specified index
126      */
getValueAt(int index)127     protected int getValueAt(int index) {
128         return mEntryValues[index];
129     }
130 
131     @Override
getSummary()132     public CharSequence getSummary() {
133         if (mValueIndex >= 0) {
134             return getTitleAt(mValueIndex);
135         }
136 
137         return null;
138     }
139 
140     @Override
onPrepareDialogBuilder(AlertDialog.Builder builder, DialogInterface.OnClickListener listener)141     protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
142             DialogInterface.OnClickListener listener) {
143         super.onPrepareDialogBuilder(builder, listener);
144 
145         final Context context = getContext();
146         final int dialogLayout = getDialogLayoutResource();
147         final View picker = LayoutInflater.from(context).inflate(dialogLayout, null);
148         final ListPreferenceAdapter adapter = new ListPreferenceAdapter();
149         final AbsListView list = (AbsListView) picker.findViewById(android.R.id.list);
150         list.setAdapter(adapter);
151         list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
152             @Override
153             public void onItemClick(AdapterView<?> adapter, View v, int position, long id) {
154                 if (callChangeListener((int) id)) {
155                     setValue((int) id);
156                 }
157 
158                 final Dialog dialog = getDialog();
159                 if (dialog != null) {
160                     dialog.dismiss();
161                 }
162             }
163         });
164 
165         // Set initial selection.
166         final int selectedPosition = getIndexForValue(mValue);
167         if (selectedPosition != AbsListView.INVALID_POSITION) {
168             list.setSelection(selectedPosition);
169         }
170 
171         builder.setView(picker);
172         builder.setPositiveButton(null, null);
173     }
174 
175     /**
176      * @return the index of the specified value within the list of entry values,
177      *         or {@link AbsListView#INVALID_POSITION} if not found
178      */
getIndexForValue(int value)179     protected int getIndexForValue(int value) {
180         final int[] values = mEntryValues;
181         if (values != null) {
182             final int count = values.length;
183             for (int i = 0; i < count; i++) {
184                 if (values[i] == value) {
185                     return i;
186                 }
187             }
188         }
189 
190         return AbsListView.INVALID_POSITION;
191     }
192 
193     /**
194      * Sets the current value. If the value exists within the set of entry
195      * values, updates the selection index.
196      *
197      * @param value the value to set
198      */
setValue(int value)199     public void setValue(int value) {
200         final boolean changed = mValue != value;
201         if (changed || !mValueSet) {
202             mValue = value;
203             mValueIndex = getIndexForValue(value);
204             mValueSet = true;
205             persistInt(value);
206             if (changed) {
207                 notifyDependencyChange(shouldDisableDependents());
208                 notifyChanged();
209             }
210             if (mOnValueChangedListener != null) {
211                 mOnValueChangedListener.onValueChanged(this, value);
212             }
213         }
214     }
215 
216     /**
217      * @return the current value
218      */
getValue()219     public int getValue() {
220         return mValue;
221     }
222 
223     @Override
onGetDefaultValue(TypedArray a, int index)224     protected Object onGetDefaultValue(TypedArray a, int index) {
225         return a.getInt(index, 0);
226     }
227 
228     @Override
onSetInitialValue(boolean restoreValue, Object defaultValue)229     protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
230         setValue(restoreValue ? getPersistedInt(mValue) : (Integer) defaultValue);
231     }
232 
233     @Override
onSaveInstanceState()234     protected Parcelable onSaveInstanceState() {
235         final Parcelable superState = super.onSaveInstanceState();
236         if (isPersistent()) {
237             // No need to save instance state since it's persistent
238             return superState;
239         }
240 
241         final SavedState myState = new SavedState(superState);
242         myState.value = getValue();
243         return myState;
244     }
245 
246     @Override
onRestoreInstanceState(Parcelable state)247     protected void onRestoreInstanceState(Parcelable state) {
248         if (state == null || !state.getClass().equals(SavedState.class)) {
249             // Didn't save state for us in onSaveInstanceState
250             super.onRestoreInstanceState(state);
251             return;
252         }
253 
254         SavedState myState = (SavedState) state;
255         super.onRestoreInstanceState(myState.getSuperState());
256         setValue(myState.value);
257     }
258 
259     private class ListPreferenceAdapter extends BaseAdapter {
260         private LayoutInflater mInflater;
261 
262         @Override
getCount()263         public int getCount() {
264             return mEntryValues.length;
265         }
266 
267         @Override
getItem(int position)268         public Integer getItem(int position) {
269             return mEntryValues[position];
270         }
271 
272         @Override
getItemId(int position)273         public long getItemId(int position) {
274             return mEntryValues[position];
275         }
276 
277         @Override
hasStableIds()278         public boolean hasStableIds() {
279             return true;
280         }
281 
282         @Override
getView(int position, View convertView, ViewGroup parent)283         public View getView(int position, View convertView, ViewGroup parent) {
284             if (convertView == null) {
285                 if (mInflater == null) {
286                     mInflater = LayoutInflater.from(parent.getContext());
287                 }
288                 convertView = mInflater.inflate(mListItemLayout, parent, false);
289             }
290             onBindListItem(convertView, position);
291             return convertView;
292         }
293     }
294 
295     private static class SavedState extends BaseSavedState {
296         public int value;
297 
SavedState(Parcel source)298         public SavedState(Parcel source) {
299             super(source);
300             value = source.readInt();
301         }
302 
303         @Override
writeToParcel(Parcel dest, int flags)304         public void writeToParcel(Parcel dest, int flags) {
305             super.writeToParcel(dest, flags);
306             dest.writeInt(value);
307         }
308 
SavedState(Parcelable superState)309         public SavedState(Parcelable superState) {
310             super(superState);
311         }
312 
313         @SuppressWarnings({ "hiding", "unused" })
314         public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
315             @Override
316             public SavedState createFromParcel(Parcel in) {
317                 return new SavedState(in);
318             }
319 
320             @Override
321             public SavedState[] newArray(int size) {
322                 return new SavedState[size];
323             }
324         };
325     }
326 
327     public interface OnValueChangedListener {
onValueChanged(ListDialogPreference preference, int value)328         public void onValueChanged(ListDialogPreference preference, int value);
329     }
330 }
331