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