blob: 2ab5a91a7013e9719fd366a596494567329db858 [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;
Svetoslav Ganov8fc552e2013-11-21 19:36:22 +000036import android.util.Log;
Dianne Hackborn50ed8292010-12-03 12:30:21 -080037import android.util.TypedValue;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070038import android.util.Xml;
39import android.view.LayoutInflater;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import android.view.View;
Amith Yamasani405c1af2011-05-26 13:08:25 -070041import android.view.View.OnClickListener;
Gilles Debunne39725ac2011-06-14 18:52:41 -070042import android.view.ViewGroup;
Dianne Hackborna21e3da2010-09-12 19:27:46 -070043import android.widget.AbsListView;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070044import android.widget.ArrayAdapter;
Gilles Debunne39725ac2011-06-14 18:52:41 -070045import android.widget.BaseAdapter;
Freeman Ng19ea2e02010-03-25 15:09:00 -070046import android.widget.Button;
Dianne Hackborn5c769a42010-08-26 17:08:08 -070047import android.widget.FrameLayout;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070048import android.widget.ImageView;
49import android.widget.ListView;
50import android.widget.TextView;
51
Gilles Debunne39725ac2011-06-14 18:52:41 -070052import com.android.internal.util.XmlUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053
Amith Yamasani405c1af2011-05-26 13:08:25 -070054import org.xmlpull.v1.XmlPullParser;
55import org.xmlpull.v1.XmlPullParserException;
56
Gilles Debunne39725ac2011-06-14 18:52:41 -070057import java.io.IOException;
58import java.util.ArrayList;
59import java.util.List;
60
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061/**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070062 * This is the base class for an activity to show a hierarchy of preferences
63 * to the user. Prior to {@link android.os.Build.VERSION_CODES#HONEYCOMB}
64 * this class only allowed the display of a single set of preference; this
65 * functionality should now be found in the new {@link PreferenceFragment}
66 * class. If you are using PreferenceActivity in its old mode, the documentation
67 * there applies to the deprecated APIs here.
Freeman Ng19ea2e02010-03-25 15:09:00 -070068 *
Gilles Debunne39725ac2011-06-14 18:52:41 -070069 * <p>This activity shows one or more headers of preferences, each of which
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070070 * is associated with a {@link PreferenceFragment} to display the preferences
71 * of that header. The actual layout and display of these associations can
72 * however vary; currently there are two major approaches it may take:
73 *
74 * <ul>
75 * <li>On a small screen it may display only the headers as a single list
76 * when first launched. Selecting one of the header items will re-launch
77 * the activity with it only showing the PreferenceFragment of that header.
78 * <li>On a large screen in may display both the headers and current
79 * PreferenceFragment together as panes. Selecting a header item switches
80 * to showing the correct PreferenceFragment for that item.
81 * </ul>
82 *
83 * <p>Subclasses of PreferenceActivity should implement
84 * {@link #onBuildHeaders} to populate the header list with the desired
85 * items. Doing this implicitly switches the class into its new "headers
86 * + fragments" mode rather than the old style of just showing a single
87 * preferences list.
Scott Maincdd0c592012-07-26 17:03:51 -070088 *
89 * <div class="special reference">
90 * <h3>Developer Guides</h3>
91 * <p>For information about using {@code PreferenceActivity},
92 * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>
93 * guide.</p>
94 * </div>
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070095 *
96 * <a name="SampleCode"></a>
97 * <h3>Sample Code</h3>
98 *
99 * <p>The following sample code shows a simple preference activity that
100 * has two different sets of preferences. The implementation, consisting
101 * of the activity itself as well as its two preference fragments is:</p>
102 *
103 * {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/PreferenceWithHeaders.java
104 * activity}
105 *
106 * <p>The preference_headers resource describes the headers to be displayed
107 * and the fragments associated with them. It is:
108 *
109 * {@sample development/samples/ApiDemos/res/xml/preference_headers.xml headers}
110 *
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700111 * <p>The first header is shown by Prefs1Fragment, which populates itself
112 * from the following XML resource:</p>
113 *
114 * {@sample development/samples/ApiDemos/res/xml/fragmented_preferences.xml preferences}
115 *
116 * <p>Note that this XML resource contains a preference screen holding another
117 * fragment, the Prefs1FragmentInner implemented here. This allows the user
118 * to traverse down a hierarchy of preferences; pressing back will pop each
119 * fragment off the stack to return to the previous preferences.
120 *
121 * <p>See {@link PreferenceFragment} for information on implementing the
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700122 * fragments themselves.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800123 */
124public abstract class PreferenceActivity extends ListActivity implements
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700125 PreferenceManager.OnPreferenceTreeClickListener,
126 PreferenceFragment.OnPreferenceStartFragmentCallback {
Freeman Ng19ea2e02010-03-25 15:09:00 -0700127
Svetoslav Ganov8fc552e2013-11-21 19:36:22 +0000128 private static final String TAG = "PreferenceActivity";
129
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700130 // Constants for state save/restore
131 private static final String HEADERS_TAG = ":android:headers";
132 private static final String CUR_HEADER_TAG = ":android:cur_header";
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700133 private static final String PREFERENCES_TAG = ":android:preferences";
Freeman Ng19ea2e02010-03-25 15:09:00 -0700134
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700135 /**
136 * When starting this activity, the invoking Intent can contain this extra
137 * string to specify which fragment should be initially displayed.
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700138 * <p/>Starting from Key Lime Pie, when this argument is passed in, the PreferenceActivity
139 * will call isValidFragment() to confirm that the fragment class name is valid for this
140 * activity.
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700141 */
142 public static final String EXTRA_SHOW_FRAGMENT = ":android:show_fragment";
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700143
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700144 /**
145 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
Dianne Hackborne72f2372011-03-16 10:43:18 -0700146 * this extra can also be specified to supply a Bundle of arguments to pass
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700147 * to that fragment when it is instantiated during the initial creation
148 * of PreferenceActivity.
149 */
150 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":android:show_fragment_args";
151
152 /**
Dianne Hackborne72f2372011-03-16 10:43:18 -0700153 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
154 * this extra can also be specify to supply the title to be shown for
155 * that fragment.
156 */
157 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":android:show_fragment_title";
158
159 /**
160 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
161 * this extra can also be specify to supply the short title to be shown for
162 * that fragment.
163 */
164 public static final String EXTRA_SHOW_FRAGMENT_SHORT_TITLE
165 = ":android:show_fragment_short_title";
166
167 /**
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700168 * When starting this activity, the invoking Intent can contain this extra
169 * boolean that the header list should not be displayed. This is most often
170 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
171 * the activity to display a specific fragment that the user has navigated
172 * to.
173 */
174 public static final String EXTRA_NO_HEADERS = ":android:no_headers";
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700175
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700176 private static final String BACK_STACK_PREFS = ":android:prefs";
177
Freeman Ng19ea2e02010-03-25 15:09:00 -0700178 // extras that allow any preference activity to be launched as part of a wizard
179
180 // show Back and Next buttons? takes boolean parameter
181 // Back will then return RESULT_CANCELED and Next RESULT_OK
182 private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
183
Freeman Ng09dbf182010-08-11 15:45:01 -0700184 // add a Skip button?
185 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
186
Freeman Ng19ea2e02010-03-25 15:09:00 -0700187 // specify custom text for the Back or Next buttons, or cause a button to not appear
188 // at all by setting it to null
189 private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
190 private static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
191
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700192 // --- State for new mode when showing a list of headers + prefs fragment
193
194 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
195
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700196 private FrameLayout mListFooter;
197
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800198 private ViewGroup mPrefsContainer;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700199
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700200 private FragmentBreadCrumbs mFragmentBreadCrumbs;
201
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700202 private boolean mSinglePane;
203
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700204 private Header mCurHeader;
205
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700206 // --- State for old mode when showing a single preference list
Freeman Ng19ea2e02010-03-25 15:09:00 -0700207
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800208 private PreferenceManager mPreferenceManager;
Freeman Ng19ea2e02010-03-25 15:09:00 -0700209
Adam Powelle7fea452010-03-18 14:51:39 -0700210 private Bundle mSavedInstanceState;
211
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700212 // --- Common state
213
214 private Button mNextButton;
215
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 /**
217 * The starting request code given out to preference framework.
218 */
219 private static final int FIRST_REQUEST_CODE = 100;
Freeman Ng19ea2e02010-03-25 15:09:00 -0700220
Dianne Hackborn3e449ce2010-09-11 20:52:31 -0700221 private static final int MSG_BIND_PREFERENCES = 1;
222 private static final int MSG_BUILD_HEADERS = 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800223 private Handler mHandler = new Handler() {
224 @Override
225 public void handleMessage(Message msg) {
226 switch (msg.what) {
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700227 case MSG_BIND_PREFERENCES: {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800228 bindPreferences();
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700229 } break;
230 case MSG_BUILD_HEADERS: {
231 ArrayList<Header> oldHeaders = new ArrayList<Header>(mHeaders);
232 mHeaders.clear();
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700233 onBuildHeaders(mHeaders);
Gilles Debunne39725ac2011-06-14 18:52:41 -0700234 if (mAdapter instanceof BaseAdapter) {
235 ((BaseAdapter) mAdapter).notifyDataSetChanged();
Andrew Stadler83681eb2010-11-04 15:49:05 -0700236 }
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700237 Header header = onGetNewHeader();
238 if (header != null && header.fragment != null) {
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700239 Header mappedHeader = findBestMatchingHeader(header, oldHeaders);
240 if (mappedHeader == null || mCurHeader != mappedHeader) {
241 switchToHeader(header);
242 }
243 } else if (mCurHeader != null) {
244 Header mappedHeader = findBestMatchingHeader(mCurHeader, mHeaders);
245 if (mappedHeader != null) {
246 setSelectedHeader(mappedHeader);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700247 }
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700248 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700249 } break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250 }
251 }
252 };
253
Andrew Stadler468c3232010-08-17 16:16:42 -0700254 private static class HeaderAdapter extends ArrayAdapter<Header> {
255 private static class HeaderViewHolder {
256 ImageView icon;
257 TextView title;
258 TextView summary;
259 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700260
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700261 private LayoutInflater mInflater;
262
263 public HeaderAdapter(Context context, List<Header> objects) {
264 super(context, 0, objects);
265 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
266 }
267
268 @Override
269 public View getView(int position, View convertView, ViewGroup parent) {
270 HeaderViewHolder holder;
271 View view;
272
273 if (convertView == null) {
Dianne Hackbornd0fa3712010-09-14 18:57:14 -0700274 view = mInflater.inflate(com.android.internal.R.layout.preference_header_item,
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700275 parent, false);
276 holder = new HeaderViewHolder();
Andrew Stadler468c3232010-08-17 16:16:42 -0700277 holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon);
278 holder.title = (TextView) view.findViewById(com.android.internal.R.id.title);
279 holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700280 view.setTag(holder);
281 } else {
282 view = convertView;
Andrew Stadler468c3232010-08-17 16:16:42 -0700283 holder = (HeaderViewHolder) view.getTag();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700284 }
285
Andrew Stadler468c3232010-08-17 16:16:42 -0700286 // All view fields must be updated every time, because the view may be recycled
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700287 Header header = getItem(position);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700288 holder.icon.setImageResource(header.iconRes);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800289 holder.title.setText(header.getTitle(getContext().getResources()));
290 CharSequence summary = header.getSummary(getContext().getResources());
291 if (!TextUtils.isEmpty(summary)) {
Andrew Stadler468c3232010-08-17 16:16:42 -0700292 holder.summary.setVisibility(View.VISIBLE);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800293 holder.summary.setText(summary);
294 } else {
295 holder.summary.setVisibility(View.GONE);
Andrew Stadler468c3232010-08-17 16:16:42 -0700296 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700297
298 return view;
299 }
300 }
301
302 /**
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700303 * Default value for {@link Header#id Header.id} indicating that no
304 * identifier value is set. All other values (including those below -1)
305 * are valid.
306 */
307 public static final long HEADER_ID_UNDEFINED = -1;
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700308
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700309 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700310 * Description of a single Header item that the user can select.
311 */
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700312 public static final class Header implements Parcelable {
313 /**
314 * Identifier for this header, to correlate with a new list when
315 * it is updated. The default value is
316 * {@link PreferenceActivity#HEADER_ID_UNDEFINED}, meaning no id.
317 * @attr ref android.R.styleable#PreferenceHeader_id
318 */
319 public long id = HEADER_ID_UNDEFINED;
320
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700321 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800322 * Resource ID of title of the header that is shown to the user.
323 * @attr ref android.R.styleable#PreferenceHeader_title
324 */
325 public int titleRes;
326
327 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700328 * Title of the header that is shown to the user.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700329 * @attr ref android.R.styleable#PreferenceHeader_title
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700330 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700331 public CharSequence title;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700332
333 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800334 * Resource ID of optional summary describing what this header controls.
335 * @attr ref android.R.styleable#PreferenceHeader_summary
336 */
337 public int summaryRes;
338
339 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700340 * Optional summary describing what this header controls.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700341 * @attr ref android.R.styleable#PreferenceHeader_summary
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700342 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700343 public CharSequence summary;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700344
345 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800346 * Resource ID of optional text to show as the title in the bread crumb.
347 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
348 */
349 public int breadCrumbTitleRes;
350
351 /**
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700352 * Optional text to show as the title in the bread crumb.
353 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
354 */
355 public CharSequence breadCrumbTitle;
356
357 /**
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800358 * Resource ID of optional text to show as the short title in the bread crumb.
359 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
360 */
361 public int breadCrumbShortTitleRes;
362
363 /**
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700364 * Optional text to show as the short title in the bread crumb.
365 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
366 */
367 public CharSequence breadCrumbShortTitle;
368
369 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700370 * Optional icon resource to show for this header.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700371 * @attr ref android.R.styleable#PreferenceHeader_icon
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700372 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700373 public int iconRes;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700374
375 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700376 * Full class name of the fragment to display when this header is
377 * selected.
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700378 * @attr ref android.R.styleable#PreferenceHeader_fragment
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700379 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700380 public String fragment;
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700381
382 /**
383 * Optional arguments to supply to the fragment when it is
384 * instantiated.
385 */
Andrew Stadler468c3232010-08-17 16:16:42 -0700386 public Bundle fragmentArguments;
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700387
388 /**
389 * Intent to launch when the preference is selected.
390 */
391 public Intent intent;
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700392
393 /**
394 * Optional additional data for use by subclasses of PreferenceActivity.
395 */
396 public Bundle extras;
397
398 public Header() {
Gilles Debunne39725ac2011-06-14 18:52:41 -0700399 // Empty
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700400 }
401
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800402 /**
403 * Return the currently set title. If {@link #titleRes} is set,
404 * this resource is loaded from <var>res</var> and returned. Otherwise
405 * {@link #title} is returned.
406 */
407 public CharSequence getTitle(Resources res) {
408 if (titleRes != 0) {
409 return res.getText(titleRes);
410 }
411 return title;
412 }
413
414 /**
415 * Return the currently set summary. If {@link #summaryRes} is set,
416 * this resource is loaded from <var>res</var> and returned. Otherwise
417 * {@link #summary} is returned.
418 */
419 public CharSequence getSummary(Resources res) {
420 if (summaryRes != 0) {
421 return res.getText(summaryRes);
422 }
Dianne Hackborn9d071802010-12-08 14:49:15 -0800423 return summary;
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800424 }
425
426 /**
427 * Return the currently set bread crumb title. If {@link #breadCrumbTitleRes} is set,
428 * this resource is loaded from <var>res</var> and returned. Otherwise
429 * {@link #breadCrumbTitle} is returned.
430 */
431 public CharSequence getBreadCrumbTitle(Resources res) {
432 if (breadCrumbTitleRes != 0) {
433 return res.getText(breadCrumbTitleRes);
434 }
435 return breadCrumbTitle;
436 }
437
438 /**
439 * Return the currently set bread crumb short title. If
440 * {@link #breadCrumbShortTitleRes} is set,
441 * this resource is loaded from <var>res</var> and returned. Otherwise
442 * {@link #breadCrumbShortTitle} is returned.
443 */
444 public CharSequence getBreadCrumbShortTitle(Resources res) {
445 if (breadCrumbShortTitleRes != 0) {
446 return res.getText(breadCrumbShortTitleRes);
447 }
448 return breadCrumbShortTitle;
449 }
450
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700451 @Override
452 public int describeContents() {
453 return 0;
454 }
455
456 @Override
457 public void writeToParcel(Parcel dest, int flags) {
458 dest.writeLong(id);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800459 dest.writeInt(titleRes);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700460 TextUtils.writeToParcel(title, dest, flags);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800461 dest.writeInt(summaryRes);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700462 TextUtils.writeToParcel(summary, dest, flags);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800463 dest.writeInt(breadCrumbTitleRes);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700464 TextUtils.writeToParcel(breadCrumbTitle, dest, flags);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800465 dest.writeInt(breadCrumbShortTitleRes);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700466 TextUtils.writeToParcel(breadCrumbShortTitle, dest, flags);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700467 dest.writeInt(iconRes);
468 dest.writeString(fragment);
469 dest.writeBundle(fragmentArguments);
470 if (intent != null) {
471 dest.writeInt(1);
472 intent.writeToParcel(dest, flags);
473 } else {
474 dest.writeInt(0);
475 }
476 dest.writeBundle(extras);
477 }
478
479 public void readFromParcel(Parcel in) {
480 id = in.readLong();
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800481 titleRes = in.readInt();
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700482 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800483 summaryRes = in.readInt();
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700484 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800485 breadCrumbTitleRes = in.readInt();
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700486 breadCrumbTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800487 breadCrumbShortTitleRes = in.readInt();
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700488 breadCrumbShortTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700489 iconRes = in.readInt();
490 fragment = in.readString();
491 fragmentArguments = in.readBundle();
492 if (in.readInt() != 0) {
493 intent = Intent.CREATOR.createFromParcel(in);
494 }
495 extras = in.readBundle();
496 }
497
498 Header(Parcel in) {
499 readFromParcel(in);
500 }
501
502 public static final Creator<Header> CREATOR = new Creator<Header>() {
503 public Header createFromParcel(Parcel source) {
504 return new Header(source);
505 }
506 public Header[] newArray(int size) {
507 return new Header[size];
508 }
509 };
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700510 }
511
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800512 @Override
513 protected void onCreate(Bundle savedInstanceState) {
514 super.onCreate(savedInstanceState);
515
Amith Yamasani405c1af2011-05-26 13:08:25 -0700516 setContentView(com.android.internal.R.layout.preference_list_content);
Freeman Ng19ea2e02010-03-25 15:09:00 -0700517
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700518 mListFooter = (FrameLayout)findViewById(com.android.internal.R.id.list_footer);
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800519 mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs_frame);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700520 boolean hidingHeaders = onIsHidingHeaders();
521 mSinglePane = hidingHeaders || !onIsMultiPane();
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700522 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
523 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
Dianne Hackborne72f2372011-03-16 10:43:18 -0700524 int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
525 int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700526
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700527 if (savedInstanceState != null) {
528 // We are restarting from a previous saved state; used that to
529 // initialize, instead of starting fresh.
530 ArrayList<Header> headers = savedInstanceState.getParcelableArrayList(HEADERS_TAG);
531 if (headers != null) {
532 mHeaders.addAll(headers);
533 int curHeader = savedInstanceState.getInt(CUR_HEADER_TAG,
Gilles Debunne39725ac2011-06-14 18:52:41 -0700534 (int) HEADER_ID_UNDEFINED);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700535 if (curHeader >= 0 && curHeader < mHeaders.size()) {
536 setSelectedHeader(mHeaders.get(curHeader));
537 }
538 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700539
540 } else {
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700541 if (initialFragment != null && mSinglePane) {
542 // If we are just showing a fragment, we want to run in
543 // new fragment mode, but don't need to compute and show
544 // the headers.
545 switchToHeader(initialFragment, initialArguments);
Dianne Hackborne72f2372011-03-16 10:43:18 -0700546 if (initialTitle != 0) {
547 CharSequence initialTitleStr = getText(initialTitle);
548 CharSequence initialShortTitleStr = initialShortTitle != 0
549 ? getText(initialShortTitle) : null;
550 showBreadCrumbs(initialTitleStr, initialShortTitleStr);
551 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700552
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700553 } else {
554 // We need to try to build the headers.
555 onBuildHeaders(mHeaders);
556
557 // If there are headers, then at this point we need to show
558 // them and, depending on the screen, we may also show in-line
559 // the currently selected preference fragment.
560 if (mHeaders.size() > 0) {
561 if (!mSinglePane) {
562 if (initialFragment == null) {
563 Header h = onGetInitialHeader();
564 switchToHeader(h);
565 } else {
566 switchToHeader(initialFragment, initialArguments);
567 }
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700568 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700569 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700570 }
571 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700572
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700573 // The default configuration is to only show the list view. Adjust
574 // visibility for other configurations.
575 if (initialFragment != null && mSinglePane) {
576 // Single pane, showing just a prefs fragment.
Amith Yamasani05fbc312010-09-26 13:29:01 -0700577 findViewById(com.android.internal.R.id.headers).setVisibility(View.GONE);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700578 mPrefsContainer.setVisibility(View.VISIBLE);
Dianne Hackborn34905a92011-07-21 17:30:07 -0700579 if (initialTitle != 0) {
Svetoslav Ganov8fc552e2013-11-21 19:36:22 +0000580 CharSequence initialTitleStr = getText(initialTitle);
581 CharSequence initialShortTitleStr = initialShortTitle != 0
Dianne Hackborn34905a92011-07-21 17:30:07 -0700582 ? getText(initialShortTitle) : null;
Svetoslav Ganov8fc552e2013-11-21 19:36:22 +0000583 showBreadCrumbs(initialTitleStr, initialShortTitleStr);
Dianne Hackborn34905a92011-07-21 17:30:07 -0700584 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700585 } else if (mHeaders.size() > 0) {
Gilles Debunne39725ac2011-06-14 18:52:41 -0700586 setListAdapter(new HeaderAdapter(this, mHeaders));
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700587 if (!mSinglePane) {
588 // Multi-pane.
589 getListView().setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
Dianne Hackbornd0fa3712010-09-14 18:57:14 -0700590 if (mCurHeader != null) {
591 setSelectedHeader(mCurHeader);
592 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700593 mPrefsContainer.setVisibility(View.VISIBLE);
594 }
595 } else {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700596 // If there are no headers, we are in the old "just show a screen
597 // of preferences" mode.
Amith Yamasani405c1af2011-05-26 13:08:25 -0700598 setContentView(com.android.internal.R.layout.preference_list_content_single);
Amith Yamasani8da35292010-11-05 09:15:51 -0700599 mListFooter = (FrameLayout) findViewById(com.android.internal.R.id.list_footer);
Amith Yamasani3c9f5192010-12-08 16:48:31 -0800600 mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs);
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700601 mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
602 mPreferenceManager.setOnPreferenceTreeClickListener(this);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700603 }
604
Freeman Ng19ea2e02010-03-25 15:09:00 -0700605 // see if we should show Back/Next buttons
606 Intent intent = getIntent();
607 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
608
609 findViewById(com.android.internal.R.id.button_bar).setVisibility(View.VISIBLE);
610
611 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
612 backButton.setOnClickListener(new OnClickListener() {
613 public void onClick(View v) {
614 setResult(RESULT_CANCELED);
615 finish();
616 }
617 });
Freeman Ng09dbf182010-08-11 15:45:01 -0700618 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
619 skipButton.setOnClickListener(new OnClickListener() {
620 public void onClick(View v) {
621 setResult(RESULT_OK);
622 finish();
623 }
624 });
Freeman Ng19ea2e02010-03-25 15:09:00 -0700625 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
626 mNextButton.setOnClickListener(new OnClickListener() {
627 public void onClick(View v) {
628 setResult(RESULT_OK);
629 finish();
630 }
631 });
632
633 // set our various button parameters
634 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
635 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
636 if (TextUtils.isEmpty(buttonText)) {
637 mNextButton.setVisibility(View.GONE);
638 }
639 else {
640 mNextButton.setText(buttonText);
641 }
642 }
643 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
644 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
645 if (TextUtils.isEmpty(buttonText)) {
646 backButton.setVisibility(View.GONE);
647 }
648 else {
649 backButton.setText(buttonText);
650 }
651 }
Freeman Ng09dbf182010-08-11 15:45:01 -0700652 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
653 skipButton.setVisibility(View.VISIBLE);
654 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700655 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700656 }
Freeman Ng19ea2e02010-03-25 15:09:00 -0700657
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700658 /**
Dianne Hackborn291905e2010-08-17 15:17:15 -0700659 * Returns true if this activity is currently showing the header list.
660 */
661 public boolean hasHeaders() {
662 return getListView().getVisibility() == View.VISIBLE
663 && mPreferenceManager == null;
664 }
665
666 /**
Amith Yamasani423d48b2012-06-20 13:54:53 -0700667 * Returns the Header list
668 * @hide
669 */
670 public List<Header> getHeaders() {
671 return mHeaders;
672 }
673
674 /**
Dianne Hackborn291905e2010-08-17 15:17:15 -0700675 * Returns true if this activity is showing multiple panes -- the headers
676 * and a preference fragment.
677 */
678 public boolean isMultiPane() {
679 return hasHeaders() && mPrefsContainer.getVisibility() == View.VISIBLE;
680 }
681
682 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700683 * Called to determine if the activity should run in multi-pane mode.
684 * The default implementation returns true if the screen is large
685 * enough.
686 */
687 public boolean onIsMultiPane() {
Amith Yamasani405c1af2011-05-26 13:08:25 -0700688 boolean preferMultiPane = getResources().getBoolean(
689 com.android.internal.R.bool.preferences_prefer_dual_pane);
690 return preferMultiPane;
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700691 }
692
693 /**
Dianne Hackborn291905e2010-08-17 15:17:15 -0700694 * Called to determine whether the header list should be hidden.
695 * The default implementation returns the
696 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
697 * This is set to false, for example, when the activity is being re-launched
698 * to show a particular preference activity.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700699 */
700 public boolean onIsHidingHeaders() {
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700701 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700702 }
703
704 /**
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700705 * Called to determine the initial header to be shown. The default
706 * implementation simply returns the fragment of the first header. Note
707 * that the returned Header object does not actually need to exist in
708 * your header list -- whatever its fragment is will simply be used to
709 * show for the initial UI.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700710 */
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700711 public Header onGetInitialHeader() {
Dianne Hackborn19470ec2013-02-12 11:42:51 -0800712 for (int i=0; i<mHeaders.size(); i++) {
713 Header h = mHeaders.get(i);
714 if (h.fragment != null) {
715 return h;
716 }
717 }
718 throw new IllegalStateException("Must have at least one header with a fragment");
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700719 }
720
721 /**
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700722 * Called after the header list has been updated ({@link #onBuildHeaders}
723 * has been called and returned due to {@link #invalidateHeaders()}) to
724 * specify the header that should now be selected. The default implementation
725 * returns null to keep whatever header is currently selected.
726 */
727 public Header onGetNewHeader() {
728 return null;
729 }
730
731 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700732 * Called when the activity needs its list of headers build. By
733 * implementing this and adding at least one item to the list, you
734 * will cause the activity to run in its modern fragment mode. Note
735 * that this function may not always be called; for example, if the
736 * activity has been asked to display a particular fragment without
737 * the header list, there is no need to build the headers.
738 *
739 * <p>Typical implementations will use {@link #loadHeadersFromResource}
740 * to fill in the list from a resource.
741 *
742 * @param target The list in which to place the headers.
743 */
744 public void onBuildHeaders(List<Header> target) {
Gilles Debunne39725ac2011-06-14 18:52:41 -0700745 // Should be overloaded by subclasses
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700746 }
747
748 /**
Dianne Hackbornb7a2e472010-08-12 16:20:42 -0700749 * Call when you need to change the headers being displayed. Will result
750 * in onBuildHeaders() later being called to retrieve the new list.
751 */
752 public void invalidateHeaders() {
753 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
754 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
755 }
756 }
757
758 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700759 * Parse the given XML file as a header description, adding each
760 * parsed Header into the target list.
761 *
762 * @param resid The XML resource to load and parse.
763 * @param target The list in which the parsed headers should be placed.
764 */
765 public void loadHeadersFromResource(int resid, List<Header> target) {
766 XmlResourceParser parser = null;
767 try {
768 parser = getResources().getXml(resid);
769 AttributeSet attrs = Xml.asAttributeSet(parser);
770
771 int type;
772 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
773 && type != XmlPullParser.START_TAG) {
Gilles Debunne39725ac2011-06-14 18:52:41 -0700774 // Parse next until start tag is found
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700775 }
776
777 String nodeName = parser.getName();
Dianne Hackborndef15372010-08-15 12:43:52 -0700778 if (!"preference-headers".equals(nodeName)) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700779 throw new RuntimeException(
Dianne Hackborndef15372010-08-15 12:43:52 -0700780 "XML document must start with <preference-headers> tag; found"
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700781 + nodeName + " at " + parser.getPositionDescription());
782 }
783
Dianne Hackborndef15372010-08-15 12:43:52 -0700784 Bundle curBundle = null;
785
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700786 final int outerDepth = parser.getDepth();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700787 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
788 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
789 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
790 continue;
791 }
792
793 nodeName = parser.getName();
Dianne Hackborndef15372010-08-15 12:43:52 -0700794 if ("header".equals(nodeName)) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700795 Header header = new Header();
796
797 TypedArray sa = getResources().obtainAttributes(attrs,
798 com.android.internal.R.styleable.PreferenceHeader);
Amith Yamasanied13cde2010-09-17 16:56:47 -0700799 header.id = sa.getResourceId(
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700800 com.android.internal.R.styleable.PreferenceHeader_id,
801 (int)HEADER_ID_UNDEFINED);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800802 TypedValue tv = sa.peekValue(
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700803 com.android.internal.R.styleable.PreferenceHeader_title);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800804 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
805 if (tv.resourceId != 0) {
806 header.titleRes = tv.resourceId;
807 } else {
808 header.title = tv.string;
809 }
810 }
811 tv = sa.peekValue(
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700812 com.android.internal.R.styleable.PreferenceHeader_summary);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800813 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
814 if (tv.resourceId != 0) {
815 header.summaryRes = tv.resourceId;
816 } else {
817 header.summary = tv.string;
818 }
819 }
820 tv = sa.peekValue(
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700821 com.android.internal.R.styleable.PreferenceHeader_breadCrumbTitle);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800822 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
823 if (tv.resourceId != 0) {
824 header.breadCrumbTitleRes = tv.resourceId;
825 } else {
826 header.breadCrumbTitle = tv.string;
827 }
828 }
829 tv = sa.peekValue(
Dianne Hackbornc6669ca2010-09-16 01:33:24 -0700830 com.android.internal.R.styleable.PreferenceHeader_breadCrumbShortTitle);
Dianne Hackborn50ed8292010-12-03 12:30:21 -0800831 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
832 if (tv.resourceId != 0) {
833 header.breadCrumbShortTitleRes = tv.resourceId;
834 } else {
835 header.breadCrumbShortTitle = tv.string;
836 }
837 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700838 header.iconRes = sa.getResourceId(
839 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
840 header.fragment = sa.getString(
841 com.android.internal.R.styleable.PreferenceHeader_fragment);
842 sa.recycle();
843
Dianne Hackborndef15372010-08-15 12:43:52 -0700844 if (curBundle == null) {
845 curBundle = new Bundle();
846 }
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700847
848 final int innerDepth = parser.getDepth();
849 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
850 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
851 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
852 continue;
853 }
854
855 String innerNodeName = parser.getName();
856 if (innerNodeName.equals("extra")) {
857 getResources().parseBundleExtra("extra", attrs, curBundle);
858 XmlUtils.skipCurrentTag(parser);
859
860 } else if (innerNodeName.equals("intent")) {
861 header.intent = Intent.parseIntent(getResources(), parser, attrs);
862
863 } else {
864 XmlUtils.skipCurrentTag(parser);
865 }
866 }
867
Dianne Hackborndef15372010-08-15 12:43:52 -0700868 if (curBundle.size() > 0) {
869 header.fragmentArguments = curBundle;
870 curBundle = null;
871 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700872
Dianne Hackborndef15372010-08-15 12:43:52 -0700873 target.add(header);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700874 } else {
875 XmlUtils.skipCurrentTag(parser);
876 }
877 }
878
879 } catch (XmlPullParserException e) {
880 throw new RuntimeException("Error parsing headers", e);
881 } catch (IOException e) {
882 throw new RuntimeException("Error parsing headers", e);
883 } finally {
884 if (parser != null) parser.close();
885 }
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700886 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700887
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700888 /**
889 * Subclasses should override this method and verify that the given fragment is a valid type
Amith Yamasanic2be0d62013-09-23 11:16:28 -0700890 * to be attached to this activity. The default implementation returns <code>true</code> for
891 * apps built for <code>android:targetSdkVersion</code> older than
892 * {@link android.os.Build.VERSION_CODES#KITKAT}. For later versions, it will throw an exception.
Amith Yamasani20915daf2013-08-01 12:44:01 -0700893 * @param fragmentName the class name of the Fragment about to be attached to this activity.
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700894 * @return true if the fragment class name is valid for this Activity and false otherwise.
895 */
896 protected boolean isValidFragment(String fragmentName) {
Chet Haasee8222dd2013-09-05 07:44:18 -0700897 if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.KITKAT) {
Amith Yamasania5001b92013-09-06 14:18:00 -0700898 throw new RuntimeException(
899 "Subclasses of PreferenceActivity must override isValidFragment(String)"
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700900 + " to verify that the Fragment class is valid! " + this.getClass().getName()
901 + " has not checked if fragment " + fragmentName + " is valid.");
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700902 } else {
Amith Yamasani364ed4d2013-07-26 13:37:56 -0700903 return true;
904 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800905 }
906
Dianne Hackborn5c769a42010-08-26 17:08:08 -0700907 /**
908 * Set a footer that should be shown at the bottom of the header list.
909 */
910 public void setListFooter(View view) {
911 mListFooter.removeAllViews();
912 mListFooter.addView(view, new FrameLayout.LayoutParams(
913 FrameLayout.LayoutParams.MATCH_PARENT,
914 FrameLayout.LayoutParams.WRAP_CONTENT));
915 }
916
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800917 @Override
918 protected void onStop() {
919 super.onStop();
Freeman Ng19ea2e02010-03-25 15:09:00 -0700920
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700921 if (mPreferenceManager != null) {
922 mPreferenceManager.dispatchActivityStop();
923 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800924 }
925
926 @Override
927 protected void onDestroy() {
Hiroaki Kuriyama1ebf13e2012-11-16 18:46:49 +0900928 mHandler.removeMessages(MSG_BIND_PREFERENCES);
929 mHandler.removeMessages(MSG_BUILD_HEADERS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800930 super.onDestroy();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700931
932 if (mPreferenceManager != null) {
933 mPreferenceManager.dispatchActivityDestroy();
934 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800935 }
936
937 @Override
938 protected void onSaveInstanceState(Bundle outState) {
939 super.onSaveInstanceState(outState);
940
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700941 if (mHeaders.size() > 0) {
942 outState.putParcelableArrayList(HEADERS_TAG, mHeaders);
943 if (mCurHeader != null) {
944 int index = mHeaders.indexOf(mCurHeader);
945 if (index >= 0) {
946 outState.putInt(CUR_HEADER_TAG, index);
947 }
948 }
949 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -0700950
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700951 if (mPreferenceManager != null) {
952 final PreferenceScreen preferenceScreen = getPreferenceScreen();
953 if (preferenceScreen != null) {
954 Bundle container = new Bundle();
955 preferenceScreen.saveHierarchyState(container);
956 outState.putBundle(PREFERENCES_TAG, container);
957 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800958 }
959 }
960
961 @Override
962 protected void onRestoreInstanceState(Bundle state) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700963 if (mPreferenceManager != null) {
964 Bundle container = state.getBundle(PREFERENCES_TAG);
965 if (container != null) {
966 final PreferenceScreen preferenceScreen = getPreferenceScreen();
967 if (preferenceScreen != null) {
968 preferenceScreen.restoreHierarchyState(container);
969 mSavedInstanceState = state;
970 return;
971 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800972 }
973 }
Adam Powelle7fea452010-03-18 14:51:39 -0700974
975 // Only call this if we didn't save the instance state for later.
976 // If we did save it, it will be restored when we bind the adapter.
977 super.onRestoreInstanceState(state);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800978 }
979
980 @Override
981 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
982 super.onActivityResult(requestCode, resultCode, data);
Freeman Ng19ea2e02010-03-25 15:09:00 -0700983
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700984 if (mPreferenceManager != null) {
985 mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
986 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800987 }
988
989 @Override
990 public void onContentChanged() {
991 super.onContentChanged();
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700992
993 if (mPreferenceManager != null) {
994 postBindPreferences();
995 }
996 }
997
998 @Override
999 protected void onListItemClick(ListView l, View v, int position, long id) {
Amith Yamasani49bdc162013-07-17 15:52:45 -07001000 if (!isResumed()) {
1001 return;
1002 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001003 super.onListItemClick(l, v, position, id);
1004
1005 if (mAdapter != null) {
Gilles Debunne39725ac2011-06-14 18:52:41 -07001006 Object item = mAdapter.getItem(position);
1007 if (item instanceof Header) onHeaderClick((Header) item, position);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001008 }
1009 }
1010
1011 /**
1012 * Called when the user selects an item in the header list. The default
Dianne Hackborne72f2372011-03-16 10:43:18 -07001013 * implementation will call either
1014 * {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001015 * or {@link #switchToHeader(Header)} as appropriate.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001016 *
1017 * @param header The header that was selected.
1018 * @param position The header's position in the list.
1019 */
1020 public void onHeaderClick(Header header, int position) {
Dianne Hackborn5c769a42010-08-26 17:08:08 -07001021 if (header.fragment != null) {
1022 if (mSinglePane) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001023 int titleRes = header.breadCrumbTitleRes;
1024 int shortTitleRes = header.breadCrumbShortTitleRes;
1025 if (titleRes == 0) {
1026 titleRes = header.titleRes;
1027 shortTitleRes = 0;
1028 }
1029 startWithFragment(header.fragment, header.fragmentArguments, null, 0,
1030 titleRes, shortTitleRes);
Dianne Hackborn5c769a42010-08-26 17:08:08 -07001031 } else {
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001032 switchToHeader(header);
Dianne Hackborn5c769a42010-08-26 17:08:08 -07001033 }
1034 } else if (header.intent != null) {
1035 startActivity(header.intent);
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001036 }
1037 }
1038
1039 /**
Dianne Hackborne72f2372011-03-16 10:43:18 -07001040 * Called by {@link #startWithFragment(String, Bundle, Fragment, int, int, int)} when
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001041 * in single-pane mode, to build an Intent to launch a new activity showing
1042 * the selected fragment. The default implementation constructs an Intent
1043 * that re-launches the current activity with the appropriate arguments to
1044 * display the fragment.
1045 *
1046 * @param fragmentName The name of the fragment to display.
1047 * @param args Optional arguments to supply to the fragment.
Dianne Hackborne72f2372011-03-16 10:43:18 -07001048 * @param titleRes Optional resource ID of title to show for this item.
Gilles Debunne39725ac2011-06-14 18:52:41 -07001049 * @param shortTitleRes Optional resource ID of short title to show for this item.
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001050 * @return Returns an Intent that can be launched to display the given
1051 * fragment.
1052 */
Dianne Hackborne72f2372011-03-16 10:43:18 -07001053 public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
1054 int titleRes, int shortTitleRes) {
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001055 Intent intent = new Intent(Intent.ACTION_MAIN);
1056 intent.setClass(this, getClass());
1057 intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
1058 intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
Dianne Hackborne72f2372011-03-16 10:43:18 -07001059 intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, titleRes);
1060 intent.putExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, shortTitleRes);
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001061 intent.putExtra(EXTRA_NO_HEADERS, true);
1062 return intent;
1063 }
1064
1065 /**
Dianne Hackborne72f2372011-03-16 10:43:18 -07001066 * Like {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
1067 * but uses a 0 titleRes.
1068 */
1069 public void startWithFragment(String fragmentName, Bundle args,
1070 Fragment resultTo, int resultRequestCode) {
1071 startWithFragment(fragmentName, args, resultTo, resultRequestCode, 0, 0);
1072 }
1073
1074 /**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001075 * Start a new instance of this activity, showing only the given
1076 * preference fragment. When launched in this mode, the header list
1077 * will be hidden and the given preference fragment will be instantiated
1078 * and fill the entire activity.
1079 *
1080 * @param fragmentName The name of the fragment to display.
Dianne Hackbornb7a2e472010-08-12 16:20:42 -07001081 * @param args Optional arguments to supply to the fragment.
Dianne Hackbornb1a6e4392011-03-15 16:23:01 -07001082 * @param resultTo Option fragment that should receive the result of
1083 * the activity launch.
1084 * @param resultRequestCode If resultTo is non-null, this is the request
1085 * code in which to report the result.
Dianne Hackborne72f2372011-03-16 10:43:18 -07001086 * @param titleRes Resource ID of string to display for the title of
1087 * this set of preferences.
Gilles Debunne39725ac2011-06-14 18:52:41 -07001088 * @param shortTitleRes Resource ID of string to display for the short title of
Dianne Hackborne72f2372011-03-16 10:43:18 -07001089 * this set of preferences.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001090 */
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001091 public void startWithFragment(String fragmentName, Bundle args,
Dianne Hackborne72f2372011-03-16 10:43:18 -07001092 Fragment resultTo, int resultRequestCode, int titleRes, int shortTitleRes) {
1093 Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001094 if (resultTo == null) {
1095 startActivity(intent);
1096 } else {
1097 resultTo.startActivityForResult(intent, resultRequestCode);
1098 }
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001099 }
1100
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001101 /**
1102 * Change the base title of the bread crumbs for the current preferences.
1103 * This will normally be called for you. See
1104 * {@link android.app.FragmentBreadCrumbs} for more information.
1105 */
1106 public void showBreadCrumbs(CharSequence title, CharSequence shortTitle) {
1107 if (mFragmentBreadCrumbs == null) {
Amith Yamasani3e860402010-12-10 14:20:51 -08001108 View crumbs = findViewById(android.R.id.title);
1109 // For screens with a different kind of title, don't create breadcrumbs.
Dianne Hackborne72f2372011-03-16 10:43:18 -07001110 try {
1111 mFragmentBreadCrumbs = (FragmentBreadCrumbs)crumbs;
1112 } catch (ClassCastException e) {
Amith Yamasanicf638ac2013-08-05 10:26:18 -07001113 setTitle(title);
Dianne Hackborne72f2372011-03-16 10:43:18 -07001114 return;
1115 }
Amith Yamasani3c9f5192010-12-08 16:48:31 -08001116 if (mFragmentBreadCrumbs == null) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001117 if (title != null) {
1118 setTitle(title);
Amith Yamasani3c9f5192010-12-08 16:48:31 -08001119 }
Dianne Hackborne72f2372011-03-16 10:43:18 -07001120 return;
Jim Millerc57406c2010-12-08 16:01:05 -08001121 }
Amith Yamasani3ec7bac2012-10-03 16:02:51 -07001122 if (mSinglePane) {
1123 mFragmentBreadCrumbs.setVisibility(View.GONE);
1124 // Hide the breadcrumb section completely for single-pane
1125 View bcSection = findViewById(com.android.internal.R.id.breadcrumb_section);
1126 if (bcSection != null) bcSection.setVisibility(View.GONE);
Amith Yamasanicf638ac2013-08-05 10:26:18 -07001127 setTitle(title);
Amith Yamasani3ec7bac2012-10-03 16:02:51 -07001128 }
Amith Yamasani3c9f5192010-12-08 16:48:31 -08001129 mFragmentBreadCrumbs.setMaxVisible(2);
1130 mFragmentBreadCrumbs.setActivity(this);
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001131 }
Amith Yamasanicf638ac2013-08-05 10:26:18 -07001132 if (mFragmentBreadCrumbs.getVisibility() != View.VISIBLE) {
1133 setTitle(title);
1134 } else {
1135 mFragmentBreadCrumbs.setTitle(title, shortTitle);
1136 mFragmentBreadCrumbs.setParentTitle(null, null, null);
1137 }
Amith Yamasanic9ecb732010-12-14 14:23:21 -08001138 }
1139
1140 /**
1141 * Should be called after onCreate to ensure that the breadcrumbs, if any, were created.
1142 * This prepends a title to the fragment breadcrumbs and attaches a listener to any clicks
1143 * on the parent entry.
1144 * @param title the title for the breadcrumb
1145 * @param shortTitle the short title for the breadcrumb
1146 */
1147 public void setParentTitle(CharSequence title, CharSequence shortTitle,
1148 OnClickListener listener) {
1149 if (mFragmentBreadCrumbs != null) {
1150 mFragmentBreadCrumbs.setParentTitle(title, shortTitle, listener);
1151 }
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001152 }
1153
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001154 void setSelectedHeader(Header header) {
1155 mCurHeader = header;
1156 int index = mHeaders.indexOf(header);
1157 if (index >= 0) {
1158 getListView().setItemChecked(index, true);
1159 } else {
1160 getListView().clearChoices();
1161 }
Dianne Hackborn34905a92011-07-21 17:30:07 -07001162 showBreadCrumbs(header);
1163 }
1164
1165 void showBreadCrumbs(Header header) {
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001166 if (header != null) {
Dianne Hackborn50ed8292010-12-03 12:30:21 -08001167 CharSequence title = header.getBreadCrumbTitle(getResources());
1168 if (title == null) title = header.getTitle(getResources());
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001169 if (title == null) title = getTitle();
Dianne Hackborn50ed8292010-12-03 12:30:21 -08001170 showBreadCrumbs(title, header.getBreadCrumbShortTitle(getResources()));
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001171 } else {
1172 showBreadCrumbs(getTitle(), null);
1173 }
1174 }
1175
Amith Yamasanif5cbaed2010-10-27 16:14:20 -07001176 private void switchToHeaderInner(String fragmentName, Bundle args, int direction) {
Dianne Hackborn3a57fb92010-11-15 17:58:52 -08001177 getFragmentManager().popBackStack(BACK_STACK_PREFS,
1178 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Amith Yamasani364ed4d2013-07-26 13:37:56 -07001179 if (!isValidFragment(fragmentName)) {
1180 throw new IllegalArgumentException("Invalid fragment for this activity: "
1181 + fragmentName);
1182 }
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001183 Fragment f = Fragment.instantiate(this, fragmentName, args);
Dianne Hackborn48e7b452011-01-17 12:28:35 -08001184 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Dianne Hackborn327fbd22011-01-17 14:38:50 -08001185 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Chet Haase9ff82bf2010-10-05 14:30:51 -07001186 transaction.replace(com.android.internal.R.id.prefs, f);
Dianne Hackborncf407ad2011-03-11 13:17:57 -08001187 transaction.commitAllowingStateLoss();
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001188 }
1189
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001190 /**
1191 * When in two-pane mode, switch the fragment pane to show the given
1192 * preference fragment.
1193 *
1194 * @param fragmentName The name of the fragment to display.
Dianne Hackbornb7a2e472010-08-12 16:20:42 -07001195 * @param args Optional arguments to supply to the fragment.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001196 */
Dianne Hackbornb7a2e472010-08-12 16:20:42 -07001197 public void switchToHeader(String fragmentName, Bundle args) {
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001198 setSelectedHeader(null);
Amith Yamasanif5cbaed2010-10-27 16:14:20 -07001199 switchToHeaderInner(fragmentName, args, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001200 }
1201
Andrew Stadleraa904f42010-09-02 14:50:08 -07001202 /**
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001203 * When in two-pane mode, switch to the fragment pane to show the given
1204 * preference fragment.
1205 *
1206 * @param header The new header to display.
1207 */
1208 public void switchToHeader(Header header) {
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001209 if (mCurHeader == header) {
1210 // This is the header we are currently displaying. Just make sure
1211 // to pop the stack up to its root state.
Dianne Hackborn3a57fb92010-11-15 17:58:52 -08001212 getFragmentManager().popBackStack(BACK_STACK_PREFS,
1213 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001214 } else {
Dianne Hackborn19470ec2013-02-12 11:42:51 -08001215 if (header.fragment == null) {
1216 throw new IllegalStateException("can't switch to header that has no fragment");
1217 }
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001218 int direction = mHeaders.indexOf(header) - mHeaders.indexOf(mCurHeader);
1219 switchToHeaderInner(header.fragment, header.fragmentArguments, direction);
1220 setSelectedHeader(header);
1221 }
Dianne Hackborna21e3da2010-09-12 19:27:46 -07001222 }
1223
1224 Header findBestMatchingHeader(Header cur, ArrayList<Header> from) {
1225 ArrayList<Header> matches = new ArrayList<Header>();
1226 for (int j=0; j<from.size(); j++) {
1227 Header oh = from.get(j);
1228 if (cur == oh || (cur.id != HEADER_ID_UNDEFINED && cur.id == oh.id)) {
1229 // Must be this one.
1230 matches.clear();
1231 matches.add(oh);
1232 break;
1233 }
1234 if (cur.fragment != null) {
1235 if (cur.fragment.equals(oh.fragment)) {
1236 matches.add(oh);
1237 }
1238 } else if (cur.intent != null) {
1239 if (cur.intent.equals(oh.intent)) {
1240 matches.add(oh);
1241 }
1242 } else if (cur.title != null) {
1243 if (cur.title.equals(oh.title)) {
1244 matches.add(oh);
1245 }
1246 }
1247 }
1248 final int NM = matches.size();
1249 if (NM == 1) {
1250 return matches.get(0);
1251 } else if (NM > 1) {
1252 for (int j=0; j<NM; j++) {
1253 Header oh = matches.get(j);
1254 if (cur.fragmentArguments != null &&
1255 cur.fragmentArguments.equals(oh.fragmentArguments)) {
1256 return oh;
1257 }
1258 if (cur.extras != null && cur.extras.equals(oh.extras)) {
1259 return oh;
1260 }
1261 if (cur.title != null && cur.title.equals(oh.title)) {
1262 return oh;
1263 }
1264 }
1265 }
1266 return null;
1267 }
1268
1269 /**
Andrew Stadleraa904f42010-09-02 14:50:08 -07001270 * Start a new fragment.
1271 *
1272 * @param fragment The fragment to start
1273 * @param push If true, the current fragment will be pushed onto the back stack. If false,
1274 * the current fragment will be replaced.
1275 */
1276 public void startPreferenceFragment(Fragment fragment, boolean push) {
Dianne Hackborn48e7b452011-01-17 12:28:35 -08001277 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001278 transaction.replace(com.android.internal.R.id.prefs, fragment);
Andrew Stadleraa904f42010-09-02 14:50:08 -07001279 if (push) {
Chet Haase9ff82bf2010-10-05 14:30:51 -07001280 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
Andrew Stadleraa904f42010-09-02 14:50:08 -07001281 transaction.addToBackStack(BACK_STACK_PREFS);
Chet Haase9ff82bf2010-10-05 14:30:51 -07001282 } else {
Dianne Hackborn327fbd22011-01-17 14:38:50 -08001283 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Andrew Stadleraa904f42010-09-02 14:50:08 -07001284 }
Dianne Hackborncf407ad2011-03-11 13:17:57 -08001285 transaction.commitAllowingStateLoss();
Andrew Stadleraa904f42010-09-02 14:50:08 -07001286 }
1287
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001288 /**
Amith Yamasania47372c2013-08-05 10:55:28 -07001289 * Start a new fragment containing a preference panel. If the preferences
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001290 * are being displayed in multi-pane mode, the given fragment class will
1291 * be instantiated and placed in the appropriate pane. If running in
1292 * single-pane mode, a new activity will be launched in which to show the
1293 * fragment.
1294 *
1295 * @param fragmentClass Full name of the class implementing the fragment.
1296 * @param args Any desired arguments to supply to the fragment.
1297 * @param titleRes Optional resource identifier of the title of this
1298 * fragment.
1299 * @param titleText Optional text of the title of this fragment.
1300 * @param resultTo Optional fragment that result data should be sent to.
1301 * If non-null, resultTo.onActivityResult() will be called when this
1302 * preference panel is done. The launched panel must use
1303 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
1304 * @param resultRequestCode If resultTo is non-null, this is the caller's
1305 * request code to be received with the resut.
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001306 */
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001307 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
1308 CharSequence titleText, Fragment resultTo, int resultRequestCode) {
1309 if (mSinglePane) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001310 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, 0);
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001311 } else {
1312 Fragment f = Fragment.instantiate(this, fragmentClass, args);
1313 if (resultTo != null) {
1314 f.setTargetFragment(resultTo, resultRequestCode);
1315 }
Dianne Hackborn48e7b452011-01-17 12:28:35 -08001316 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001317 transaction.replace(com.android.internal.R.id.prefs, f);
1318 if (titleRes != 0) {
1319 transaction.setBreadCrumbTitle(titleRes);
1320 } else if (titleText != null) {
1321 transaction.setBreadCrumbTitle(titleText);
1322 }
1323 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1324 transaction.addToBackStack(BACK_STACK_PREFS);
Dianne Hackborncf407ad2011-03-11 13:17:57 -08001325 transaction.commitAllowingStateLoss();
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001326 }
Dianne Hackbornc6669ca2010-09-16 01:33:24 -07001327 }
Amith Yamasani364ed4d2013-07-26 13:37:56 -07001328
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001329 /**
1330 * Called by a preference panel fragment to finish itself.
1331 *
1332 * @param caller The fragment that is asking to be finished.
1333 * @param resultCode Optional result code to send back to the original
1334 * launching fragment.
1335 * @param resultData Optional result data to send back to the original
1336 * launching fragment.
1337 */
1338 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
1339 if (mSinglePane) {
1340 setResult(resultCode, resultData);
1341 finish();
1342 } else {
Dianne Hackborn3a57fb92010-11-15 17:58:52 -08001343 // XXX be smarter about popping the stack.
1344 onBackPressed();
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001345 if (caller != null) {
1346 if (caller.getTargetFragment() != null) {
1347 caller.getTargetFragment().onActivityResult(caller.getTargetRequestCode(),
1348 resultCode, resultData);
1349 }
1350 }
Dianne Hackborn8eb2e242010-11-01 12:31:24 -07001351 }
1352 }
1353
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -07001354 @Override
1355 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
Dianne Hackborne72f2372011-03-16 10:43:18 -07001356 startPreferencePanel(pref.getFragment(), pref.getExtras(), pref.getTitleRes(),
1357 pref.getTitle(), null, 0);
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -07001358 return true;
1359 }
1360
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001361 /**
1362 * Posts a message to bind the preferences to the list view.
1363 * <p>
1364 * Binding late is preferred as any custom preference types created in
1365 * {@link #onCreate(Bundle)} are able to have their views recycled.
1366 */
1367 private void postBindPreferences() {
1368 if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
1369 mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
1370 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001371
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001372 private void bindPreferences() {
1373 final PreferenceScreen preferenceScreen = getPreferenceScreen();
1374 if (preferenceScreen != null) {
1375 preferenceScreen.bind(getListView());
Adam Powelle7fea452010-03-18 14:51:39 -07001376 if (mSavedInstanceState != null) {
1377 super.onRestoreInstanceState(mSavedInstanceState);
1378 mSavedInstanceState = null;
1379 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001380 }
1381 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001382
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001383 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001384 * Returns the {@link PreferenceManager} used by this activity.
1385 * @return The {@link PreferenceManager}.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001386 *
1387 * @deprecated This function is not relevant for a modern fragment-based
1388 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001389 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001390 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001391 public PreferenceManager getPreferenceManager() {
1392 return mPreferenceManager;
1393 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001394
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001395 private void requirePreferenceManager() {
1396 if (mPreferenceManager == null) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001397 if (mAdapter == null) {
1398 throw new RuntimeException("This should be called after super.onCreate.");
1399 }
1400 throw new RuntimeException(
1401 "Modern two-pane PreferenceActivity requires use of a PreferenceFragment");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001402 }
1403 }
1404
1405 /**
1406 * Sets the root of the preference hierarchy that this activity is showing.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001407 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001408 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001409 *
1410 * @deprecated This function is not relevant for a modern fragment-based
1411 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001412 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001413 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001414 public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001415 requirePreferenceManager();
1416
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001417 if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
1418 postBindPreferences();
1419 CharSequence title = getPreferenceScreen().getTitle();
1420 // Set the title of the activity
1421 if (title != null) {
1422 setTitle(title);
1423 }
1424 }
1425 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001426
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001427 /**
1428 * Gets the root of the preference hierarchy that this activity is showing.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001429 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001430 * @return The {@link PreferenceScreen} that is the root of the preference
1431 * hierarchy.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001432 *
1433 * @deprecated This function is not relevant for a modern fragment-based
1434 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001435 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001436 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001437 public PreferenceScreen getPreferenceScreen() {
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001438 if (mPreferenceManager != null) {
1439 return mPreferenceManager.getPreferenceScreen();
1440 }
1441 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001442 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001443
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001444 /**
1445 * Adds preferences from activities that match the given {@link Intent}.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001446 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001447 * @param intent The {@link Intent} to query activities.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001448 *
1449 * @deprecated This function is not relevant for a modern fragment-based
1450 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001451 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001452 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001453 public void addPreferencesFromIntent(Intent intent) {
1454 requirePreferenceManager();
Freeman Ng19ea2e02010-03-25 15:09:00 -07001455
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001456 setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
1457 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001458
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001459 /**
1460 * Inflates the given XML resource and adds the preference hierarchy to the current
1461 * preference hierarchy.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001462 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001463 * @param preferencesResId The XML resource ID to inflate.
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001464 *
1465 * @deprecated This function is not relevant for a modern fragment-based
1466 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001467 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001468 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001469 public void addPreferencesFromResource(int preferencesResId) {
1470 requirePreferenceManager();
Freeman Ng19ea2e02010-03-25 15:09:00 -07001471
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001472 setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
1473 getPreferenceScreen()));
1474 }
1475
1476 /**
1477 * {@inheritDoc}
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001478 *
1479 * @deprecated This function is not relevant for a modern fragment-based
1480 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001481 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001482 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001483 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
1484 return false;
1485 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001486
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001487 /**
1488 * Finds a {@link Preference} based on its key.
Freeman Ng19ea2e02010-03-25 15:09:00 -07001489 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001490 * @param key The key of the preference to retrieve.
1491 * @return The {@link Preference} with the key, or null.
1492 * @see PreferenceGroup#findPreference(CharSequence)
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001493 *
1494 * @deprecated This function is not relevant for a modern fragment-based
1495 * PreferenceActivity.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001496 */
Dianne Hackbornb1ad5972010-08-02 17:30:33 -07001497 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001498 public Preference findPreference(CharSequence key) {
Freeman Ng19ea2e02010-03-25 15:09:00 -07001499
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001500 if (mPreferenceManager == null) {
1501 return null;
1502 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001503
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001504 return mPreferenceManager.findPreference(key);
1505 }
1506
1507 @Override
1508 protected void onNewIntent(Intent intent) {
1509 if (mPreferenceManager != null) {
1510 mPreferenceManager.dispatchNewIntent(intent);
1511 }
1512 }
Freeman Ng19ea2e02010-03-25 15:09:00 -07001513
1514 // give subclasses access to the Next button
1515 /** @hide */
1516 protected boolean hasNextButton() {
1517 return mNextButton != null;
1518 }
1519 /** @hide */
1520 protected Button getNextButton() {
1521 return mNextButton;
1522 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001523}