blob: 06666f459e5df269d0807d0018f46a03d8a6fea4 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 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 android.preference;
18
Scott Kennedy3c32b622015-02-22 16:58:58 -080019import android.annotation.Nullable;
Tor Norbye7b9c9122013-05-30 16:48:33 -070020import android.annotation.StringRes;
21import android.annotation.XmlRes;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070022import android.app.Fragment;
Dianne Hackbornc6669ca2010-09-16 01:33:24 -070023import android.app.FragmentBreadCrumbs;
Dianne Hackborn3a57fb92010-11-15 17:58:52 -080024import android.app.FragmentManager;
Andrew Stadleraa904f42010-09-02 14:50:08 -070025import android.app.FragmentTransaction;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.app.ListActivity;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070027import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.content.Intent;
Dianne Hackborn50ed8292010-12-03 12:30:21 -080029import android.content.res.Resources;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070030import android.content.res.TypedArray;
31import android.content.res.XmlResourceParser;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.os.Bundle;
33import android.os.Handler;
34import android.os.Message;
Dianne Hackborna21e3da2010-09-12 19:27:46 -070035import android.os.Parcel;
36import android.os.Parcelable;
Freeman Ng19ea2e02010-03-25 15:09:00 -070037import android.text.TextUtils;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070038import android.util.AttributeSet;
Dianne Hackborn50ed8292010-12-03 12:30:21 -080039import android.util.TypedValue;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070040import android.util.Xml;
41import android.view.LayoutInflater;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042import android.view.View;
Amith Yamasani405c1af2011-05-26 13:08:25 -070043import android.view.View.OnClickListener;
Gilles Debunne39725ac2011-06-14 18:52:41 -070044import android.view.ViewGroup;
Dianne Hackborna21e3da2010-09-12 19:27:46 -070045import android.widget.AbsListView;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070046import android.widget.ArrayAdapter;
Gilles Debunne39725ac2011-06-14 18:52:41 -070047import android.widget.BaseAdapter;
Freeman Ng19ea2e02010-03-25 15:09:00 -070048import android.widget.Button;
Dianne Hackborn5c769a42010-08-26 17:08:08 -070049import android.widget.FrameLayout;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070050import android.widget.ImageView;
51import android.widget.ListView;
52import android.widget.TextView;
53
Gilles Debunne39725ac2011-06-14 18:52:41 -070054import com.android.internal.util.XmlUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055
Amith Yamasani405c1af2011-05-26 13:08:25 -070056import org.xmlpull.v1.XmlPullParser;
57import org.xmlpull.v1.XmlPullParserException;
58
Gilles Debunne39725ac2011-06-14 18:52:41 -070059import java.io.IOException;
60import java.util.ArrayList;
61import java.util.List;
62
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063/**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070064 * This is the base class for an activity to show a hierarchy of preferences
65 * to the user. Prior to {@link android.os.Build.VERSION_CODES#HONEYCOMB}
66 * this class only allowed the display of a single set of preference; this
67 * functionality should now be found in the new {@link PreferenceFragment}
68 * class. If you are using PreferenceActivity in its old mode, the documentation
69 * there applies to the deprecated APIs here.
Freeman Ng19ea2e02010-03-25 15:09:00 -070070 *
Gilles Debunne39725ac2011-06-14 18:52:41 -070071 * <p>This activity shows one or more headers of preferences, each of which
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070072 * is associated with a {@link PreferenceFragment} to display the preferences
73 * of that header. The actual layout and display of these associations can
74 * however vary; currently there are two major approaches it may take:
75 *
76 * <ul>
77 * <li>On a small screen it may display only the headers as a single list
78 * when first launched. Selecting one of the header items will re-launch
79 * the activity with it only showing the PreferenceFragment of that header.
80 * <li>On a large screen in may display both the headers and current
81 * PreferenceFragment together as panes. Selecting a header item switches
82 * to showing the correct PreferenceFragment for that item.
83 * </ul>
84 *
85 * <p>Subclasses of PreferenceActivity should implement
86 * {@link #onBuildHeaders} to populate the header list with the desired
87 * items. Doing this implicitly switches the class into its new "headers
88 * + fragments" mode rather than the old style of just showing a single
89 * preferences list.
Scott Maincdd0c592012-07-26 17:03:51 -070090 *
91 * <div class="special reference">
92 * <h3>Developer Guides</h3>
93 * <p>For information about using {@code PreferenceActivity},
94 * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>
95 * guide.</p>
96 * </div>
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070097 *
98 * <a name="SampleCode"></a>
99 * <h3>Sample Code</h3>
100 *
101 * <p>The following sample code shows a simple preference activity that
102 * has two different sets of preferences. The implementation, consisting
103 * of the activity itself as well as its two preference fragments is:</p>
104 *
105 * {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/PreferenceWithHeaders.java
106 * activity}
107 *
108 * <p>The preference_headers resource describes the headers to be displayed
109 * and the fragments associated with them. It is:
110 *
111 * {@sample development/samples/ApiDemos/res/xml/preference_headers.xml headers}
112 *
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700113 * <p>The first header is shown by Prefs1Fragment, which populates itself
114 * from the following XML resource:</p>
115 *
116 * {@sample development/samples/ApiDemos/res/xml/fragmented_preferences.xml preferences}
117 *
118 * <p>Note that this XML resource contains a preference screen holding another
119 * fragment, the Prefs1FragmentInner implemented here. This allows the user
120 * to traverse down a hierarchy of preferences; pressing back will pop each
121 * fragment off the stack to return to the previous preferences.
122 *
123 * <p>See {@link PreferenceFragment} for information on implementing the
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700124 * fragments themselves.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 */
126public abstract class PreferenceActivity extends ListActivity implements
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700127 PreferenceManager.OnPreferenceTreeClickListener,
128 PreferenceFragment.OnPreferenceStartFragmentCallback {
Freeman Ng19ea2e02010-03-25 15:09:00 -0700129
Svetoslav Ganov8fc552e2013-11-21 19:36:22 +0000130 private static final String TAG = "PreferenceActivity";
131
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700132 // Constants for state save/restore
133 private static final String HEADERS_TAG = ":android:headers";
134 private static final String CUR_HEADER_TAG = ":android:cur_header";
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700135 private static final String PREFERENCES_TAG = ":android:preferences";
Freeman Ng19ea2e02010-03-25 15:09:00 -0700136
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700137 /**
138 * When starting this activity, the invoking Intent can contain this extra
139 * string to specify which fragment should be initially displayed.
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700140 * <p/>Starting from Key Lime Pie, when this argument is passed in, the PreferenceActivity
141 * will call isValidFragment() to confirm that the fragment class name is valid for this
142 * activity.
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700143 */
144 public static final String EXTRA_SHOW_FRAGMENT = ":android:show_fragment";
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700145
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700146 /**
147 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
Dianne Hackborne72f2372011-03-16 10:43:18 -0700148 * this extra can also be specified to supply a Bundle of arguments to pass
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700149 * to that fragment when it is instantiated during the initial creation
150 * of PreferenceActivity.
151 */
152 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":android:show_fragment_args";
153
154 /**
Dianne Hackborne72f2372011-03-16 10:43:18 -0700155 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
156 * this extra can also be specify to supply the title to be shown for
157 * that fragment.
158 */
159 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":android:show_fragment_title";
160
161 /**
162 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
163 * this extra can also be specify to supply the short title to be shown for
164 * that fragment.
165 */
166 public static final String EXTRA_SHOW_FRAGMENT_SHORT_TITLE
167 = ":android:show_fragment_short_title";
168
169 /**
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700170 * When starting this activity, the invoking Intent can contain this extra
171 * boolean that the header list should not be displayed. This is most often
172 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
173 * the activity to display a specific fragment that the user has navigated
174 * to.
175 */
176 public static final String EXTRA_NO_HEADERS = ":android:no_headers";
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700177
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700178 private static final String BACK_STACK_PREFS = ":android:prefs";
179
Freeman Ng19ea2e02010-03-25 15:09:00 -0700180 // extras that allow any preference activity to be launched as part of a wizard
181
182 // show Back and Next buttons? takes boolean parameter
183 // Back will then return RESULT_CANCELED and Next RESULT_OK
184 private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
185
Freeman Ng09dbf182010-08-11 15:45:01 -0700186 // add a Skip button?
187 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
188
Freeman Ng19ea2e02010-03-25 15:09:00 -0700189 // specify custom text for the Back or Next buttons, or cause a button to not appear
190 // at all by setting it to null
191 private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
192 private static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
193
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700194 // --- State for new mode when showing a list of headers + prefs fragment
195
196 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
197
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700198 private FrameLayout mListFooter;
199
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800200 private ViewGroup mPrefsContainer;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700201
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700202 private FragmentBreadCrumbs mFragmentBreadCrumbs;
203
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700204 private boolean mSinglePane;
205
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700206 private Header mCurHeader;
207
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700208 // --- State for old mode when showing a single preference list
Freeman Ng19ea2e02010-03-25 15:09:00 -0700209
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210 private PreferenceManager mPreferenceManager;
Freeman Ng19ea2e02010-03-25 15:09:00 -0700211
Adam Powelle7fea452010-03-18 14:51:39 -0700212 private Bundle mSavedInstanceState;
213
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700214 // --- Common state
215
216 private Button mNextButton;
217
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700218 private int mPreferenceHeaderItemResId = 0;
219 private boolean mPreferenceHeaderRemoveEmptyIcon = false;
220
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800221 /**
222 * The starting request code given out to preference framework.
223 */
224 private static final int FIRST_REQUEST_CODE = 100;
Freeman Ng19ea2e02010-03-25 15:09:00 -0700225
Dianne Hackborn3e449ce2010-09-11 20:52:31 -0700226 private static final int MSG_BIND_PREFERENCES = 1;
227 private static final int MSG_BUILD_HEADERS = 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800228 private Handler mHandler = new Handler() {
229 @Override
230 public void handleMessage(Message msg) {
231 switch (msg.what) {
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700232 case MSG_BIND_PREFERENCES: {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800233 bindPreferences();
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700234 } break;
235 case MSG_BUILD_HEADERS: {
236 ArrayList<Header> oldHeaders = new ArrayList<Header>(mHeaders);
237 mHeaders.clear();
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700238 onBuildHeaders(mHeaders);
Gilles Debunne39725ac2011-06-14 18:52:41 -0700239 if (mAdapter instanceof BaseAdapter) {
240 ((BaseAdapter) mAdapter).notifyDataSetChanged();
Andrew Stadler83681eb2010-11-04 15:49:05 -0700241 }
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700242 Header header = onGetNewHeader();
243 if (header != null && header.fragment != null) {
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700244 Header mappedHeader = findBestMatchingHeader(header, oldHeaders);
245 if (mappedHeader == null || mCurHeader != mappedHeader) {
246 switchToHeader(header);
247 }
248 } else if (mCurHeader != null) {
249 Header mappedHeader = findBestMatchingHeader(mCurHeader, mHeaders);
250 if (mappedHeader != null) {
251 setSelectedHeader(mappedHeader);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700252 }
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700253 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700254 } break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800255 }
256 }
257 };
258
Andrew Stadler468c3232010-08-17 16:16:42 -0700259 private static class HeaderAdapter extends ArrayAdapter<Header> {
260 private static class HeaderViewHolder {
261 ImageView icon;
262 TextView title;
263 TextView summary;
264 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700265
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700266 private LayoutInflater mInflater;
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700267 private int mLayoutResId;
268 private boolean mRemoveIconIfEmpty;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700269
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700270 public HeaderAdapter(Context context, List<Header> objects, int layoutResId,
271 boolean removeIconBehavior) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700272 super(context, 0, objects);
273 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700274 mLayoutResId = layoutResId;
275 mRemoveIconIfEmpty = removeIconBehavior;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700276 }
277
278 @Override
279 public View getView(int position, View convertView, ViewGroup parent) {
280 HeaderViewHolder holder;
281 View view;
282
283 if (convertView == null) {
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700284 view = mInflater.inflate(mLayoutResId, parent, false);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700285 holder = new HeaderViewHolder();
Andrew Stadler468c3232010-08-17 16:16:42 -0700286 holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon);
287 holder.title = (TextView) view.findViewById(com.android.internal.R.id.title);
288 holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700289 view.setTag(holder);
290 } else {
291 view = convertView;
Andrew Stadler468c3232010-08-17 16:16:42 -0700292 holder = (HeaderViewHolder) view.getTag();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700293 }
294
Andrew Stadler468c3232010-08-17 16:16:42 -0700295 // All view fields must be updated every time, because the view may be recycled
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700296 Header header = getItem(position);
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700297 if (mRemoveIconIfEmpty) {
298 if (header.iconRes == 0) {
299 holder.icon.setVisibility(View.GONE);
300 } else {
301 holder.icon.setVisibility(View.VISIBLE);
302 holder.icon.setImageResource(header.iconRes);
303 }
304 } else {
305 holder.icon.setImageResource(header.iconRes);
306 }
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800307 holder.title.setText(header.getTitle(getContext().getResources()));
308 CharSequence summary = header.getSummary(getContext().getResources());
309 if (!TextUtils.isEmpty(summary)) {
Andrew Stadler468c3232010-08-17 16:16:42 -0700310 holder.summary.setVisibility(View.VISIBLE);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800311 holder.summary.setText(summary);
312 } else {
313 holder.summary.setVisibility(View.GONE);
Andrew Stadler468c3232010-08-17 16:16:42 -0700314 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700315
316 return view;
317 }
318 }
319
320 /**
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700321 * Default value for {@link Header#id Header.id} indicating that no
322 * identifier value is set. All other values (including those below -1)
323 * are valid.
324 */
325 public static final long HEADER_ID_UNDEFINED = -1;
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700326
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700327 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700328 * Description of a single Header item that the user can select.
329 */
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700330 public static final class Header implements Parcelable {
331 /**
332 * Identifier for this header, to correlate with a new list when
333 * it is updated. The default value is
334 * {@link PreferenceActivity#HEADER_ID_UNDEFINED}, meaning no id.
335 * @attr ref android.R.styleable#PreferenceHeader_id
336 */
337 public long id = HEADER_ID_UNDEFINED;
338
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700339 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800340 * Resource ID of title of the header that is shown to the user.
341 * @attr ref android.R.styleable#PreferenceHeader_title
342 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700343 @StringRes
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800344 public int titleRes;
345
346 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700347 * Title of the header that is shown to the user.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700348 * @attr ref android.R.styleable#PreferenceHeader_title
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700349 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700350 public CharSequence title;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700351
352 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800353 * Resource ID of optional summary describing what this header controls.
354 * @attr ref android.R.styleable#PreferenceHeader_summary
355 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700356 @StringRes
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800357 public int summaryRes;
358
359 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700360 * Optional summary describing what this header controls.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700361 * @attr ref android.R.styleable#PreferenceHeader_summary
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700362 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700363 public CharSequence summary;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700364
365 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800366 * Resource ID of optional text to show as the title in the bread crumb.
367 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
368 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700369 @StringRes
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800370 public int breadCrumbTitleRes;
371
372 /**
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700373 * Optional text to show as the title in the bread crumb.
374 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
375 */
376 public CharSequence breadCrumbTitle;
377
378 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800379 * Resource ID of optional text to show as the short title in the bread crumb.
380 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
381 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700382 @StringRes
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800383 public int breadCrumbShortTitleRes;
384
385 /**
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700386 * Optional text to show as the short title in the bread crumb.
387 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
388 */
389 public CharSequence breadCrumbShortTitle;
390
391 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700392 * Optional icon resource to show for this header.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700393 * @attr ref android.R.styleable#PreferenceHeader_icon
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700394 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700395 public int iconRes;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700396
397 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700398 * Full class name of the fragment to display when this header is
399 * selected.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700400 * @attr ref android.R.styleable#PreferenceHeader_fragment
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700401 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700402 public String fragment;
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700403
404 /**
405 * Optional arguments to supply to the fragment when it is
406 * instantiated.
407 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700408 public Bundle fragmentArguments;
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700409
410 /**
411 * Intent to launch when the preference is selected.
412 */
413 public Intent intent;
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700414
415 /**
416 * Optional additional data for use by subclasses of PreferenceActivity.
417 */
418 public Bundle extras;
419
420 public Header() {
Gilles Debunne39725ac2011-06-14 18:52:41 -0700421 // Empty
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700422 }
423
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800424 /**
425 * Return the currently set title. If {@link #titleRes} is set,
426 * this resource is loaded from <var>res</var> and returned. Otherwise
427 * {@link #title} is returned.
428 */
429 public CharSequence getTitle(Resources res) {
430 if (titleRes != 0) {
431 return res.getText(titleRes);
432 }
433 return title;
434 }
435
436 /**
437 * Return the currently set summary. If {@link #summaryRes} is set,
438 * this resource is loaded from <var>res</var> and returned. Otherwise
439 * {@link #summary} is returned.
440 */
441 public CharSequence getSummary(Resources res) {
442 if (summaryRes != 0) {
443 return res.getText(summaryRes);
444 }
Dianne Hackborn9d071802010-12-08 14:49:15 -0800445 return summary;
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800446 }
447
448 /**
449 * Return the currently set bread crumb title. If {@link #breadCrumbTitleRes} is set,
450 * this resource is loaded from <var>res</var> and returned. Otherwise
451 * {@link #breadCrumbTitle} is returned.
452 */
453 public CharSequence getBreadCrumbTitle(Resources res) {
454 if (breadCrumbTitleRes != 0) {
455 return res.getText(breadCrumbTitleRes);
456 }
457 return breadCrumbTitle;
458 }
459
460 /**
461 * Return the currently set bread crumb short title. If
462 * {@link #breadCrumbShortTitleRes} is set,
463 * this resource is loaded from <var>res</var> and returned. Otherwise
464 * {@link #breadCrumbShortTitle} is returned.
465 */
466 public CharSequence getBreadCrumbShortTitle(Resources res) {
467 if (breadCrumbShortTitleRes != 0) {
468 return res.getText(breadCrumbShortTitleRes);
469 }
470 return breadCrumbShortTitle;
471 }
472
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700473 @Override
474 public int describeContents() {
475 return 0;
476 }
477
478 @Override
479 public void writeToParcel(Parcel dest, int flags) {
480 dest.writeLong(id);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800481 dest.writeInt(titleRes);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700482 TextUtils.writeToParcel(title, dest, flags);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800483 dest.writeInt(summaryRes);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700484 TextUtils.writeToParcel(summary, dest, flags);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800485 dest.writeInt(breadCrumbTitleRes);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700486 TextUtils.writeToParcel(breadCrumbTitle, dest, flags);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800487 dest.writeInt(breadCrumbShortTitleRes);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700488 TextUtils.writeToParcel(breadCrumbShortTitle, dest, flags);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700489 dest.writeInt(iconRes);
490 dest.writeString(fragment);
491 dest.writeBundle(fragmentArguments);
492 if (intent != null) {
493 dest.writeInt(1);
494 intent.writeToParcel(dest, flags);
495 } else {
496 dest.writeInt(0);
497 }
498 dest.writeBundle(extras);
499 }
500
501 public void readFromParcel(Parcel in) {
502 id = in.readLong();
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800503 titleRes = in.readInt();
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700504 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800505 summaryRes = in.readInt();
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700506 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800507 breadCrumbTitleRes = in.readInt();
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700508 breadCrumbTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800509 breadCrumbShortTitleRes = in.readInt();
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700510 breadCrumbShortTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700511 iconRes = in.readInt();
512 fragment = in.readString();
513 fragmentArguments = in.readBundle();
514 if (in.readInt() != 0) {
515 intent = Intent.CREATOR.createFromParcel(in);
516 }
517 extras = in.readBundle();
518 }
519
520 Header(Parcel in) {
521 readFromParcel(in);
522 }
523
524 public static final Creator<Header> CREATOR = new Creator<Header>() {
525 public Header createFromParcel(Parcel source) {
526 return new Header(source);
527 }
528 public Header[] newArray(int size) {
529 return new Header[size];
530 }
531 };
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700532 }
533
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800534 @Override
Scott Kennedy3c32b622015-02-22 16:58:58 -0800535 protected void onCreate(@Nullable Bundle savedInstanceState) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800536 super.onCreate(savedInstanceState);
537
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700538 // Theming for the PreferenceActivity layout and for the Preference Header(s) layout
539 TypedArray sa = obtainStyledAttributes(null,
540 com.android.internal.R.styleable.PreferenceActivity,
541 com.android.internal.R.attr.preferenceActivityStyle,
542 0);
543
544 final int layoutResId = sa.getResourceId(
545 com.android.internal.R.styleable.PreferenceActivity_layout,
546 com.android.internal.R.layout.preference_list_content);
547
548 mPreferenceHeaderItemResId = sa.getResourceId(
549 com.android.internal.R.styleable.PreferenceActivity_headerLayout,
550 com.android.internal.R.layout.preference_header_item);
551 mPreferenceHeaderRemoveEmptyIcon = sa.getBoolean(
552 com.android.internal.R.styleable.PreferenceActivity_headerRemoveIconIfEmpty,
553 false);
554
555 sa.recycle();
556
557 setContentView(layoutResId);
Freeman Ng19ea2e02010-03-25 15:09:00 -0700558
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700559 mListFooter = (FrameLayout)findViewById(com.android.internal.R.id.list_footer);
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800560 mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs_frame);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700561 boolean hidingHeaders = onIsHidingHeaders();
562 mSinglePane = hidingHeaders || !onIsMultiPane();
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700563 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
564 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
Dianne Hackborne72f2372011-03-16 10:43:18 -0700565 int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
566 int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700567
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700568 if (savedInstanceState != null) {
569 // We are restarting from a previous saved state; used that to
570 // initialize, instead of starting fresh.
571 ArrayList<Header> headers = savedInstanceState.getParcelableArrayList(HEADERS_TAG);
572 if (headers != null) {
573 mHeaders.addAll(headers);
574 int curHeader = savedInstanceState.getInt(CUR_HEADER_TAG,
Gilles Debunne39725ac2011-06-14 18:52:41 -0700575 (int) HEADER_ID_UNDEFINED);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700576 if (curHeader >= 0 && curHeader < mHeaders.size()) {
577 setSelectedHeader(mHeaders.get(curHeader));
578 }
579 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700580
581 } else {
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700582 if (initialFragment != null && mSinglePane) {
583 // If we are just showing a fragment, we want to run in
584 // new fragment mode, but don't need to compute and show
585 // the headers.
586 switchToHeader(initialFragment, initialArguments);
Dianne Hackborne72f2372011-03-16 10:43:18 -0700587 if (initialTitle != 0) {
588 CharSequence initialTitleStr = getText(initialTitle);
589 CharSequence initialShortTitleStr = initialShortTitle != 0
590 ? getText(initialShortTitle) : null;
591 showBreadCrumbs(initialTitleStr, initialShortTitleStr);
592 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700593
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700594 } else {
595 // We need to try to build the headers.
596 onBuildHeaders(mHeaders);
597
598 // If there are headers, then at this point we need to show
599 // them and, depending on the screen, we may also show in-line
600 // the currently selected preference fragment.
601 if (mHeaders.size() > 0) {
602 if (!mSinglePane) {
603 if (initialFragment == null) {
604 Header h = onGetInitialHeader();
605 switchToHeader(h);
606 } else {
607 switchToHeader(initialFragment, initialArguments);
608 }
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700609 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700610 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700611 }
612 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700613
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700614 // The default configuration is to only show the list view. Adjust
615 // visibility for other configurations.
616 if (initialFragment != null && mSinglePane) {
617 // Single pane, showing just a prefs fragment.
Amith Yamasani05fbc312010-09-26 13:29:01 -0700618 findViewById(com.android.internal.R.id.headers).setVisibility(View.GONE);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700619 mPrefsContainer.setVisibility(View.VISIBLE);
Dianne Hackborn34905a92011-07-21 17:30:07 -0700620 if (initialTitle != 0) {
Svetoslav Ganov8fc552e2013-11-21 19:36:22 +0000621 CharSequence initialTitleStr = getText(initialTitle);
622 CharSequence initialShortTitleStr = initialShortTitle != 0
Dianne Hackborn34905a92011-07-21 17:30:07 -0700623 ? getText(initialShortTitle) : null;
Svetoslav Ganov8fc552e2013-11-21 19:36:22 +0000624 showBreadCrumbs(initialTitleStr, initialShortTitleStr);
Dianne Hackborn34905a92011-07-21 17:30:07 -0700625 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700626 } else if (mHeaders.size() > 0) {
Fabrice Di Megliob22be6f2014-09-24 18:48:11 -0700627 setListAdapter(new HeaderAdapter(this, mHeaders, mPreferenceHeaderItemResId,
628 mPreferenceHeaderRemoveEmptyIcon));
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700629 if (!mSinglePane) {
630 // Multi-pane.
631 getListView().setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
Dianne Hackbornd0fa3712010-09-14 18:57:14 -0700632 if (mCurHeader != null) {
633 setSelectedHeader(mCurHeader);
634 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700635 mPrefsContainer.setVisibility(View.VISIBLE);
636 }
637 } else {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700638 // If there are no headers, we are in the old "just show a screen
639 // of preferences" mode.
Amith Yamasani405c1af2011-05-26 13:08:25 -0700640 setContentView(com.android.internal.R.layout.preference_list_content_single);
Amith Yamasani8da35292010-11-05 09:15:51 -0700641 mListFooter = (FrameLayout) findViewById(com.android.internal.R.id.list_footer);
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800642 mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700643 mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
644 mPreferenceManager.setOnPreferenceTreeClickListener(this);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700645 }
646
Freeman Ng19ea2e02010-03-25 15:09:00 -0700647 // see if we should show Back/Next buttons
648 Intent intent = getIntent();
649 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
650
651 findViewById(com.android.internal.R.id.button_bar).setVisibility(View.VISIBLE);
652
653 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
654 backButton.setOnClickListener(new OnClickListener() {
655 public void onClick(View v) {
656 setResult(RESULT_CANCELED);
657 finish();
658 }
659 });
Freeman Ng09dbf182010-08-11 15:45:01 -0700660 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
661 skipButton.setOnClickListener(new OnClickListener() {
662 public void onClick(View v) {
663 setResult(RESULT_OK);
664 finish();
665 }
666 });
Freeman Ng19ea2e02010-03-25 15:09:00 -0700667 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
668 mNextButton.setOnClickListener(new OnClickListener() {
669 public void onClick(View v) {
670 setResult(RESULT_OK);
671 finish();
672 }
673 });
674
675 // set our various button parameters
676 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
677 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
678 if (TextUtils.isEmpty(buttonText)) {
679 mNextButton.setVisibility(View.GONE);
680 }
681 else {
682 mNextButton.setText(buttonText);
683 }
684 }
685 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
686 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
687 if (TextUtils.isEmpty(buttonText)) {
688 backButton.setVisibility(View.GONE);
689 }
690 else {
691 backButton.setText(buttonText);
692 }
693 }
Freeman Ng09dbf182010-08-11 15:45:01 -0700694 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
695 skipButton.setVisibility(View.VISIBLE);
696 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700697 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700698 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700699
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700700 /**
Dianne Hackborn291905e2010-08-17 15:17:15 -0700701 * Returns true if this activity is currently showing the header list.
702 */
703 public boolean hasHeaders() {
704 return getListView().getVisibility() == View.VISIBLE
705 && mPreferenceManager == null;
706 }
707
708 /**
Amith Yamasani423d48b2012-06-20 13:54:53 -0700709 * Returns the Header list
710 * @hide
711 */
712 public List<Header> getHeaders() {
713 return mHeaders;
714 }
715
716 /**
Dianne Hackborn291905e2010-08-17 15:17:15 -0700717 * Returns true if this activity is showing multiple panes -- the headers
718 * and a preference fragment.
719 */
720 public boolean isMultiPane() {
721 return hasHeaders() && mPrefsContainer.getVisibility() == View.VISIBLE;
722 }
723
724 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700725 * Called to determine if the activity should run in multi-pane mode.
726 * The default implementation returns true if the screen is large
727 * enough.
728 */
729 public boolean onIsMultiPane() {
Amith Yamasani405c1af2011-05-26 13:08:25 -0700730 boolean preferMultiPane = getResources().getBoolean(
731 com.android.internal.R.bool.preferences_prefer_dual_pane);
732 return preferMultiPane;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700733 }
734
735 /**
Dianne Hackborn291905e2010-08-17 15:17:15 -0700736 * Called to determine whether the header list should be hidden.
737 * The default implementation returns the
738 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
739 * This is set to false, for example, when the activity is being re-launched
740 * to show a particular preference activity.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700741 */
742 public boolean onIsHidingHeaders() {
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700743 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700744 }
745
746 /**
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700747 * Called to determine the initial header to be shown. The default
748 * implementation simply returns the fragment of the first header. Note
749 * that the returned Header object does not actually need to exist in
750 * your header list -- whatever its fragment is will simply be used to
751 * show for the initial UI.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700752 */
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700753 public Header onGetInitialHeader() {
Dianne Hackborn19470ec2013-02-12 11:42:51 -0800754 for (int i=0; i<mHeaders.size(); i++) {
755 Header h = mHeaders.get(i);
756 if (h.fragment != null) {
757 return h;
758 }
759 }
760 throw new IllegalStateException("Must have at least one header with a fragment");
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700761 }
762
763 /**
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700764 * Called after the header list has been updated ({@link #onBuildHeaders}
765 * has been called and returned due to {@link #invalidateHeaders()}) to
766 * specify the header that should now be selected. The default implementation
767 * returns null to keep whatever header is currently selected.
768 */
769 public Header onGetNewHeader() {
770 return null;
771 }
772
773 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700774 * Called when the activity needs its list of headers build. By
775 * implementing this and adding at least one item to the list, you
776 * will cause the activity to run in its modern fragment mode. Note
777 * that this function may not always be called; for example, if the
778 * activity has been asked to display a particular fragment without
779 * the header list, there is no need to build the headers.
780 *
781 * <p>Typical implementations will use {@link #loadHeadersFromResource}
782 * to fill in the list from a resource.
783 *
784 * @param target The list in which to place the headers.
785 */
786 public void onBuildHeaders(List<Header> target) {
Gilles Debunne39725ac2011-06-14 18:52:41 -0700787 // Should be overloaded by subclasses
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700788 }
789
790 /**
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700791 * Call when you need to change the headers being displayed. Will result
792 * in onBuildHeaders() later being called to retrieve the new list.
793 */
794 public void invalidateHeaders() {
795 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
796 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
797 }
798 }
799
800 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700801 * Parse the given XML file as a header description, adding each
802 * parsed Header into the target list.
803 *
804 * @param resid The XML resource to load and parse.
805 * @param target The list in which the parsed headers should be placed.
806 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700807 public void loadHeadersFromResource(@XmlRes int resid, List<Header> target) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700808 XmlResourceParser parser = null;
809 try {
810 parser = getResources().getXml(resid);
811 AttributeSet attrs = Xml.asAttributeSet(parser);
812
813 int type;
814 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
815 && type != XmlPullParser.START_TAG) {
Gilles Debunne39725ac2011-06-14 18:52:41 -0700816 // Parse next until start tag is found
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700817 }
818
819 String nodeName = parser.getName();
Dianne Hackborndef15372010-08-15 12:43:52 -0700820 if (!"preference-headers".equals(nodeName)) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700821 throw new RuntimeException(
Dianne Hackborndef15372010-08-15 12:43:52 -0700822 "XML document must start with <preference-headers> tag; found"
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700823 + nodeName + " at " + parser.getPositionDescription());
824 }
825
Dianne Hackborndef15372010-08-15 12:43:52 -0700826 Bundle curBundle = null;
827
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700828 final int outerDepth = parser.getDepth();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700829 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
830 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
831 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
832 continue;
833 }
834
835 nodeName = parser.getName();
Dianne Hackborndef15372010-08-15 12:43:52 -0700836 if ("header".equals(nodeName)) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700837 Header header = new Header();
838
Alan Viverettef878c292013-12-02 18:36:00 -0800839 TypedArray sa = obtainStyledAttributes(
840 attrs, com.android.internal.R.styleable.PreferenceHeader);
Amith Yamasanied13cde2010-09-17 16:56:47 -0700841 header.id = sa.getResourceId(
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700842 com.android.internal.R.styleable.PreferenceHeader_id,
843 (int)HEADER_ID_UNDEFINED);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800844 TypedValue tv = sa.peekValue(
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700845 com.android.internal.R.styleable.PreferenceHeader_title);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800846 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
847 if (tv.resourceId != 0) {
848 header.titleRes = tv.resourceId;
849 } else {
850 header.title = tv.string;
851 }
852 }
853 tv = sa.peekValue(
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700854 com.android.internal.R.styleable.PreferenceHeader_summary);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800855 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
856 if (tv.resourceId != 0) {
857 header.summaryRes = tv.resourceId;
858 } else {
859 header.summary = tv.string;
860 }
861 }
862 tv = sa.peekValue(
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700863 com.android.internal.R.styleable.PreferenceHeader_breadCrumbTitle);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800864 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
865 if (tv.resourceId != 0) {
866 header.breadCrumbTitleRes = tv.resourceId;
867 } else {
868 header.breadCrumbTitle = tv.string;
869 }
870 }
871 tv = sa.peekValue(
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700872 com.android.internal.R.styleable.PreferenceHeader_breadCrumbShortTitle);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800873 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
874 if (tv.resourceId != 0) {
875 header.breadCrumbShortTitleRes = tv.resourceId;
876 } else {
877 header.breadCrumbShortTitle = tv.string;
878 }
879 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700880 header.iconRes = sa.getResourceId(
881 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
882 header.fragment = sa.getString(
883 com.android.internal.R.styleable.PreferenceHeader_fragment);
884 sa.recycle();
885
Dianne Hackborndef15372010-08-15 12:43:52 -0700886 if (curBundle == null) {
887 curBundle = new Bundle();
888 }
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700889
890 final int innerDepth = parser.getDepth();
891 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
892 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
893 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
894 continue;
895 }
896
897 String innerNodeName = parser.getName();
898 if (innerNodeName.equals("extra")) {
899 getResources().parseBundleExtra("extra", attrs, curBundle);
900 XmlUtils.skipCurrentTag(parser);
901
902 } else if (innerNodeName.equals("intent")) {
903 header.intent = Intent.parseIntent(getResources(), parser, attrs);
904
905 } else {
906 XmlUtils.skipCurrentTag(parser);
907 }
908 }
909
Dianne Hackborndef15372010-08-15 12:43:52 -0700910 if (curBundle.size() > 0) {
911 header.fragmentArguments = curBundle;
912 curBundle = null;
913 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700914
Dianne Hackborndef15372010-08-15 12:43:52 -0700915 target.add(header);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700916 } else {
917 XmlUtils.skipCurrentTag(parser);
918 }
919 }
920
921 } catch (XmlPullParserException e) {
922 throw new RuntimeException("Error parsing headers", e);
923 } catch (IOException e) {
924 throw new RuntimeException("Error parsing headers", e);
925 } finally {
926 if (parser != null) parser.close();
927 }
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700928 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700929
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700930 /**
931 * Subclasses should override this method and verify that the given fragment is a valid type
Amith Yamasanic2be0d62013-09-23 11:16:28 -0700932 * to be attached to this activity. The default implementation returns <code>true</code> for
933 * apps built for <code>android:targetSdkVersion</code> older than
934 * {@link android.os.Build.VERSION_CODES#KITKAT}. For later versions, it will throw an exception.
Amith Yamasani20915daf2013-08-01 12:44:01 -0700935 * @param fragmentName the class name of the Fragment about to be attached to this activity.
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700936 * @return true if the fragment class name is valid for this Activity and false otherwise.
937 */
938 protected boolean isValidFragment(String fragmentName) {
Chet Haasee8222dd2013-09-05 07:44:18 -0700939 if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.KITKAT) {
Amith Yamasania5001b92013-09-06 14:18:00 -0700940 throw new RuntimeException(
941 "Subclasses of PreferenceActivity must override isValidFragment(String)"
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700942 + " to verify that the Fragment class is valid! " + this.getClass().getName()
943 + " has not checked if fragment " + fragmentName + " is valid.");
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700944 } else {
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700945 return true;
946 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800947 }
948
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700949 /**
950 * Set a footer that should be shown at the bottom of the header list.
951 */
952 public void setListFooter(View view) {
953 mListFooter.removeAllViews();
954 mListFooter.addView(view, new FrameLayout.LayoutParams(
955 FrameLayout.LayoutParams.MATCH_PARENT,
956 FrameLayout.LayoutParams.WRAP_CONTENT));
957 }
958
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800959 @Override
960 protected void onStop() {
961 super.onStop();
Freeman Ng19ea2e02010-03-25 15:09:00 -0700962
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700963 if (mPreferenceManager != null) {
964 mPreferenceManager.dispatchActivityStop();
965 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800966 }
967
968 @Override
969 protected void onDestroy() {
Hiroaki Kuriyama1ebf13e2012-11-16 18:46:49 +0900970 mHandler.removeMessages(MSG_BIND_PREFERENCES);
971 mHandler.removeMessages(MSG_BUILD_HEADERS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800972 super.onDestroy();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700973
974 if (mPreferenceManager != null) {
975 mPreferenceManager.dispatchActivityDestroy();
976 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800977 }
978
979 @Override
980 protected void onSaveInstanceState(Bundle outState) {
981 super.onSaveInstanceState(outState);
982
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700983 if (mHeaders.size() > 0) {
984 outState.putParcelableArrayList(HEADERS_TAG, mHeaders);
985 if (mCurHeader != null) {
986 int index = mHeaders.indexOf(mCurHeader);
987 if (index >= 0) {
988 outState.putInt(CUR_HEADER_TAG, index);
989 }
990 }
991 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700992
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700993 if (mPreferenceManager != null) {
994 final PreferenceScreen preferenceScreen = getPreferenceScreen();
995 if (preferenceScreen != null) {
996 Bundle container = new Bundle();
997 preferenceScreen.saveHierarchyState(container);
998 outState.putBundle(PREFERENCES_TAG, container);
999 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001000 }
1001 }
1002
1003 @Override
1004 protected void onRestoreInstanceState(Bundle state) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001005 if (mPreferenceManager != null) {
1006 Bundle container = state.getBundle(PREFERENCES_TAG);
1007 if (container != null) {
1008 final PreferenceScreen preferenceScreen = getPreferenceScreen();
1009 if (preferenceScreen != null) {
1010 preferenceScreen.restoreHierarchyState(container);
1011 mSavedInstanceState = state;
1012 return;
1013 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001014 }
1015 }
Adam Powelle7fea452010-03-18 14:51:39 -07001016
1017 // Only call this if we didn't save the instance state for later.
1018 // If we did save it, it will be restored when we bind the adapter.
1019 super.onRestoreInstanceState(state);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001020 }
1021
1022 @Override
1023 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1024 super.onActivityResult(requestCode, resultCode, data);
Freeman Ng19ea2e02010-03-25 15:09:00 -07001025
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001026 if (mPreferenceManager != null) {
1027 mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
1028 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001029 }
1030
1031 @Override
1032 public void onContentChanged() {
1033 super.onContentChanged();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001034
1035 if (mPreferenceManager != null) {
1036 postBindPreferences();
1037 }
1038 }
1039
1040 @Override
1041 protected void onListItemClick(ListView l, View v, int position, long id) {
Amith Yamasani49bdc162013-07-17 15:52:45 -07001042 if (!isResumed()) {
1043 return;
1044 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001045 super.onListItemClick(l, v, position, id);
1046
1047 if (mAdapter != null) {
Gilles Debunne39725ac2011-06-14 18:52:41 -07001048 Object item = mAdapter.getItem(position);
1049 if (item instanceof Header) onHeaderClick((Header) item, position);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001050 }
1051 }
1052
1053 /**
1054 * Called when the user selects an item in the header list. The default
Dianne Hackborne72f2372011-03-16 10:43:18 -07001055 * implementation will call either
1056 * {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001057 * or {@link #switchToHeader(Header)} as appropriate.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001058 *
1059 * @param header The header that was selected.
1060 * @param position The header's position in the list.
1061 */
1062 public void onHeaderClick(Header header, int position) {
Dianne Hackborn5c769a42010-08-26 17:08:08 -07001063 if (header.fragment != null) {
1064 if (mSinglePane) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001065 int titleRes = header.breadCrumbTitleRes;
1066 int shortTitleRes = header.breadCrumbShortTitleRes;
1067 if (titleRes == 0) {
1068 titleRes = header.titleRes;
1069 shortTitleRes = 0;
1070 }
1071 startWithFragment(header.fragment, header.fragmentArguments, null, 0,
1072 titleRes, shortTitleRes);
Dianne Hackborn5c769a42010-08-26 17:08:08 -07001073 } else {
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001074 switchToHeader(header);
Dianne Hackborn5c769a42010-08-26 17:08:08 -07001075 }
1076 } else if (header.intent != null) {
1077 startActivity(header.intent);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001078 }
1079 }
1080
1081 /**
Dianne Hackborne72f2372011-03-16 10:43:18 -07001082 * Called by {@link #startWithFragment(String, Bundle, Fragment, int, int, int)} when
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001083 * in single-pane mode, to build an Intent to launch a new activity showing
1084 * the selected fragment. The default implementation constructs an Intent
1085 * that re-launches the current activity with the appropriate arguments to
1086 * display the fragment.
1087 *
1088 * @param fragmentName The name of the fragment to display.
1089 * @param args Optional arguments to supply to the fragment.
Dianne Hackborne72f2372011-03-16 10:43:18 -07001090 * @param titleRes Optional resource ID of title to show for this item.
Gilles Debunne39725ac2011-06-14 18:52:41 -07001091 * @param shortTitleRes Optional resource ID of short title to show for this item.
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001092 * @return Returns an Intent that can be launched to display the given
1093 * fragment.
1094 */
Dianne Hackborne72f2372011-03-16 10:43:18 -07001095 public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
Tor Norbye7b9c9122013-05-30 16:48:33 -07001096 @StringRes int titleRes, int shortTitleRes) {
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001097 Intent intent = new Intent(Intent.ACTION_MAIN);
1098 intent.setClass(this, getClass());
1099 intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
1100 intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
Dianne Hackborne72f2372011-03-16 10:43:18 -07001101 intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, titleRes);
1102 intent.putExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, shortTitleRes);
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001103 intent.putExtra(EXTRA_NO_HEADERS, true);
1104 return intent;
1105 }
1106
1107 /**
Dianne Hackborne72f2372011-03-16 10:43:18 -07001108 * Like {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
1109 * but uses a 0 titleRes.
1110 */
1111 public void startWithFragment(String fragmentName, Bundle args,
1112 Fragment resultTo, int resultRequestCode) {
1113 startWithFragment(fragmentName, args, resultTo, resultRequestCode, 0, 0);
1114 }
1115
1116 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001117 * Start a new instance of this activity, showing only the given
1118 * preference fragment. When launched in this mode, the header list
1119 * will be hidden and the given preference fragment will be instantiated
1120 * and fill the entire activity.
1121 *
1122 * @param fragmentName The name of the fragment to display.
Dianne Hackbornb7a2e472010-08-12 16:20:42 -07001123 * @param args Optional arguments to supply to the fragment.
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001124 * @param resultTo Option fragment that should receive the result of
1125 * the activity launch.
1126 * @param resultRequestCode If resultTo is non-null, this is the request
1127 * code in which to report the result.
Dianne Hackborne72f2372011-03-16 10:43:18 -07001128 * @param titleRes Resource ID of string to display for the title of
1129 * this set of preferences.
Gilles Debunne39725ac2011-06-14 18:52:41 -07001130 * @param shortTitleRes Resource ID of string to display for the short title of
Dianne Hackborne72f2372011-03-16 10:43:18 -07001131 * this set of preferences.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001132 */
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001133 public void startWithFragment(String fragmentName, Bundle args,
Tor Norbye7b9c9122013-05-30 16:48:33 -07001134 Fragment resultTo, int resultRequestCode, @StringRes int titleRes,
1135 @StringRes int shortTitleRes) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001136 Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001137 if (resultTo == null) {
1138 startActivity(intent);
1139 } else {
1140 resultTo.startActivityForResult(intent, resultRequestCode);
1141 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001142 }
1143
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001144 /**
1145 * Change the base title of the bread crumbs for the current preferences.
1146 * This will normally be called for you. See
1147 * {@link android.app.FragmentBreadCrumbs} for more information.
1148 */
1149 public void showBreadCrumbs(CharSequence title, CharSequence shortTitle) {
1150 if (mFragmentBreadCrumbs == null) {
Amith Yamasani3e860402010-12-10 14:20:51 -08001151 View crumbs = findViewById(android.R.id.title);
1152 // For screens with a different kind of title, don't create breadcrumbs.
Dianne Hackborne72f2372011-03-16 10:43:18 -07001153 try {
1154 mFragmentBreadCrumbs = (FragmentBreadCrumbs)crumbs;
1155 } catch (ClassCastException e) {
Amith Yamasanicf638ac2013-08-05 10:26:18 -07001156 setTitle(title);
Dianne Hackborne72f2372011-03-16 10:43:18 -07001157 return;
1158 }
Amith Yamasani3c9f5192010-12-08 16:48:31 -08001159 if (mFragmentBreadCrumbs == null) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001160 if (title != null) {
1161 setTitle(title);
Amith Yamasani3c9f5192010-12-08 16:48:31 -08001162 }
Dianne Hackborne72f2372011-03-16 10:43:18 -07001163 return;
Jim Millerc57406c2010-12-08 16:01:05 -08001164 }
Amith Yamasani3ec7bac2012-10-03 16:02:51 -07001165 if (mSinglePane) {
1166 mFragmentBreadCrumbs.setVisibility(View.GONE);
1167 // Hide the breadcrumb section completely for single-pane
1168 View bcSection = findViewById(com.android.internal.R.id.breadcrumb_section);
1169 if (bcSection != null) bcSection.setVisibility(View.GONE);
Amith Yamasanicf638ac2013-08-05 10:26:18 -07001170 setTitle(title);
Amith Yamasani3ec7bac2012-10-03 16:02:51 -07001171 }
Amith Yamasani3c9f5192010-12-08 16:48:31 -08001172 mFragmentBreadCrumbs.setMaxVisible(2);
1173 mFragmentBreadCrumbs.setActivity(this);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001174 }
Amith Yamasanicf638ac2013-08-05 10:26:18 -07001175 if (mFragmentBreadCrumbs.getVisibility() != View.VISIBLE) {
1176 setTitle(title);
1177 } else {
1178 mFragmentBreadCrumbs.setTitle(title, shortTitle);
1179 mFragmentBreadCrumbs.setParentTitle(null, null, null);
1180 }
Amith Yamasanic9ecb732010-12-14 14:23:21 -08001181 }
1182
1183 /**
1184 * Should be called after onCreate to ensure that the breadcrumbs, if any, were created.
1185 * This prepends a title to the fragment breadcrumbs and attaches a listener to any clicks
1186 * on the parent entry.
1187 * @param title the title for the breadcrumb
1188 * @param shortTitle the short title for the breadcrumb
1189 */
1190 public void setParentTitle(CharSequence title, CharSequence shortTitle,
1191 OnClickListener listener) {
1192 if (mFragmentBreadCrumbs != null) {
1193 mFragmentBreadCrumbs.setParentTitle(title, shortTitle, listener);
1194 }
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001195 }
1196
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001197 void setSelectedHeader(Header header) {
1198 mCurHeader = header;
1199 int index = mHeaders.indexOf(header);
1200 if (index >= 0) {
1201 getListView().setItemChecked(index, true);
1202 } else {
1203 getListView().clearChoices();
1204 }
Dianne Hackborn34905a92011-07-21 17:30:07 -07001205 showBreadCrumbs(header);
1206 }
1207
1208 void showBreadCrumbs(Header header) {
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001209 if (header != null) {
Dianne Hackborn50ed8292010-12-03 12:30:21 -08001210 CharSequence title = header.getBreadCrumbTitle(getResources());
1211 if (title == null) title = header.getTitle(getResources());
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001212 if (title == null) title = getTitle();
Dianne Hackborn50ed8292010-12-03 12:30:21 -08001213 showBreadCrumbs(title, header.getBreadCrumbShortTitle(getResources()));
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001214 } else {
1215 showBreadCrumbs(getTitle(), null);
1216 }
1217 }
1218
Fabrice Di Meglio5aaae372014-01-08 15:50:34 -08001219 private void switchToHeaderInner(String fragmentName, Bundle args) {
Dianne Hackborn3a57fb92010-11-15 17:58:52 -08001220 getFragmentManager().popBackStack(BACK_STACK_PREFS,
1221 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Amith Yamasani364ed4d2013-07-26 13:37:56 -07001222 if (!isValidFragment(fragmentName)) {
1223 throw new IllegalArgumentException("Invalid fragment for this activity: "
1224 + fragmentName);
1225 }
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001226 Fragment f = Fragment.instantiate(this, fragmentName, args);
Dianne Hackborn48e7b452011-01-17 12:28:35 -08001227 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Dianne Hackborn327fbd22011-01-17 14:38:50 -08001228 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Chet Haase9ff82bf2010-10-05 14:30:51 -07001229 transaction.replace(com.android.internal.R.id.prefs, f);
Dianne Hackborncf407ad2011-03-11 13:17:57 -08001230 transaction.commitAllowingStateLoss();
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001231 }
1232
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001233 /**
1234 * When in two-pane mode, switch the fragment pane to show the given
1235 * preference fragment.
1236 *
1237 * @param fragmentName The name of the fragment to display.
Dianne Hackbornb7a2e472010-08-12 16:20:42 -07001238 * @param args Optional arguments to supply to the fragment.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001239 */
Dianne Hackbornb7a2e472010-08-12 16:20:42 -07001240 public void switchToHeader(String fragmentName, Bundle args) {
Brian Attwell02bc31e2014-06-16 17:06:13 -07001241 Header selectedHeader = null;
1242 for (int i = 0; i < mHeaders.size(); i++) {
1243 if (fragmentName.equals(mHeaders.get(i).fragment)) {
1244 selectedHeader = mHeaders.get(i);
1245 break;
1246 }
1247 }
1248 setSelectedHeader(selectedHeader);
Fabrice Di Meglio5aaae372014-01-08 15:50:34 -08001249 switchToHeaderInner(fragmentName, args);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001250 }
1251
Andrew Stadleraa904f42010-09-02 14:50:08 -07001252 /**
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001253 * When in two-pane mode, switch to the fragment pane to show the given
1254 * preference fragment.
1255 *
1256 * @param header The new header to display.
1257 */
1258 public void switchToHeader(Header header) {
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001259 if (mCurHeader == header) {
1260 // This is the header we are currently displaying. Just make sure
1261 // to pop the stack up to its root state.
Dianne Hackborn3a57fb92010-11-15 17:58:52 -08001262 getFragmentManager().popBackStack(BACK_STACK_PREFS,
1263 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001264 } else {
Dianne Hackborn19470ec2013-02-12 11:42:51 -08001265 if (header.fragment == null) {
1266 throw new IllegalStateException("can't switch to header that has no fragment");
1267 }
Fabrice Di Meglio5aaae372014-01-08 15:50:34 -08001268 switchToHeaderInner(header.fragment, header.fragmentArguments);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001269 setSelectedHeader(header);
1270 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001271 }
1272
1273 Header findBestMatchingHeader(Header cur, ArrayList<Header> from) {
1274 ArrayList<Header> matches = new ArrayList<Header>();
1275 for (int j=0; j<from.size(); j++) {
1276 Header oh = from.get(j);
1277 if (cur == oh || (cur.id != HEADER_ID_UNDEFINED && cur.id == oh.id)) {
1278 // Must be this one.
1279 matches.clear();
1280 matches.add(oh);
1281 break;
1282 }
1283 if (cur.fragment != null) {
1284 if (cur.fragment.equals(oh.fragment)) {
1285 matches.add(oh);
1286 }
1287 } else if (cur.intent != null) {
1288 if (cur.intent.equals(oh.intent)) {
1289 matches.add(oh);
1290 }
1291 } else if (cur.title != null) {
1292 if (cur.title.equals(oh.title)) {
1293 matches.add(oh);
1294 }
1295 }
1296 }
1297 final int NM = matches.size();
1298 if (NM == 1) {
1299 return matches.get(0);
1300 } else if (NM > 1) {
1301 for (int j=0; j<NM; j++) {
1302 Header oh = matches.get(j);
1303 if (cur.fragmentArguments != null &&
1304 cur.fragmentArguments.equals(oh.fragmentArguments)) {
1305 return oh;
1306 }
1307 if (cur.extras != null && cur.extras.equals(oh.extras)) {
1308 return oh;
1309 }
1310 if (cur.title != null && cur.title.equals(oh.title)) {
1311 return oh;
1312 }
1313 }
1314 }
1315 return null;
1316 }
1317
1318 /**
Andrew Stadleraa904f42010-09-02 14:50:08 -07001319 * Start a new fragment.
1320 *
1321 * @param fragment The fragment to start
1322 * @param push If true, the current fragment will be pushed onto the back stack. If false,
1323 * the current fragment will be replaced.
1324 */
1325 public void startPreferenceFragment(Fragment fragment, boolean push) {
Dianne Hackborn48e7b452011-01-17 12:28:35 -08001326 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001327 transaction.replace(com.android.internal.R.id.prefs, fragment);
Andrew Stadleraa904f42010-09-02 14:50:08 -07001328 if (push) {
Chet Haase9ff82bf2010-10-05 14:30:51 -07001329 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
Andrew Stadleraa904f42010-09-02 14:50:08 -07001330 transaction.addToBackStack(BACK_STACK_PREFS);
Chet Haase9ff82bf2010-10-05 14:30:51 -07001331 } else {
Dianne Hackborn327fbd22011-01-17 14:38:50 -08001332 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Andrew Stadleraa904f42010-09-02 14:50:08 -07001333 }
Dianne Hackborncf407ad2011-03-11 13:17:57 -08001334 transaction.commitAllowingStateLoss();
Andrew Stadleraa904f42010-09-02 14:50:08 -07001335 }
1336
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001337 /**
Amith Yamasania47372c2013-08-05 10:55:28 -07001338 * Start a new fragment containing a preference panel. If the preferences
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001339 * are being displayed in multi-pane mode, the given fragment class will
1340 * be instantiated and placed in the appropriate pane. If running in
1341 * single-pane mode, a new activity will be launched in which to show the
1342 * fragment.
1343 *
1344 * @param fragmentClass Full name of the class implementing the fragment.
1345 * @param args Any desired arguments to supply to the fragment.
1346 * @param titleRes Optional resource identifier of the title of this
1347 * fragment.
1348 * @param titleText Optional text of the title of this fragment.
1349 * @param resultTo Optional fragment that result data should be sent to.
1350 * If non-null, resultTo.onActivityResult() will be called when this
1351 * preference panel is done. The launched panel must use
1352 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
1353 * @param resultRequestCode If resultTo is non-null, this is the caller's
Tor Norbye7b9c9122013-05-30 16:48:33 -07001354 * request code to be received with the result.
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001355 */
Tor Norbye7b9c9122013-05-30 16:48:33 -07001356 public void startPreferencePanel(String fragmentClass, Bundle args, @StringRes int titleRes,
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001357 CharSequence titleText, Fragment resultTo, int resultRequestCode) {
1358 if (mSinglePane) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001359 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, 0);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001360 } else {
1361 Fragment f = Fragment.instantiate(this, fragmentClass, args);
1362 if (resultTo != null) {
1363 f.setTargetFragment(resultTo, resultRequestCode);
1364 }
Dianne Hackborn48e7b452011-01-17 12:28:35 -08001365 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001366 transaction.replace(com.android.internal.R.id.prefs, f);
1367 if (titleRes != 0) {
1368 transaction.setBreadCrumbTitle(titleRes);
1369 } else if (titleText != null) {
1370 transaction.setBreadCrumbTitle(titleText);
1371 }
1372 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1373 transaction.addToBackStack(BACK_STACK_PREFS);
Dianne Hackborncf407ad2011-03-11 13:17:57 -08001374 transaction.commitAllowingStateLoss();
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001375 }
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001376 }
Amith Yamasani364ed4d2013-07-26 13:37:56 -07001377
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001378 /**
1379 * Called by a preference panel fragment to finish itself.
1380 *
1381 * @param caller The fragment that is asking to be finished.
1382 * @param resultCode Optional result code to send back to the original
1383 * launching fragment.
1384 * @param resultData Optional result data to send back to the original
1385 * launching fragment.
1386 */
1387 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
1388 if (mSinglePane) {
1389 setResult(resultCode, resultData);
1390 finish();
1391 } else {
Dianne Hackborn3a57fb92010-11-15 17:58:52 -08001392 // XXX be smarter about popping the stack.
1393 onBackPressed();
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001394 if (caller != null) {
1395 if (caller.getTargetFragment() != null) {
1396 caller.getTargetFragment().onActivityResult(caller.getTargetRequestCode(),
1397 resultCode, resultData);
1398 }
1399 }
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001400 }
1401 }
1402
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -07001403 @Override
1404 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001405 startPreferencePanel(pref.getFragment(), pref.getExtras(), pref.getTitleRes(),
1406 pref.getTitle(), null, 0);
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -07001407 return true;
1408 }
1409
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001410 /**
1411 * Posts a message to bind the preferences to the list view.
1412 * <p>
1413 * Binding late is preferred as any custom preference types created in
1414 * {@link #onCreate(Bundle)} are able to have their views recycled.
1415 */
1416 private void postBindPreferences() {
1417 if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
1418 mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
1419 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001420
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001421 private void bindPreferences() {
1422 final PreferenceScreen preferenceScreen = getPreferenceScreen();
1423 if (preferenceScreen != null) {
1424 preferenceScreen.bind(getListView());
Adam Powelle7fea452010-03-18 14:51:39 -07001425 if (mSavedInstanceState != null) {
1426 super.onRestoreInstanceState(mSavedInstanceState);
1427 mSavedInstanceState = null;
1428 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001429 }
1430 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001431
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001432 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001433 * Returns the {@link PreferenceManager} used by this activity.
1434 * @return The {@link PreferenceManager}.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001435 *
1436 * @deprecated This function is not relevant for a modern fragment-based
1437 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001438 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001439 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001440 public PreferenceManager getPreferenceManager() {
1441 return mPreferenceManager;
1442 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001443
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001444 private void requirePreferenceManager() {
1445 if (mPreferenceManager == null) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001446 if (mAdapter == null) {
1447 throw new RuntimeException("This should be called after super.onCreate.");
1448 }
1449 throw new RuntimeException(
1450 "Modern two-pane PreferenceActivity requires use of a PreferenceFragment");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001451 }
1452 }
1453
1454 /**
1455 * Sets the root of the preference hierarchy that this activity is showing.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001456 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001457 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001458 *
1459 * @deprecated This function is not relevant for a modern fragment-based
1460 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001461 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001462 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001463 public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001464 requirePreferenceManager();
1465
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001466 if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
1467 postBindPreferences();
1468 CharSequence title = getPreferenceScreen().getTitle();
1469 // Set the title of the activity
1470 if (title != null) {
1471 setTitle(title);
1472 }
1473 }
1474 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001475
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001476 /**
1477 * Gets the root of the preference hierarchy that this activity is showing.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001478 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001479 * @return The {@link PreferenceScreen} that is the root of the preference
1480 * hierarchy.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001481 *
1482 * @deprecated This function is not relevant for a modern fragment-based
1483 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001484 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001485 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001486 public PreferenceScreen getPreferenceScreen() {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001487 if (mPreferenceManager != null) {
1488 return mPreferenceManager.getPreferenceScreen();
1489 }
1490 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001491 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001492
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001493 /**
1494 * Adds preferences from activities that match the given {@link Intent}.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001495 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001496 * @param intent The {@link Intent} to query activities.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001497 *
1498 * @deprecated This function is not relevant for a modern fragment-based
1499 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001500 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001501 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001502 public void addPreferencesFromIntent(Intent intent) {
1503 requirePreferenceManager();
Freeman Ng19ea2e02010-03-25 15:09:00 -07001504
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001505 setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
1506 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001507
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001508 /**
1509 * Inflates the given XML resource and adds the preference hierarchy to the current
1510 * preference hierarchy.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001511 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001512 * @param preferencesResId The XML resource ID to inflate.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001513 *
1514 * @deprecated This function is not relevant for a modern fragment-based
1515 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001516 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001517 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001518 public void addPreferencesFromResource(int preferencesResId) {
1519 requirePreferenceManager();
Freeman Ng19ea2e02010-03-25 15:09:00 -07001520
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001521 setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
1522 getPreferenceScreen()));
1523 }
1524
1525 /**
1526 * {@inheritDoc}
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001527 *
1528 * @deprecated This function is not relevant for a modern fragment-based
1529 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001530 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001531 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001532 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
1533 return false;
1534 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001535
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001536 /**
1537 * Finds a {@link Preference} based on its key.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001538 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001539 * @param key The key of the preference to retrieve.
1540 * @return The {@link Preference} with the key, or null.
1541 * @see PreferenceGroup#findPreference(CharSequence)
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001542 *
1543 * @deprecated This function is not relevant for a modern fragment-based
1544 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001545 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001546 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001547 public Preference findPreference(CharSequence key) {
Freeman Ng19ea2e02010-03-25 15:09:00 -07001548
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001549 if (mPreferenceManager == null) {
1550 return null;
1551 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001552
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001553 return mPreferenceManager.findPreference(key);
1554 }
1555
1556 @Override
1557 protected void onNewIntent(Intent intent) {
1558 if (mPreferenceManager != null) {
1559 mPreferenceManager.dispatchNewIntent(intent);
1560 }
1561 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001562
1563 // give subclasses access to the Next button
1564 /** @hide */
1565 protected boolean hasNextButton() {
1566 return mNextButton != null;
1567 }
1568 /** @hide */
1569 protected Button getNextButton() {
1570 return mNextButton;
1571 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001572}