blob: 04180491f137b62b359727cb58fc959364f3e317 [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
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070019import android.app.Fragment;
Dianne Hackbornc6669ca2010-09-16 01:33:24 -070020import android.app.FragmentBreadCrumbs;
Dianne Hackborn3a57fb92010-11-15 17:58:52 -080021import android.app.FragmentManager;
Andrew Stadleraa904f42010-09-02 14:50:08 -070022import android.app.FragmentTransaction;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.app.ListActivity;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070024import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.content.Intent;
Dianne Hackborn50ed8292010-12-03 12:30:21 -080026import android.content.res.Resources;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070027import android.content.res.TypedArray;
28import android.content.res.XmlResourceParser;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.os.Bundle;
30import android.os.Handler;
31import android.os.Message;
Dianne Hackborna21e3da2010-09-12 19:27:46 -070032import android.os.Parcel;
33import android.os.Parcelable;
Freeman Ng19ea2e02010-03-25 15:09:00 -070034import android.text.TextUtils;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070035import android.util.AttributeSet;
Dianne Hackborn50ed8292010-12-03 12:30:21 -080036import android.util.TypedValue;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070037import android.util.Xml;
38import android.view.LayoutInflater;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.view.View;
Amith Yamasani405c1af2011-05-26 13:08:25 -070040import android.view.View.OnClickListener;
Gilles Debunne39725ac2011-06-14 18:52:41 -070041import android.view.ViewGroup;
Dianne Hackborna21e3da2010-09-12 19:27:46 -070042import android.widget.AbsListView;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070043import android.widget.ArrayAdapter;
Gilles Debunne39725ac2011-06-14 18:52:41 -070044import android.widget.BaseAdapter;
Freeman Ng19ea2e02010-03-25 15:09:00 -070045import android.widget.Button;
Dianne Hackborn5c769a42010-08-26 17:08:08 -070046import android.widget.FrameLayout;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070047import android.widget.ImageView;
48import android.widget.ListView;
49import android.widget.TextView;
50
Gilles Debunne39725ac2011-06-14 18:52:41 -070051import com.android.internal.util.XmlUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052
Amith Yamasani405c1af2011-05-26 13:08:25 -070053import org.xmlpull.v1.XmlPullParser;
54import org.xmlpull.v1.XmlPullParserException;
55
Gilles Debunne39725ac2011-06-14 18:52:41 -070056import java.io.IOException;
57import java.util.ArrayList;
58import java.util.List;
59
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060/**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070061 * This is the base class for an activity to show a hierarchy of preferences
62 * to the user. Prior to {@link android.os.Build.VERSION_CODES#HONEYCOMB}
63 * this class only allowed the display of a single set of preference; this
64 * functionality should now be found in the new {@link PreferenceFragment}
65 * class. If you are using PreferenceActivity in its old mode, the documentation
66 * there applies to the deprecated APIs here.
Freeman Ng19ea2e02010-03-25 15:09:00 -070067 *
Gilles Debunne39725ac2011-06-14 18:52:41 -070068 * <p>This activity shows one or more headers of preferences, each of which
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070069 * is associated with a {@link PreferenceFragment} to display the preferences
70 * of that header. The actual layout and display of these associations can
71 * however vary; currently there are two major approaches it may take:
72 *
73 * <ul>
74 * <li>On a small screen it may display only the headers as a single list
75 * when first launched. Selecting one of the header items will re-launch
76 * the activity with it only showing the PreferenceFragment of that header.
77 * <li>On a large screen in may display both the headers and current
78 * PreferenceFragment together as panes. Selecting a header item switches
79 * to showing the correct PreferenceFragment for that item.
80 * </ul>
81 *
82 * <p>Subclasses of PreferenceActivity should implement
83 * {@link #onBuildHeaders} to populate the header list with the desired
84 * items. Doing this implicitly switches the class into its new "headers
85 * + fragments" mode rather than the old style of just showing a single
86 * preferences list.
Scott Maincdd0c592012-07-26 17:03:51 -070087 *
88 * <div class="special reference">
89 * <h3>Developer Guides</h3>
90 * <p>For information about using {@code PreferenceActivity},
91 * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>
92 * guide.</p>
93 * </div>
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070094 *
95 * <a name="SampleCode"></a>
96 * <h3>Sample Code</h3>
97 *
98 * <p>The following sample code shows a simple preference activity that
99 * has two different sets of preferences. The implementation, consisting
100 * of the activity itself as well as its two preference fragments is:</p>
101 *
102 * {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/PreferenceWithHeaders.java
103 * activity}
104 *
105 * <p>The preference_headers resource describes the headers to be displayed
106 * and the fragments associated with them. It is:
107 *
108 * {@sample development/samples/ApiDemos/res/xml/preference_headers.xml headers}
109 *
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700110 * <p>The first header is shown by Prefs1Fragment, which populates itself
111 * from the following XML resource:</p>
112 *
113 * {@sample development/samples/ApiDemos/res/xml/fragmented_preferences.xml preferences}
114 *
115 * <p>Note that this XML resource contains a preference screen holding another
116 * fragment, the Prefs1FragmentInner implemented here. This allows the user
117 * to traverse down a hierarchy of preferences; pressing back will pop each
118 * fragment off the stack to return to the previous preferences.
119 *
120 * <p>See {@link PreferenceFragment} for information on implementing the
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700121 * fragments themselves.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122 */
123public abstract class PreferenceActivity extends ListActivity implements
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700124 PreferenceManager.OnPreferenceTreeClickListener,
125 PreferenceFragment.OnPreferenceStartFragmentCallback {
Freeman Ng19ea2e02010-03-25 15:09:00 -0700126
Svetoslav Ganov8fc552e2013-11-21 19:36:22 +0000127 private static final String TAG = "PreferenceActivity";
128
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700129 // Constants for state save/restore
130 private static final String HEADERS_TAG = ":android:headers";
131 private static final String CUR_HEADER_TAG = ":android:cur_header";
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700132 private static final String PREFERENCES_TAG = ":android:preferences";
Freeman Ng19ea2e02010-03-25 15:09:00 -0700133
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700134 /**
135 * When starting this activity, the invoking Intent can contain this extra
136 * string to specify which fragment should be initially displayed.
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700137 * <p/>Starting from Key Lime Pie, when this argument is passed in, the PreferenceActivity
138 * will call isValidFragment() to confirm that the fragment class name is valid for this
139 * activity.
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700140 */
141 public static final String EXTRA_SHOW_FRAGMENT = ":android:show_fragment";
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700142
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700143 /**
144 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
Dianne Hackborne72f2372011-03-16 10:43:18 -0700145 * this extra can also be specified to supply a Bundle of arguments to pass
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700146 * to that fragment when it is instantiated during the initial creation
147 * of PreferenceActivity.
148 */
149 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":android:show_fragment_args";
150
151 /**
Dianne Hackborne72f2372011-03-16 10:43:18 -0700152 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
153 * this extra can also be specify to supply the title to be shown for
154 * that fragment.
155 */
156 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":android:show_fragment_title";
157
158 /**
159 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
160 * this extra can also be specify to supply the short title to be shown for
161 * that fragment.
162 */
163 public static final String EXTRA_SHOW_FRAGMENT_SHORT_TITLE
164 = ":android:show_fragment_short_title";
165
166 /**
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700167 * When starting this activity, the invoking Intent can contain this extra
168 * boolean that the header list should not be displayed. This is most often
169 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
170 * the activity to display a specific fragment that the user has navigated
171 * to.
172 */
173 public static final String EXTRA_NO_HEADERS = ":android:no_headers";
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700174
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700175 private static final String BACK_STACK_PREFS = ":android:prefs";
176
Freeman Ng19ea2e02010-03-25 15:09:00 -0700177 // extras that allow any preference activity to be launched as part of a wizard
178
179 // show Back and Next buttons? takes boolean parameter
180 // Back will then return RESULT_CANCELED and Next RESULT_OK
181 private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
182
Freeman Ng09dbf182010-08-11 15:45:01 -0700183 // add a Skip button?
184 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
185
Freeman Ng19ea2e02010-03-25 15:09:00 -0700186 // specify custom text for the Back or Next buttons, or cause a button to not appear
187 // at all by setting it to null
188 private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
189 private static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
190
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700191 // --- State for new mode when showing a list of headers + prefs fragment
192
193 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
194
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700195 private FrameLayout mListFooter;
196
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800197 private ViewGroup mPrefsContainer;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700198
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700199 private FragmentBreadCrumbs mFragmentBreadCrumbs;
200
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700201 private boolean mSinglePane;
202
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700203 private Header mCurHeader;
204
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700205 // --- State for old mode when showing a single preference list
Freeman Ng19ea2e02010-03-25 15:09:00 -0700206
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800207 private PreferenceManager mPreferenceManager;
Freeman Ng19ea2e02010-03-25 15:09:00 -0700208
Adam Powelle7fea452010-03-18 14:51:39 -0700209 private Bundle mSavedInstanceState;
210
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700211 // --- Common state
212
213 private Button mNextButton;
214
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800215 /**
216 * The starting request code given out to preference framework.
217 */
218 private static final int FIRST_REQUEST_CODE = 100;
Freeman Ng19ea2e02010-03-25 15:09:00 -0700219
Dianne Hackborn3e449ce2010-09-11 20:52:31 -0700220 private static final int MSG_BIND_PREFERENCES = 1;
221 private static final int MSG_BUILD_HEADERS = 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222 private Handler mHandler = new Handler() {
223 @Override
224 public void handleMessage(Message msg) {
225 switch (msg.what) {
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700226 case MSG_BIND_PREFERENCES: {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800227 bindPreferences();
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700228 } break;
229 case MSG_BUILD_HEADERS: {
230 ArrayList<Header> oldHeaders = new ArrayList<Header>(mHeaders);
231 mHeaders.clear();
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700232 onBuildHeaders(mHeaders);
Gilles Debunne39725ac2011-06-14 18:52:41 -0700233 if (mAdapter instanceof BaseAdapter) {
234 ((BaseAdapter) mAdapter).notifyDataSetChanged();
Andrew Stadler83681eb2010-11-04 15:49:05 -0700235 }
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700236 Header header = onGetNewHeader();
237 if (header != null && header.fragment != null) {
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700238 Header mappedHeader = findBestMatchingHeader(header, oldHeaders);
239 if (mappedHeader == null || mCurHeader != mappedHeader) {
240 switchToHeader(header);
241 }
242 } else if (mCurHeader != null) {
243 Header mappedHeader = findBestMatchingHeader(mCurHeader, mHeaders);
244 if (mappedHeader != null) {
245 setSelectedHeader(mappedHeader);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700246 }
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700247 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700248 } break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800249 }
250 }
251 };
252
Andrew Stadler468c3232010-08-17 16:16:42 -0700253 private static class HeaderAdapter extends ArrayAdapter<Header> {
254 private static class HeaderViewHolder {
255 ImageView icon;
256 TextView title;
257 TextView summary;
258 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700259
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700260 private LayoutInflater mInflater;
261
262 public HeaderAdapter(Context context, List<Header> objects) {
263 super(context, 0, objects);
264 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
265 }
266
267 @Override
268 public View getView(int position, View convertView, ViewGroup parent) {
269 HeaderViewHolder holder;
270 View view;
271
272 if (convertView == null) {
Dianne Hackbornd0fa3712010-09-14 18:57:14 -0700273 view = mInflater.inflate(com.android.internal.R.layout.preference_header_item,
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700274 parent, false);
275 holder = new HeaderViewHolder();
Andrew Stadler468c3232010-08-17 16:16:42 -0700276 holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon);
277 holder.title = (TextView) view.findViewById(com.android.internal.R.id.title);
278 holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700279 view.setTag(holder);
280 } else {
281 view = convertView;
Andrew Stadler468c3232010-08-17 16:16:42 -0700282 holder = (HeaderViewHolder) view.getTag();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700283 }
284
Andrew Stadler468c3232010-08-17 16:16:42 -0700285 // All view fields must be updated every time, because the view may be recycled
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700286 Header header = getItem(position);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700287 holder.icon.setImageResource(header.iconRes);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800288 holder.title.setText(header.getTitle(getContext().getResources()));
289 CharSequence summary = header.getSummary(getContext().getResources());
290 if (!TextUtils.isEmpty(summary)) {
Andrew Stadler468c3232010-08-17 16:16:42 -0700291 holder.summary.setVisibility(View.VISIBLE);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800292 holder.summary.setText(summary);
293 } else {
294 holder.summary.setVisibility(View.GONE);
Andrew Stadler468c3232010-08-17 16:16:42 -0700295 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700296
297 return view;
298 }
299 }
300
301 /**
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700302 * Default value for {@link Header#id Header.id} indicating that no
303 * identifier value is set. All other values (including those below -1)
304 * are valid.
305 */
306 public static final long HEADER_ID_UNDEFINED = -1;
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700307
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700308 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700309 * Description of a single Header item that the user can select.
310 */
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700311 public static final class Header implements Parcelable {
312 /**
313 * Identifier for this header, to correlate with a new list when
314 * it is updated. The default value is
315 * {@link PreferenceActivity#HEADER_ID_UNDEFINED}, meaning no id.
316 * @attr ref android.R.styleable#PreferenceHeader_id
317 */
318 public long id = HEADER_ID_UNDEFINED;
319
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700320 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800321 * Resource ID of title of the header that is shown to the user.
322 * @attr ref android.R.styleable#PreferenceHeader_title
323 */
324 public int titleRes;
325
326 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700327 * Title of the header that is shown to the user.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700328 * @attr ref android.R.styleable#PreferenceHeader_title
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700329 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700330 public CharSequence title;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700331
332 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800333 * Resource ID of optional summary describing what this header controls.
334 * @attr ref android.R.styleable#PreferenceHeader_summary
335 */
336 public int summaryRes;
337
338 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700339 * Optional summary describing what this header controls.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700340 * @attr ref android.R.styleable#PreferenceHeader_summary
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700341 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700342 public CharSequence summary;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700343
344 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800345 * Resource ID of optional text to show as the title in the bread crumb.
346 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
347 */
348 public int breadCrumbTitleRes;
349
350 /**
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700351 * Optional text to show as the title in the bread crumb.
352 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
353 */
354 public CharSequence breadCrumbTitle;
355
356 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800357 * Resource ID of optional text to show as the short title in the bread crumb.
358 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
359 */
360 public int breadCrumbShortTitleRes;
361
362 /**
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700363 * Optional text to show as the short title in the bread crumb.
364 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
365 */
366 public CharSequence breadCrumbShortTitle;
367
368 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700369 * Optional icon resource to show for this header.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700370 * @attr ref android.R.styleable#PreferenceHeader_icon
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700371 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700372 public int iconRes;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700373
374 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700375 * Full class name of the fragment to display when this header is
376 * selected.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700377 * @attr ref android.R.styleable#PreferenceHeader_fragment
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700378 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700379 public String fragment;
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700380
381 /**
382 * Optional arguments to supply to the fragment when it is
383 * instantiated.
384 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700385 public Bundle fragmentArguments;
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700386
387 /**
388 * Intent to launch when the preference is selected.
389 */
390 public Intent intent;
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700391
392 /**
393 * Optional additional data for use by subclasses of PreferenceActivity.
394 */
395 public Bundle extras;
396
397 public Header() {
Gilles Debunne39725ac2011-06-14 18:52:41 -0700398 // Empty
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700399 }
400
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800401 /**
402 * Return the currently set title. If {@link #titleRes} is set,
403 * this resource is loaded from <var>res</var> and returned. Otherwise
404 * {@link #title} is returned.
405 */
406 public CharSequence getTitle(Resources res) {
407 if (titleRes != 0) {
408 return res.getText(titleRes);
409 }
410 return title;
411 }
412
413 /**
414 * Return the currently set summary. If {@link #summaryRes} is set,
415 * this resource is loaded from <var>res</var> and returned. Otherwise
416 * {@link #summary} is returned.
417 */
418 public CharSequence getSummary(Resources res) {
419 if (summaryRes != 0) {
420 return res.getText(summaryRes);
421 }
Dianne Hackborn9d071802010-12-08 14:49:15 -0800422 return summary;
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800423 }
424
425 /**
426 * Return the currently set bread crumb title. If {@link #breadCrumbTitleRes} is set,
427 * this resource is loaded from <var>res</var> and returned. Otherwise
428 * {@link #breadCrumbTitle} is returned.
429 */
430 public CharSequence getBreadCrumbTitle(Resources res) {
431 if (breadCrumbTitleRes != 0) {
432 return res.getText(breadCrumbTitleRes);
433 }
434 return breadCrumbTitle;
435 }
436
437 /**
438 * Return the currently set bread crumb short title. If
439 * {@link #breadCrumbShortTitleRes} is set,
440 * this resource is loaded from <var>res</var> and returned. Otherwise
441 * {@link #breadCrumbShortTitle} is returned.
442 */
443 public CharSequence getBreadCrumbShortTitle(Resources res) {
444 if (breadCrumbShortTitleRes != 0) {
445 return res.getText(breadCrumbShortTitleRes);
446 }
447 return breadCrumbShortTitle;
448 }
449
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700450 @Override
451 public int describeContents() {
452 return 0;
453 }
454
455 @Override
456 public void writeToParcel(Parcel dest, int flags) {
457 dest.writeLong(id);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800458 dest.writeInt(titleRes);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700459 TextUtils.writeToParcel(title, dest, flags);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800460 dest.writeInt(summaryRes);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700461 TextUtils.writeToParcel(summary, dest, flags);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800462 dest.writeInt(breadCrumbTitleRes);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700463 TextUtils.writeToParcel(breadCrumbTitle, dest, flags);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800464 dest.writeInt(breadCrumbShortTitleRes);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700465 TextUtils.writeToParcel(breadCrumbShortTitle, dest, flags);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700466 dest.writeInt(iconRes);
467 dest.writeString(fragment);
468 dest.writeBundle(fragmentArguments);
469 if (intent != null) {
470 dest.writeInt(1);
471 intent.writeToParcel(dest, flags);
472 } else {
473 dest.writeInt(0);
474 }
475 dest.writeBundle(extras);
476 }
477
478 public void readFromParcel(Parcel in) {
479 id = in.readLong();
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800480 titleRes = in.readInt();
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700481 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800482 summaryRes = in.readInt();
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700483 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800484 breadCrumbTitleRes = in.readInt();
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700485 breadCrumbTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800486 breadCrumbShortTitleRes = in.readInt();
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700487 breadCrumbShortTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700488 iconRes = in.readInt();
489 fragment = in.readString();
490 fragmentArguments = in.readBundle();
491 if (in.readInt() != 0) {
492 intent = Intent.CREATOR.createFromParcel(in);
493 }
494 extras = in.readBundle();
495 }
496
497 Header(Parcel in) {
498 readFromParcel(in);
499 }
500
501 public static final Creator<Header> CREATOR = new Creator<Header>() {
502 public Header createFromParcel(Parcel source) {
503 return new Header(source);
504 }
505 public Header[] newArray(int size) {
506 return new Header[size];
507 }
508 };
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700509 }
510
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 @Override
512 protected void onCreate(Bundle savedInstanceState) {
513 super.onCreate(savedInstanceState);
514
Amith Yamasani405c1af2011-05-26 13:08:25 -0700515 setContentView(com.android.internal.R.layout.preference_list_content);
Freeman Ng19ea2e02010-03-25 15:09:00 -0700516
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700517 mListFooter = (FrameLayout)findViewById(com.android.internal.R.id.list_footer);
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800518 mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs_frame);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700519 boolean hidingHeaders = onIsHidingHeaders();
520 mSinglePane = hidingHeaders || !onIsMultiPane();
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700521 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
522 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
Dianne Hackborne72f2372011-03-16 10:43:18 -0700523 int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
524 int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700525
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700526 if (savedInstanceState != null) {
527 // We are restarting from a previous saved state; used that to
528 // initialize, instead of starting fresh.
529 ArrayList<Header> headers = savedInstanceState.getParcelableArrayList(HEADERS_TAG);
530 if (headers != null) {
531 mHeaders.addAll(headers);
532 int curHeader = savedInstanceState.getInt(CUR_HEADER_TAG,
Gilles Debunne39725ac2011-06-14 18:52:41 -0700533 (int) HEADER_ID_UNDEFINED);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700534 if (curHeader >= 0 && curHeader < mHeaders.size()) {
535 setSelectedHeader(mHeaders.get(curHeader));
536 }
537 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700538
539 } else {
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700540 if (initialFragment != null && mSinglePane) {
541 // If we are just showing a fragment, we want to run in
542 // new fragment mode, but don't need to compute and show
543 // the headers.
544 switchToHeader(initialFragment, initialArguments);
Dianne Hackborne72f2372011-03-16 10:43:18 -0700545 if (initialTitle != 0) {
546 CharSequence initialTitleStr = getText(initialTitle);
547 CharSequence initialShortTitleStr = initialShortTitle != 0
548 ? getText(initialShortTitle) : null;
549 showBreadCrumbs(initialTitleStr, initialShortTitleStr);
550 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700551
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700552 } else {
553 // We need to try to build the headers.
554 onBuildHeaders(mHeaders);
555
556 // If there are headers, then at this point we need to show
557 // them and, depending on the screen, we may also show in-line
558 // the currently selected preference fragment.
559 if (mHeaders.size() > 0) {
560 if (!mSinglePane) {
561 if (initialFragment == null) {
562 Header h = onGetInitialHeader();
563 switchToHeader(h);
564 } else {
565 switchToHeader(initialFragment, initialArguments);
566 }
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700567 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700568 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700569 }
570 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700571
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700572 // The default configuration is to only show the list view. Adjust
573 // visibility for other configurations.
574 if (initialFragment != null && mSinglePane) {
575 // Single pane, showing just a prefs fragment.
Amith Yamasani05fbc312010-09-26 13:29:01 -0700576 findViewById(com.android.internal.R.id.headers).setVisibility(View.GONE);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700577 mPrefsContainer.setVisibility(View.VISIBLE);
Dianne Hackborn34905a92011-07-21 17:30:07 -0700578 if (initialTitle != 0) {
Svetoslav Ganov8fc552e2013-11-21 19:36:22 +0000579 CharSequence initialTitleStr = getText(initialTitle);
580 CharSequence initialShortTitleStr = initialShortTitle != 0
Dianne Hackborn34905a92011-07-21 17:30:07 -0700581 ? getText(initialShortTitle) : null;
Svetoslav Ganov8fc552e2013-11-21 19:36:22 +0000582 showBreadCrumbs(initialTitleStr, initialShortTitleStr);
Dianne Hackborn34905a92011-07-21 17:30:07 -0700583 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700584 } else if (mHeaders.size() > 0) {
Gilles Debunne39725ac2011-06-14 18:52:41 -0700585 setListAdapter(new HeaderAdapter(this, mHeaders));
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700586 if (!mSinglePane) {
587 // Multi-pane.
588 getListView().setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
Dianne Hackbornd0fa3712010-09-14 18:57:14 -0700589 if (mCurHeader != null) {
590 setSelectedHeader(mCurHeader);
591 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700592 mPrefsContainer.setVisibility(View.VISIBLE);
593 }
594 } else {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700595 // If there are no headers, we are in the old "just show a screen
596 // of preferences" mode.
Amith Yamasani405c1af2011-05-26 13:08:25 -0700597 setContentView(com.android.internal.R.layout.preference_list_content_single);
Amith Yamasani8da35292010-11-05 09:15:51 -0700598 mListFooter = (FrameLayout) findViewById(com.android.internal.R.id.list_footer);
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800599 mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700600 mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
601 mPreferenceManager.setOnPreferenceTreeClickListener(this);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700602 }
603
Freeman Ng19ea2e02010-03-25 15:09:00 -0700604 // see if we should show Back/Next buttons
605 Intent intent = getIntent();
606 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
607
608 findViewById(com.android.internal.R.id.button_bar).setVisibility(View.VISIBLE);
609
610 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
611 backButton.setOnClickListener(new OnClickListener() {
612 public void onClick(View v) {
613 setResult(RESULT_CANCELED);
614 finish();
615 }
616 });
Freeman Ng09dbf182010-08-11 15:45:01 -0700617 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
618 skipButton.setOnClickListener(new OnClickListener() {
619 public void onClick(View v) {
620 setResult(RESULT_OK);
621 finish();
622 }
623 });
Freeman Ng19ea2e02010-03-25 15:09:00 -0700624 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
625 mNextButton.setOnClickListener(new OnClickListener() {
626 public void onClick(View v) {
627 setResult(RESULT_OK);
628 finish();
629 }
630 });
631
632 // set our various button parameters
633 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
634 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
635 if (TextUtils.isEmpty(buttonText)) {
636 mNextButton.setVisibility(View.GONE);
637 }
638 else {
639 mNextButton.setText(buttonText);
640 }
641 }
642 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
643 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
644 if (TextUtils.isEmpty(buttonText)) {
645 backButton.setVisibility(View.GONE);
646 }
647 else {
648 backButton.setText(buttonText);
649 }
650 }
Freeman Ng09dbf182010-08-11 15:45:01 -0700651 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
652 skipButton.setVisibility(View.VISIBLE);
653 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700654 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700655 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700656
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700657 /**
Dianne Hackborn291905e2010-08-17 15:17:15 -0700658 * Returns true if this activity is currently showing the header list.
659 */
660 public boolean hasHeaders() {
661 return getListView().getVisibility() == View.VISIBLE
662 && mPreferenceManager == null;
663 }
664
665 /**
Amith Yamasani423d48b2012-06-20 13:54:53 -0700666 * Returns the Header list
667 * @hide
668 */
669 public List<Header> getHeaders() {
670 return mHeaders;
671 }
672
673 /**
Dianne Hackborn291905e2010-08-17 15:17:15 -0700674 * Returns true if this activity is showing multiple panes -- the headers
675 * and a preference fragment.
676 */
677 public boolean isMultiPane() {
678 return hasHeaders() && mPrefsContainer.getVisibility() == View.VISIBLE;
679 }
680
681 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700682 * Called to determine if the activity should run in multi-pane mode.
683 * The default implementation returns true if the screen is large
684 * enough.
685 */
686 public boolean onIsMultiPane() {
Amith Yamasani405c1af2011-05-26 13:08:25 -0700687 boolean preferMultiPane = getResources().getBoolean(
688 com.android.internal.R.bool.preferences_prefer_dual_pane);
689 return preferMultiPane;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700690 }
691
692 /**
Dianne Hackborn291905e2010-08-17 15:17:15 -0700693 * Called to determine whether the header list should be hidden.
694 * The default implementation returns the
695 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
696 * This is set to false, for example, when the activity is being re-launched
697 * to show a particular preference activity.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700698 */
699 public boolean onIsHidingHeaders() {
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700700 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700701 }
702
703 /**
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700704 * Called to determine the initial header to be shown. The default
705 * implementation simply returns the fragment of the first header. Note
706 * that the returned Header object does not actually need to exist in
707 * your header list -- whatever its fragment is will simply be used to
708 * show for the initial UI.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700709 */
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700710 public Header onGetInitialHeader() {
Dianne Hackborn19470ec2013-02-12 11:42:51 -0800711 for (int i=0; i<mHeaders.size(); i++) {
712 Header h = mHeaders.get(i);
713 if (h.fragment != null) {
714 return h;
715 }
716 }
717 throw new IllegalStateException("Must have at least one header with a fragment");
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700718 }
719
720 /**
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700721 * Called after the header list has been updated ({@link #onBuildHeaders}
722 * has been called and returned due to {@link #invalidateHeaders()}) to
723 * specify the header that should now be selected. The default implementation
724 * returns null to keep whatever header is currently selected.
725 */
726 public Header onGetNewHeader() {
727 return null;
728 }
729
730 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700731 * Called when the activity needs its list of headers build. By
732 * implementing this and adding at least one item to the list, you
733 * will cause the activity to run in its modern fragment mode. Note
734 * that this function may not always be called; for example, if the
735 * activity has been asked to display a particular fragment without
736 * the header list, there is no need to build the headers.
737 *
738 * <p>Typical implementations will use {@link #loadHeadersFromResource}
739 * to fill in the list from a resource.
740 *
741 * @param target The list in which to place the headers.
742 */
743 public void onBuildHeaders(List<Header> target) {
Gilles Debunne39725ac2011-06-14 18:52:41 -0700744 // Should be overloaded by subclasses
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700745 }
746
747 /**
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700748 * Call when you need to change the headers being displayed. Will result
749 * in onBuildHeaders() later being called to retrieve the new list.
750 */
751 public void invalidateHeaders() {
752 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
753 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
754 }
755 }
756
757 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700758 * Parse the given XML file as a header description, adding each
759 * parsed Header into the target list.
760 *
761 * @param resid The XML resource to load and parse.
762 * @param target The list in which the parsed headers should be placed.
763 */
764 public void loadHeadersFromResource(int resid, List<Header> target) {
765 XmlResourceParser parser = null;
766 try {
767 parser = getResources().getXml(resid);
768 AttributeSet attrs = Xml.asAttributeSet(parser);
769
770 int type;
771 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
772 && type != XmlPullParser.START_TAG) {
Gilles Debunne39725ac2011-06-14 18:52:41 -0700773 // Parse next until start tag is found
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700774 }
775
776 String nodeName = parser.getName();
Dianne Hackborndef15372010-08-15 12:43:52 -0700777 if (!"preference-headers".equals(nodeName)) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700778 throw new RuntimeException(
Dianne Hackborndef15372010-08-15 12:43:52 -0700779 "XML document must start with <preference-headers> tag; found"
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700780 + nodeName + " at " + parser.getPositionDescription());
781 }
782
Dianne Hackborndef15372010-08-15 12:43:52 -0700783 Bundle curBundle = null;
784
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700785 final int outerDepth = parser.getDepth();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700786 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
787 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
788 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
789 continue;
790 }
791
792 nodeName = parser.getName();
Dianne Hackborndef15372010-08-15 12:43:52 -0700793 if ("header".equals(nodeName)) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700794 Header header = new Header();
795
Alan Viverettef878c292013-12-02 18:36:00 -0800796 TypedArray sa = obtainStyledAttributes(
797 attrs, com.android.internal.R.styleable.PreferenceHeader);
Amith Yamasanied13cde2010-09-17 16:56:47 -0700798 header.id = sa.getResourceId(
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700799 com.android.internal.R.styleable.PreferenceHeader_id,
800 (int)HEADER_ID_UNDEFINED);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800801 TypedValue tv = sa.peekValue(
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700802 com.android.internal.R.styleable.PreferenceHeader_title);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800803 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
804 if (tv.resourceId != 0) {
805 header.titleRes = tv.resourceId;
806 } else {
807 header.title = tv.string;
808 }
809 }
810 tv = sa.peekValue(
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700811 com.android.internal.R.styleable.PreferenceHeader_summary);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800812 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
813 if (tv.resourceId != 0) {
814 header.summaryRes = tv.resourceId;
815 } else {
816 header.summary = tv.string;
817 }
818 }
819 tv = sa.peekValue(
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700820 com.android.internal.R.styleable.PreferenceHeader_breadCrumbTitle);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800821 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
822 if (tv.resourceId != 0) {
823 header.breadCrumbTitleRes = tv.resourceId;
824 } else {
825 header.breadCrumbTitle = tv.string;
826 }
827 }
828 tv = sa.peekValue(
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700829 com.android.internal.R.styleable.PreferenceHeader_breadCrumbShortTitle);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800830 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
831 if (tv.resourceId != 0) {
832 header.breadCrumbShortTitleRes = tv.resourceId;
833 } else {
834 header.breadCrumbShortTitle = tv.string;
835 }
836 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700837 header.iconRes = sa.getResourceId(
838 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
839 header.fragment = sa.getString(
840 com.android.internal.R.styleable.PreferenceHeader_fragment);
841 sa.recycle();
842
Dianne Hackborndef15372010-08-15 12:43:52 -0700843 if (curBundle == null) {
844 curBundle = new Bundle();
845 }
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700846
847 final int innerDepth = parser.getDepth();
848 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
849 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
850 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
851 continue;
852 }
853
854 String innerNodeName = parser.getName();
855 if (innerNodeName.equals("extra")) {
856 getResources().parseBundleExtra("extra", attrs, curBundle);
857 XmlUtils.skipCurrentTag(parser);
858
859 } else if (innerNodeName.equals("intent")) {
860 header.intent = Intent.parseIntent(getResources(), parser, attrs);
861
862 } else {
863 XmlUtils.skipCurrentTag(parser);
864 }
865 }
866
Dianne Hackborndef15372010-08-15 12:43:52 -0700867 if (curBundle.size() > 0) {
868 header.fragmentArguments = curBundle;
869 curBundle = null;
870 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700871
Dianne Hackborndef15372010-08-15 12:43:52 -0700872 target.add(header);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700873 } else {
874 XmlUtils.skipCurrentTag(parser);
875 }
876 }
877
878 } catch (XmlPullParserException e) {
879 throw new RuntimeException("Error parsing headers", e);
880 } catch (IOException e) {
881 throw new RuntimeException("Error parsing headers", e);
882 } finally {
883 if (parser != null) parser.close();
884 }
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700885 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700886
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700887 /**
888 * Subclasses should override this method and verify that the given fragment is a valid type
Amith Yamasanic2be0d62013-09-23 11:16:28 -0700889 * to be attached to this activity. The default implementation returns <code>true</code> for
890 * apps built for <code>android:targetSdkVersion</code> older than
891 * {@link android.os.Build.VERSION_CODES#KITKAT}. For later versions, it will throw an exception.
Amith Yamasani20915daf2013-08-01 12:44:01 -0700892 * @param fragmentName the class name of the Fragment about to be attached to this activity.
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700893 * @return true if the fragment class name is valid for this Activity and false otherwise.
894 */
895 protected boolean isValidFragment(String fragmentName) {
Chet Haasee8222dd2013-09-05 07:44:18 -0700896 if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.KITKAT) {
Amith Yamasania5001b92013-09-06 14:18:00 -0700897 throw new RuntimeException(
898 "Subclasses of PreferenceActivity must override isValidFragment(String)"
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700899 + " to verify that the Fragment class is valid! " + this.getClass().getName()
900 + " has not checked if fragment " + fragmentName + " is valid.");
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700901 } else {
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700902 return true;
903 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800904 }
905
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700906 /**
907 * Set a footer that should be shown at the bottom of the header list.
908 */
909 public void setListFooter(View view) {
910 mListFooter.removeAllViews();
911 mListFooter.addView(view, new FrameLayout.LayoutParams(
912 FrameLayout.LayoutParams.MATCH_PARENT,
913 FrameLayout.LayoutParams.WRAP_CONTENT));
914 }
915
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800916 @Override
917 protected void onStop() {
918 super.onStop();
Freeman Ng19ea2e02010-03-25 15:09:00 -0700919
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700920 if (mPreferenceManager != null) {
921 mPreferenceManager.dispatchActivityStop();
922 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800923 }
924
925 @Override
926 protected void onDestroy() {
Hiroaki Kuriyama1ebf13e2012-11-16 18:46:49 +0900927 mHandler.removeMessages(MSG_BIND_PREFERENCES);
928 mHandler.removeMessages(MSG_BUILD_HEADERS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800929 super.onDestroy();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700930
931 if (mPreferenceManager != null) {
932 mPreferenceManager.dispatchActivityDestroy();
933 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800934 }
935
936 @Override
937 protected void onSaveInstanceState(Bundle outState) {
938 super.onSaveInstanceState(outState);
939
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700940 if (mHeaders.size() > 0) {
941 outState.putParcelableArrayList(HEADERS_TAG, mHeaders);
942 if (mCurHeader != null) {
943 int index = mHeaders.indexOf(mCurHeader);
944 if (index >= 0) {
945 outState.putInt(CUR_HEADER_TAG, index);
946 }
947 }
948 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700949
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700950 if (mPreferenceManager != null) {
951 final PreferenceScreen preferenceScreen = getPreferenceScreen();
952 if (preferenceScreen != null) {
953 Bundle container = new Bundle();
954 preferenceScreen.saveHierarchyState(container);
955 outState.putBundle(PREFERENCES_TAG, container);
956 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800957 }
958 }
959
960 @Override
961 protected void onRestoreInstanceState(Bundle state) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700962 if (mPreferenceManager != null) {
963 Bundle container = state.getBundle(PREFERENCES_TAG);
964 if (container != null) {
965 final PreferenceScreen preferenceScreen = getPreferenceScreen();
966 if (preferenceScreen != null) {
967 preferenceScreen.restoreHierarchyState(container);
968 mSavedInstanceState = state;
969 return;
970 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800971 }
972 }
Adam Powelle7fea452010-03-18 14:51:39 -0700973
974 // Only call this if we didn't save the instance state for later.
975 // If we did save it, it will be restored when we bind the adapter.
976 super.onRestoreInstanceState(state);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800977 }
978
979 @Override
980 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
981 super.onActivityResult(requestCode, resultCode, data);
Freeman Ng19ea2e02010-03-25 15:09:00 -0700982
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700983 if (mPreferenceManager != null) {
984 mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
985 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800986 }
987
988 @Override
989 public void onContentChanged() {
990 super.onContentChanged();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700991
992 if (mPreferenceManager != null) {
993 postBindPreferences();
994 }
995 }
996
997 @Override
998 protected void onListItemClick(ListView l, View v, int position, long id) {
Amith Yamasani49bdc162013-07-17 15:52:45 -0700999 if (!isResumed()) {
1000 return;
1001 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001002 super.onListItemClick(l, v, position, id);
1003
1004 if (mAdapter != null) {
Gilles Debunne39725ac2011-06-14 18:52:41 -07001005 Object item = mAdapter.getItem(position);
1006 if (item instanceof Header) onHeaderClick((Header) item, position);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001007 }
1008 }
1009
1010 /**
1011 * Called when the user selects an item in the header list. The default
Dianne Hackborne72f2372011-03-16 10:43:18 -07001012 * implementation will call either
1013 * {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001014 * or {@link #switchToHeader(Header)} as appropriate.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001015 *
1016 * @param header The header that was selected.
1017 * @param position The header's position in the list.
1018 */
1019 public void onHeaderClick(Header header, int position) {
Dianne Hackborn5c769a42010-08-26 17:08:08 -07001020 if (header.fragment != null) {
1021 if (mSinglePane) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001022 int titleRes = header.breadCrumbTitleRes;
1023 int shortTitleRes = header.breadCrumbShortTitleRes;
1024 if (titleRes == 0) {
1025 titleRes = header.titleRes;
1026 shortTitleRes = 0;
1027 }
1028 startWithFragment(header.fragment, header.fragmentArguments, null, 0,
1029 titleRes, shortTitleRes);
Dianne Hackborn5c769a42010-08-26 17:08:08 -07001030 } else {
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001031 switchToHeader(header);
Dianne Hackborn5c769a42010-08-26 17:08:08 -07001032 }
1033 } else if (header.intent != null) {
1034 startActivity(header.intent);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001035 }
1036 }
1037
1038 /**
Dianne Hackborne72f2372011-03-16 10:43:18 -07001039 * Called by {@link #startWithFragment(String, Bundle, Fragment, int, int, int)} when
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001040 * in single-pane mode, to build an Intent to launch a new activity showing
1041 * the selected fragment. The default implementation constructs an Intent
1042 * that re-launches the current activity with the appropriate arguments to
1043 * display the fragment.
1044 *
1045 * @param fragmentName The name of the fragment to display.
1046 * @param args Optional arguments to supply to the fragment.
Dianne Hackborne72f2372011-03-16 10:43:18 -07001047 * @param titleRes Optional resource ID of title to show for this item.
Gilles Debunne39725ac2011-06-14 18:52:41 -07001048 * @param shortTitleRes Optional resource ID of short title to show for this item.
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001049 * @return Returns an Intent that can be launched to display the given
1050 * fragment.
1051 */
Dianne Hackborne72f2372011-03-16 10:43:18 -07001052 public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
1053 int titleRes, int shortTitleRes) {
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001054 Intent intent = new Intent(Intent.ACTION_MAIN);
1055 intent.setClass(this, getClass());
1056 intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
1057 intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
Dianne Hackborne72f2372011-03-16 10:43:18 -07001058 intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, titleRes);
1059 intent.putExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, shortTitleRes);
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001060 intent.putExtra(EXTRA_NO_HEADERS, true);
1061 return intent;
1062 }
1063
1064 /**
Dianne Hackborne72f2372011-03-16 10:43:18 -07001065 * Like {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
1066 * but uses a 0 titleRes.
1067 */
1068 public void startWithFragment(String fragmentName, Bundle args,
1069 Fragment resultTo, int resultRequestCode) {
1070 startWithFragment(fragmentName, args, resultTo, resultRequestCode, 0, 0);
1071 }
1072
1073 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001074 * Start a new instance of this activity, showing only the given
1075 * preference fragment. When launched in this mode, the header list
1076 * will be hidden and the given preference fragment will be instantiated
1077 * and fill the entire activity.
1078 *
1079 * @param fragmentName The name of the fragment to display.
Dianne Hackbornb7a2e472010-08-12 16:20:42 -07001080 * @param args Optional arguments to supply to the fragment.
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001081 * @param resultTo Option fragment that should receive the result of
1082 * the activity launch.
1083 * @param resultRequestCode If resultTo is non-null, this is the request
1084 * code in which to report the result.
Dianne Hackborne72f2372011-03-16 10:43:18 -07001085 * @param titleRes Resource ID of string to display for the title of
1086 * this set of preferences.
Gilles Debunne39725ac2011-06-14 18:52:41 -07001087 * @param shortTitleRes Resource ID of string to display for the short title of
Dianne Hackborne72f2372011-03-16 10:43:18 -07001088 * this set of preferences.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001089 */
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001090 public void startWithFragment(String fragmentName, Bundle args,
Dianne Hackborne72f2372011-03-16 10:43:18 -07001091 Fragment resultTo, int resultRequestCode, int titleRes, int shortTitleRes) {
1092 Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001093 if (resultTo == null) {
1094 startActivity(intent);
1095 } else {
1096 resultTo.startActivityForResult(intent, resultRequestCode);
1097 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001098 }
1099
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001100 /**
1101 * Change the base title of the bread crumbs for the current preferences.
1102 * This will normally be called for you. See
1103 * {@link android.app.FragmentBreadCrumbs} for more information.
1104 */
1105 public void showBreadCrumbs(CharSequence title, CharSequence shortTitle) {
1106 if (mFragmentBreadCrumbs == null) {
Amith Yamasani3e860402010-12-10 14:20:51 -08001107 View crumbs = findViewById(android.R.id.title);
1108 // For screens with a different kind of title, don't create breadcrumbs.
Dianne Hackborne72f2372011-03-16 10:43:18 -07001109 try {
1110 mFragmentBreadCrumbs = (FragmentBreadCrumbs)crumbs;
1111 } catch (ClassCastException e) {
Amith Yamasanicf638ac2013-08-05 10:26:18 -07001112 setTitle(title);
Dianne Hackborne72f2372011-03-16 10:43:18 -07001113 return;
1114 }
Amith Yamasani3c9f5192010-12-08 16:48:31 -08001115 if (mFragmentBreadCrumbs == null) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001116 if (title != null) {
1117 setTitle(title);
Amith Yamasani3c9f5192010-12-08 16:48:31 -08001118 }
Dianne Hackborne72f2372011-03-16 10:43:18 -07001119 return;
Jim Millerc57406c2010-12-08 16:01:05 -08001120 }
Amith Yamasani3ec7bac2012-10-03 16:02:51 -07001121 if (mSinglePane) {
1122 mFragmentBreadCrumbs.setVisibility(View.GONE);
1123 // Hide the breadcrumb section completely for single-pane
1124 View bcSection = findViewById(com.android.internal.R.id.breadcrumb_section);
1125 if (bcSection != null) bcSection.setVisibility(View.GONE);
Amith Yamasanicf638ac2013-08-05 10:26:18 -07001126 setTitle(title);
Amith Yamasani3ec7bac2012-10-03 16:02:51 -07001127 }
Amith Yamasani3c9f5192010-12-08 16:48:31 -08001128 mFragmentBreadCrumbs.setMaxVisible(2);
1129 mFragmentBreadCrumbs.setActivity(this);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001130 }
Amith Yamasanicf638ac2013-08-05 10:26:18 -07001131 if (mFragmentBreadCrumbs.getVisibility() != View.VISIBLE) {
1132 setTitle(title);
1133 } else {
1134 mFragmentBreadCrumbs.setTitle(title, shortTitle);
1135 mFragmentBreadCrumbs.setParentTitle(null, null, null);
1136 }
Amith Yamasanic9ecb732010-12-14 14:23:21 -08001137 }
1138
1139 /**
1140 * Should be called after onCreate to ensure that the breadcrumbs, if any, were created.
1141 * This prepends a title to the fragment breadcrumbs and attaches a listener to any clicks
1142 * on the parent entry.
1143 * @param title the title for the breadcrumb
1144 * @param shortTitle the short title for the breadcrumb
1145 */
1146 public void setParentTitle(CharSequence title, CharSequence shortTitle,
1147 OnClickListener listener) {
1148 if (mFragmentBreadCrumbs != null) {
1149 mFragmentBreadCrumbs.setParentTitle(title, shortTitle, listener);
1150 }
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001151 }
1152
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001153 void setSelectedHeader(Header header) {
1154 mCurHeader = header;
1155 int index = mHeaders.indexOf(header);
1156 if (index >= 0) {
1157 getListView().setItemChecked(index, true);
1158 } else {
1159 getListView().clearChoices();
1160 }
Dianne Hackborn34905a92011-07-21 17:30:07 -07001161 showBreadCrumbs(header);
1162 }
1163
1164 void showBreadCrumbs(Header header) {
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001165 if (header != null) {
Dianne Hackborn50ed8292010-12-03 12:30:21 -08001166 CharSequence title = header.getBreadCrumbTitle(getResources());
1167 if (title == null) title = header.getTitle(getResources());
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001168 if (title == null) title = getTitle();
Dianne Hackborn50ed8292010-12-03 12:30:21 -08001169 showBreadCrumbs(title, header.getBreadCrumbShortTitle(getResources()));
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001170 } else {
1171 showBreadCrumbs(getTitle(), null);
1172 }
1173 }
1174
Fabrice Di Meglio5aaae372014-01-08 15:50:34 -08001175 private void switchToHeaderInner(String fragmentName, Bundle args) {
Dianne Hackborn3a57fb92010-11-15 17:58:52 -08001176 getFragmentManager().popBackStack(BACK_STACK_PREFS,
1177 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Amith Yamasani364ed4d2013-07-26 13:37:56 -07001178 if (!isValidFragment(fragmentName)) {
1179 throw new IllegalArgumentException("Invalid fragment for this activity: "
1180 + fragmentName);
1181 }
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001182 Fragment f = Fragment.instantiate(this, fragmentName, args);
Dianne Hackborn48e7b452011-01-17 12:28:35 -08001183 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Dianne Hackborn327fbd22011-01-17 14:38:50 -08001184 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Chet Haase9ff82bf2010-10-05 14:30:51 -07001185 transaction.replace(com.android.internal.R.id.prefs, f);
Dianne Hackborncf407ad2011-03-11 13:17:57 -08001186 transaction.commitAllowingStateLoss();
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001187 }
1188
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001189 /**
1190 * When in two-pane mode, switch the fragment pane to show the given
1191 * preference fragment.
1192 *
1193 * @param fragmentName The name of the fragment to display.
Dianne Hackbornb7a2e472010-08-12 16:20:42 -07001194 * @param args Optional arguments to supply to the fragment.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001195 */
Dianne Hackbornb7a2e472010-08-12 16:20:42 -07001196 public void switchToHeader(String fragmentName, Bundle args) {
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001197 setSelectedHeader(null);
Fabrice Di Meglio5aaae372014-01-08 15:50:34 -08001198 switchToHeaderInner(fragmentName, args);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001199 }
1200
Andrew Stadleraa904f42010-09-02 14:50:08 -07001201 /**
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001202 * When in two-pane mode, switch to the fragment pane to show the given
1203 * preference fragment.
1204 *
1205 * @param header The new header to display.
1206 */
1207 public void switchToHeader(Header header) {
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001208 if (mCurHeader == header) {
1209 // This is the header we are currently displaying. Just make sure
1210 // to pop the stack up to its root state.
Dianne Hackborn3a57fb92010-11-15 17:58:52 -08001211 getFragmentManager().popBackStack(BACK_STACK_PREFS,
1212 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001213 } else {
Dianne Hackborn19470ec2013-02-12 11:42:51 -08001214 if (header.fragment == null) {
1215 throw new IllegalStateException("can't switch to header that has no fragment");
1216 }
Fabrice Di Meglio5aaae372014-01-08 15:50:34 -08001217 switchToHeaderInner(header.fragment, header.fragmentArguments);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001218 setSelectedHeader(header);
1219 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001220 }
1221
1222 Header findBestMatchingHeader(Header cur, ArrayList<Header> from) {
1223 ArrayList<Header> matches = new ArrayList<Header>();
1224 for (int j=0; j<from.size(); j++) {
1225 Header oh = from.get(j);
1226 if (cur == oh || (cur.id != HEADER_ID_UNDEFINED && cur.id == oh.id)) {
1227 // Must be this one.
1228 matches.clear();
1229 matches.add(oh);
1230 break;
1231 }
1232 if (cur.fragment != null) {
1233 if (cur.fragment.equals(oh.fragment)) {
1234 matches.add(oh);
1235 }
1236 } else if (cur.intent != null) {
1237 if (cur.intent.equals(oh.intent)) {
1238 matches.add(oh);
1239 }
1240 } else if (cur.title != null) {
1241 if (cur.title.equals(oh.title)) {
1242 matches.add(oh);
1243 }
1244 }
1245 }
1246 final int NM = matches.size();
1247 if (NM == 1) {
1248 return matches.get(0);
1249 } else if (NM > 1) {
1250 for (int j=0; j<NM; j++) {
1251 Header oh = matches.get(j);
1252 if (cur.fragmentArguments != null &&
1253 cur.fragmentArguments.equals(oh.fragmentArguments)) {
1254 return oh;
1255 }
1256 if (cur.extras != null && cur.extras.equals(oh.extras)) {
1257 return oh;
1258 }
1259 if (cur.title != null && cur.title.equals(oh.title)) {
1260 return oh;
1261 }
1262 }
1263 }
1264 return null;
1265 }
1266
1267 /**
Andrew Stadleraa904f42010-09-02 14:50:08 -07001268 * Start a new fragment.
1269 *
1270 * @param fragment The fragment to start
1271 * @param push If true, the current fragment will be pushed onto the back stack. If false,
1272 * the current fragment will be replaced.
1273 */
1274 public void startPreferenceFragment(Fragment fragment, boolean push) {
Dianne Hackborn48e7b452011-01-17 12:28:35 -08001275 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001276 transaction.replace(com.android.internal.R.id.prefs, fragment);
Andrew Stadleraa904f42010-09-02 14:50:08 -07001277 if (push) {
Chet Haase9ff82bf2010-10-05 14:30:51 -07001278 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
Andrew Stadleraa904f42010-09-02 14:50:08 -07001279 transaction.addToBackStack(BACK_STACK_PREFS);
Chet Haase9ff82bf2010-10-05 14:30:51 -07001280 } else {
Dianne Hackborn327fbd22011-01-17 14:38:50 -08001281 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Andrew Stadleraa904f42010-09-02 14:50:08 -07001282 }
Dianne Hackborncf407ad2011-03-11 13:17:57 -08001283 transaction.commitAllowingStateLoss();
Andrew Stadleraa904f42010-09-02 14:50:08 -07001284 }
1285
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001286 /**
Amith Yamasania47372c2013-08-05 10:55:28 -07001287 * Start a new fragment containing a preference panel. If the preferences
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001288 * are being displayed in multi-pane mode, the given fragment class will
1289 * be instantiated and placed in the appropriate pane. If running in
1290 * single-pane mode, a new activity will be launched in which to show the
1291 * fragment.
1292 *
1293 * @param fragmentClass Full name of the class implementing the fragment.
1294 * @param args Any desired arguments to supply to the fragment.
1295 * @param titleRes Optional resource identifier of the title of this
1296 * fragment.
1297 * @param titleText Optional text of the title of this fragment.
1298 * @param resultTo Optional fragment that result data should be sent to.
1299 * If non-null, resultTo.onActivityResult() will be called when this
1300 * preference panel is done. The launched panel must use
1301 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
1302 * @param resultRequestCode If resultTo is non-null, this is the caller's
1303 * request code to be received with the resut.
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001304 */
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001305 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
1306 CharSequence titleText, Fragment resultTo, int resultRequestCode) {
1307 if (mSinglePane) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001308 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, 0);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001309 } else {
1310 Fragment f = Fragment.instantiate(this, fragmentClass, args);
1311 if (resultTo != null) {
1312 f.setTargetFragment(resultTo, resultRequestCode);
1313 }
Dianne Hackborn48e7b452011-01-17 12:28:35 -08001314 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001315 transaction.replace(com.android.internal.R.id.prefs, f);
1316 if (titleRes != 0) {
1317 transaction.setBreadCrumbTitle(titleRes);
1318 } else if (titleText != null) {
1319 transaction.setBreadCrumbTitle(titleText);
1320 }
1321 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1322 transaction.addToBackStack(BACK_STACK_PREFS);
Dianne Hackborncf407ad2011-03-11 13:17:57 -08001323 transaction.commitAllowingStateLoss();
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001324 }
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001325 }
Amith Yamasani364ed4d2013-07-26 13:37:56 -07001326
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001327 /**
1328 * Called by a preference panel fragment to finish itself.
1329 *
1330 * @param caller The fragment that is asking to be finished.
1331 * @param resultCode Optional result code to send back to the original
1332 * launching fragment.
1333 * @param resultData Optional result data to send back to the original
1334 * launching fragment.
1335 */
1336 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
1337 if (mSinglePane) {
1338 setResult(resultCode, resultData);
1339 finish();
1340 } else {
Dianne Hackborn3a57fb92010-11-15 17:58:52 -08001341 // XXX be smarter about popping the stack.
1342 onBackPressed();
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001343 if (caller != null) {
1344 if (caller.getTargetFragment() != null) {
1345 caller.getTargetFragment().onActivityResult(caller.getTargetRequestCode(),
1346 resultCode, resultData);
1347 }
1348 }
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001349 }
1350 }
1351
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -07001352 @Override
1353 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001354 startPreferencePanel(pref.getFragment(), pref.getExtras(), pref.getTitleRes(),
1355 pref.getTitle(), null, 0);
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -07001356 return true;
1357 }
1358
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001359 /**
1360 * Posts a message to bind the preferences to the list view.
1361 * <p>
1362 * Binding late is preferred as any custom preference types created in
1363 * {@link #onCreate(Bundle)} are able to have their views recycled.
1364 */
1365 private void postBindPreferences() {
1366 if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
1367 mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
1368 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001369
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001370 private void bindPreferences() {
1371 final PreferenceScreen preferenceScreen = getPreferenceScreen();
1372 if (preferenceScreen != null) {
1373 preferenceScreen.bind(getListView());
Adam Powelle7fea452010-03-18 14:51:39 -07001374 if (mSavedInstanceState != null) {
1375 super.onRestoreInstanceState(mSavedInstanceState);
1376 mSavedInstanceState = null;
1377 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001378 }
1379 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001380
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001381 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001382 * Returns the {@link PreferenceManager} used by this activity.
1383 * @return The {@link PreferenceManager}.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001384 *
1385 * @deprecated This function is not relevant for a modern fragment-based
1386 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001387 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001388 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001389 public PreferenceManager getPreferenceManager() {
1390 return mPreferenceManager;
1391 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001392
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001393 private void requirePreferenceManager() {
1394 if (mPreferenceManager == null) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001395 if (mAdapter == null) {
1396 throw new RuntimeException("This should be called after super.onCreate.");
1397 }
1398 throw new RuntimeException(
1399 "Modern two-pane PreferenceActivity requires use of a PreferenceFragment");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001400 }
1401 }
1402
1403 /**
1404 * Sets the root of the preference hierarchy that this activity is showing.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001405 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001406 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001407 *
1408 * @deprecated This function is not relevant for a modern fragment-based
1409 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001410 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001411 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001412 public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001413 requirePreferenceManager();
1414
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001415 if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
1416 postBindPreferences();
1417 CharSequence title = getPreferenceScreen().getTitle();
1418 // Set the title of the activity
1419 if (title != null) {
1420 setTitle(title);
1421 }
1422 }
1423 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001424
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001425 /**
1426 * Gets the root of the preference hierarchy that this activity is showing.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001427 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001428 * @return The {@link PreferenceScreen} that is the root of the preference
1429 * hierarchy.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001430 *
1431 * @deprecated This function is not relevant for a modern fragment-based
1432 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001433 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001434 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001435 public PreferenceScreen getPreferenceScreen() {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001436 if (mPreferenceManager != null) {
1437 return mPreferenceManager.getPreferenceScreen();
1438 }
1439 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001440 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001441
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001442 /**
1443 * Adds preferences from activities that match the given {@link Intent}.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001444 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001445 * @param intent The {@link Intent} to query activities.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001446 *
1447 * @deprecated This function is not relevant for a modern fragment-based
1448 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001449 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001450 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001451 public void addPreferencesFromIntent(Intent intent) {
1452 requirePreferenceManager();
Freeman Ng19ea2e02010-03-25 15:09:00 -07001453
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001454 setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
1455 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001456
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001457 /**
1458 * Inflates the given XML resource and adds the preference hierarchy to the current
1459 * preference hierarchy.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001460 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001461 * @param preferencesResId The XML resource ID to inflate.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001462 *
1463 * @deprecated This function is not relevant for a modern fragment-based
1464 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001465 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001466 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001467 public void addPreferencesFromResource(int preferencesResId) {
1468 requirePreferenceManager();
Freeman Ng19ea2e02010-03-25 15:09:00 -07001469
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001470 setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
1471 getPreferenceScreen()));
1472 }
1473
1474 /**
1475 * {@inheritDoc}
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001476 *
1477 * @deprecated This function is not relevant for a modern fragment-based
1478 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001479 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001480 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001481 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
1482 return false;
1483 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001484
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001485 /**
1486 * Finds a {@link Preference} based on its key.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001487 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001488 * @param key The key of the preference to retrieve.
1489 * @return The {@link Preference} with the key, or null.
1490 * @see PreferenceGroup#findPreference(CharSequence)
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001491 *
1492 * @deprecated This function is not relevant for a modern fragment-based
1493 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001494 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001495 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001496 public Preference findPreference(CharSequence key) {
Freeman Ng19ea2e02010-03-25 15:09:00 -07001497
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001498 if (mPreferenceManager == null) {
1499 return null;
1500 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001501
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001502 return mPreferenceManager.findPreference(key);
1503 }
1504
1505 @Override
1506 protected void onNewIntent(Intent intent) {
1507 if (mPreferenceManager != null) {
1508 mPreferenceManager.dispatchNewIntent(intent);
1509 }
1510 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001511
1512 // give subclasses access to the Next button
1513 /** @hide */
1514 protected boolean hasNextButton() {
1515 return mNextButton != null;
1516 }
1517 /** @hide */
1518 protected Button getNextButton() {
1519 return mNextButton;
1520 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001521}