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
17package com.android.settings.accessibility;
18
19import android.app.AlertDialog.Builder;
20import android.app.Dialog;
21import android.content.Context;
22import android.content.res.TypedArray;
23import android.os.Parcel;
24import android.os.Parcelable;
25import android.preference.DialogPreference;
26import android.util.AttributeSet;
27import android.view.LayoutInflater;
28import android.view.View;
29import android.view.ViewGroup;
30import android.widget.AbsListView;
31import android.widget.AdapterView;
32import android.widget.AdapterView.OnItemClickListener;
33import android.widget.BaseAdapter;
34
35/**
36 * Abstract dialog preference that displays a set of values and optional titles.
37 */
38public 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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
126    protected int getValueAt(int index) {
127        return mEntryValues[index];
128    }
129
130    @Override
131    public CharSequence getSummary() {
132        if (mValueIndex >= 0) {
133            return getTitleAt(mValueIndex);
134        }
135
136        return null;
137    }
138
139    @Override
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     */
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     */
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     */
217    public int getValue() {
218        return mValue;
219    }
220
221    @Override
222    protected Object onGetDefaultValue(TypedArray a, int index) {
223        return a.getInt(index, 0);
224    }
225
226    @Override
227    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
228        setValue(restoreValue ? getPersistedInt(mValue) : (Integer) defaultValue);
229    }
230
231    @Override
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
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
261        public int getCount() {
262            return mEntryValues.length;
263        }
264
265        @Override
266        public Integer getItem(int position) {
267            return mEntryValues[position];
268        }
269
270        @Override
271        public long getItemId(int position) {
272            return mEntryValues[position];
273        }
274
275        @Override
276        public boolean hasStableIds() {
277            return true;
278        }
279
280        @Override
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
296        public SavedState(Parcel source) {
297            super(source);
298            value = source.readInt();
299        }
300
301        @Override
302        public void writeToParcel(Parcel dest, int flags) {
303            super.writeToParcel(dest, flags);
304            dest.writeInt(value);
305        }
306
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 {
326        public void onValueChanged(ListDialogPreference preference, int value);
327    }
328}
329