blob: 793709bd1ddb03321dcad2a88a639a7653430cfb [file] [log] [blame]
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings;
18
19import android.accounts.Account;
20import android.accounts.AccountManager;
21import android.accounts.OnAccountsUpdateListener;
22import android.app.ActionBar;
23import android.app.Activity;
24import android.app.Fragment;
25import android.app.FragmentManager;
26import android.app.FragmentTransaction;
27import android.app.AlertDialog;
28import android.app.Dialog;
29import android.app.DialogFragment;
30import android.app.admin.DevicePolicyManager;
31import android.content.BroadcastReceiver;
32import android.content.Context;
33import android.content.Intent;
34import android.content.IntentFilter;
35import android.content.SharedPreferences;
36import android.content.pm.ActivityInfo;
37import android.content.pm.PackageManager;
38import android.content.pm.PackageManager.NameNotFoundException;
39import android.content.pm.ResolveInfo;
40import android.content.res.Configuration;
41import android.content.res.Resources;
42import android.content.res.TypedArray;
43import android.content.res.XmlResourceParser;
44import android.graphics.drawable.Drawable;
45import android.nfc.NfcAdapter;
46import android.os.Bundle;
47import android.os.Handler;
48import android.os.INetworkManagementService;
49import android.os.Message;
50import android.os.Parcel;
51import android.os.Parcelable;
52import android.os.RemoteException;
53import android.os.ServiceManager;
54import android.os.UserHandle;
55import android.os.UserManager;
56import android.preference.Preference;
57import android.preference.PreferenceFragment;
58import android.preference.PreferenceManager;
59import android.preference.PreferenceScreen;
60import android.support.v4.app.ActionBarDrawerToggle;
61import android.support.v4.widget.DrawerLayout;
62import android.text.TextUtils;
63import android.util.AttributeSet;
64import android.util.Log;
65import android.util.Pair;
66import android.util.TypedValue;
67import android.util.Xml;
68import android.view.LayoutInflater;
69import android.view.MenuItem;
70import android.view.View;
71import android.view.View.OnClickListener;
72import android.view.ViewGroup;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +000073import android.widget.AbsListView;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080074import android.widget.AdapterView;
75import android.widget.ArrayAdapter;
76import android.widget.Button;
77import android.widget.ImageButton;
78import android.widget.ImageView;
79import android.widget.ListView;
80import android.widget.Switch;
81import android.widget.TextView;
82
83import com.android.internal.util.ArrayUtils;
84import com.android.internal.util.XmlUtils;
85import com.android.settings.accessibility.AccessibilitySettings;
86import com.android.settings.accessibility.CaptionPropertiesFragment;
87import com.android.settings.accounts.AccountSyncSettings;
88import com.android.settings.accounts.AuthenticatorHelper;
89import com.android.settings.accounts.ChooseAccountFragment;
90import com.android.settings.accounts.ManageAccountsSettings;
91import com.android.settings.applications.ManageApplications;
92import com.android.settings.applications.ProcessStatsUi;
93import com.android.settings.bluetooth.BluetoothEnabler;
94import com.android.settings.bluetooth.BluetoothSettings;
95import com.android.settings.dashboard.DashboardSummary;
96import com.android.settings.deviceinfo.Memory;
97import com.android.settings.deviceinfo.UsbSettings;
98import com.android.settings.fuelgauge.PowerUsageSummary;
99import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
100import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
101import com.android.settings.inputmethod.SpellCheckersSettings;
102import com.android.settings.inputmethod.UserDictionaryList;
103import com.android.settings.location.LocationSettings;
104import com.android.settings.nfc.AndroidBeam;
105import com.android.settings.nfc.PaymentSettings;
106import com.android.settings.print.PrintJobSettingsFragment;
107import com.android.settings.print.PrintSettingsFragment;
108import com.android.settings.tts.TextToSpeechSettings;
109import com.android.settings.users.UserSettings;
110import com.android.settings.vpn2.VpnSettings;
111import com.android.settings.wfd.WifiDisplaySettings;
112import com.android.settings.wifi.AdvancedWifiSettings;
113import com.android.settings.wifi.WifiEnabler;
114import com.android.settings.wifi.WifiSettings;
115import com.android.settings.wifi.p2p.WifiP2pSettings;
116import org.xmlpull.v1.XmlPullParser;
117import org.xmlpull.v1.XmlPullParserException;
118
119import java.io.IOException;
120import java.util.ArrayList;
121import java.util.Collections;
122import java.util.Comparator;
123import java.util.HashMap;
124import java.util.List;
125
126public class SettingsActivity extends Activity
127 implements PreferenceManager.OnPreferenceTreeClickListener,
128 PreferenceFragment.OnPreferenceStartFragmentCallback,
129 ButtonBarHandler, OnAccountsUpdateListener, FragmentManager.OnBackStackChangedListener {
130
131 private static final String LOG_TAG = "Settings";
132
133 // Constants for state save/restore
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800134 private static final String SAVE_KEY_HEADERS_TAG = ":settings:headers";
135 private static final String SAVE_KEY_CURRENT_HEADER_TAG = ":settings:cur_header";
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800136 private static final String SAVE_KEY_TITLES_TAG = ":settings:titles";
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800137
138 /**
139 * When starting this activity, the invoking Intent can contain this extra
140 * string to specify which fragment should be initially displayed.
141 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
142 * will call isValidFragment() to confirm that the fragment class name is valid for this
143 * activity.
144 */
145 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
146
147 /**
148 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
149 * this extra can also be specified to supply a Bundle of arguments to pass
150 * to that fragment when it is instantiated during the initial creation
151 * of the activity.
152 */
153 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
154
155 /**
156 * When starting this activity, the invoking Intent can contain this extra
157 * boolean that the header list should not be displayed. This is most often
158 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
159 * the activity to display a specific fragment that the user has navigated
160 * to.
161 */
162 public static final String EXTRA_NO_HEADERS = ":settings:no_headers";
163
164 // extras that allow any preference activity to be launched as part of a wizard
165
166 // show Back and Next buttons? takes boolean parameter
167 // Back will then return RESULT_CANCELED and Next RESULT_OK
168 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
169
170 // add a Skip button?
171 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
172
173 // specify custom text for the Back or Next buttons, or cause a button to not appear
174 // at all by setting it to null
175 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
176 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
177
178 /**
179 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
180 * this extra can also be specify to supply the title to be shown for
181 * that fragment.
182 */
183 protected static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
184
185 private static final String BACK_STACK_PREFS = ":settings:prefs";
186
187 private static final String META_DATA_KEY_HEADER_ID =
188 "com.android.settings.TOP_LEVEL_HEADER_ID";
189
190 private static final String META_DATA_KEY_FRAGMENT_CLASS =
191 "com.android.settings.FRAGMENT_CLASS";
192
193 private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
194
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800195 private static boolean sShowNoHomeNotice = false;
196
197 private String mFragmentClass;
198 private int mTopLevelHeaderId;
199 private Header mFirstHeader;
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800200 private Header mSelectedHeader;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800201 private Header mCurrentHeader;
202
203 // Show only these settings for restricted users
204 private int[] SETTINGS_FOR_RESTRICTED = {
205 R.id.wireless_section,
206 R.id.wifi_settings,
207 R.id.bluetooth_settings,
208 R.id.data_usage_settings,
209 R.id.wireless_settings,
210 R.id.device_section,
211 R.id.sound_settings,
212 R.id.display_settings,
213 R.id.storage_settings,
214 R.id.application_settings,
215 R.id.battery_settings,
216 R.id.personal_section,
217 R.id.location_settings,
218 R.id.security_settings,
219 R.id.language_settings,
220 R.id.user_settings,
221 R.id.account_settings,
222 R.id.account_add,
223 R.id.system_section,
224 R.id.date_time_settings,
225 R.id.about_settings,
226 R.id.accessibility_settings,
227 R.id.print_settings,
228 R.id.nfc_payment_settings,
229 R.id.home_settings
230 };
231
232 private static final String[] ENTRY_FRAGMENTS = {
233 WirelessSettings.class.getName(),
234 WifiSettings.class.getName(),
235 AdvancedWifiSettings.class.getName(),
236 BluetoothSettings.class.getName(),
237 TetherSettings.class.getName(),
238 WifiP2pSettings.class.getName(),
239 VpnSettings.class.getName(),
240 DateTimeSettings.class.getName(),
241 LocalePicker.class.getName(),
242 InputMethodAndLanguageSettings.class.getName(),
243 SpellCheckersSettings.class.getName(),
244 UserDictionaryList.class.getName(),
245 UserDictionarySettings.class.getName(),
246 SoundSettings.class.getName(),
247 DisplaySettings.class.getName(),
248 DeviceInfoSettings.class.getName(),
249 ManageApplications.class.getName(),
250 ProcessStatsUi.class.getName(),
251 NotificationStation.class.getName(),
252 LocationSettings.class.getName(),
253 SecuritySettings.class.getName(),
254 PrivacySettings.class.getName(),
255 DeviceAdminSettings.class.getName(),
256 AccessibilitySettings.class.getName(),
257 CaptionPropertiesFragment.class.getName(),
258 com.android.settings.accessibility.ToggleInversionPreferenceFragment.class.getName(),
259 com.android.settings.accessibility.ToggleContrastPreferenceFragment.class.getName(),
260 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
261 TextToSpeechSettings.class.getName(),
262 Memory.class.getName(),
263 DevelopmentSettings.class.getName(),
264 UsbSettings.class.getName(),
265 AndroidBeam.class.getName(),
266 WifiDisplaySettings.class.getName(),
267 PowerUsageSummary.class.getName(),
268 AccountSyncSettings.class.getName(),
269 CryptKeeperSettings.class.getName(),
270 DataUsageSummary.class.getName(),
271 DreamSettings.class.getName(),
272 UserSettings.class.getName(),
273 NotificationAccessSettings.class.getName(),
274 ManageAccountsSettings.class.getName(),
275 PrintSettingsFragment.class.getName(),
276 PrintJobSettingsFragment.class.getName(),
277 TrustedCredentialsSettings.class.getName(),
278 PaymentSettings.class.getName(),
279 KeyboardLayoutPickerFragment.class.getName(),
280 ChooseAccountFragment.class.getName(),
Fabrice Di Meglioe6c9a5d2014-02-11 19:03:27 -0800281 DashboardSummary.class.getName()
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800282 };
283
284 private SharedPreferences mDevelopmentPreferences;
285 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
286
287 // TODO: Update Call Settings based on airplane mode state.
288
289 protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
290
291 private AuthenticatorHelper mAuthenticatorHelper;
292 private boolean mListeningToAccountUpdates;
293
294 private Button mNextButton;
295
296 private boolean mBatteryPresent = true;
297 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
298
299 @Override
300 public void onReceive(Context context, Intent intent) {
301 String action = intent.getAction();
302 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
303 boolean batteryPresent = Utils.isBatteryPresent(intent);
304
305 if (mBatteryPresent != batteryPresent) {
306 mBatteryPresent = batteryPresent;
307 invalidateHeaders();
308 }
309 }
310 }
311 };
312
313 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800314 private HeaderAdapter mHeaderAdapter;
315
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800316 static private class TitlePair extends Pair<Integer, CharSequence> implements Parcelable {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800317
318 public TitlePair(Integer first, CharSequence second) {
319 super(first, second);
320 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800321
322 @Override
323 public int describeContents() {
324 return 0;
325 }
326
327 @Override
328 public void writeToParcel(Parcel dest, int flags) {
329 dest.writeInt(first);
330 TextUtils.writeToParcel(second, dest, flags);
331 }
332
333 TitlePair(Parcel in) {
334 super(in.readInt(), TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
335 }
336
337 public static final Creator<TitlePair> CREATOR = new Creator<TitlePair>() {
338 public TitlePair createFromParcel(Parcel source) {
339 return new TitlePair(source);
340 }
341 public TitlePair[] newArray(int size) {
342 return new TitlePair[size];
343 }
344 };
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800345 }
346
347 private final ArrayList<TitlePair> mTitleStack = new ArrayList<TitlePair>();
348
349 private DrawerLayout mDrawerLayout;
350 private ListView mDrawer;
351 private ActionBarDrawerToggle mDrawerToggle;
352 private ActionBar mActionBar;
353
354 private static final int MSG_BUILD_HEADERS = 1;
355 private Handler mHandler = new Handler() {
356 @Override
357 public void handleMessage(Message msg) {
358 switch (msg.what) {
359 case MSG_BUILD_HEADERS: {
360 mHeaders.clear();
361 onBuildHeaders(mHeaders);
362 mHeaderAdapter.notifyDataSetChanged();
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800363 if (mCurrentHeader != null) {
364 Header mappedHeader = findBestMatchingHeader(mCurrentHeader, mHeaders);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800365 if (mappedHeader != null) {
366 setSelectedHeader(mappedHeader);
367 }
368 }
369 } break;
370 }
371 }
372 };
373
374 @Override
375 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
376 // Override the fragment title for Wallpaper settings
377 int titleRes = pref.getTitleRes();
378 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
379 titleRes = R.string.wallpaper_settings_fragment_title;
380 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
381 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
382 if (UserManager.get(this).isLinkedUser()) {
383 titleRes = R.string.profile_info_settings_title;
384 } else {
385 titleRes = R.string.user_info_settings_title;
386 }
387 }
388 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
389 null, 0);
390 return true;
391 }
392
393 @Override
394 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
395 return false;
396 }
397
398 private class DrawerListener implements DrawerLayout.DrawerListener {
399 @Override
400 public void onDrawerOpened(View drawerView) {
401 mDrawerToggle.onDrawerOpened(drawerView);
402 }
403
404 @Override
405 public void onDrawerClosed(View drawerView) {
406 mDrawerToggle.onDrawerClosed(drawerView);
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800407 // Cannot process clicks when the App is finishing
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800408 if (isFinishing() || mSelectedHeader == null) {
409 return;
410 }
411 switchToHeader(mSelectedHeader, false);
412 mSelectedHeader = null;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800413 }
414
415 @Override
416 public void onDrawerSlide(View drawerView, float slideOffset) {
417 mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
418 }
419
420 @Override
421 public void onDrawerStateChanged(int newState) {
422 mDrawerToggle.onDrawerStateChanged(newState);
423 }
424 }
425
426 private class DrawerItemClickListener implements ListView.OnItemClickListener {
427 @Override
428 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
429 mDrawerLayout.closeDrawer(mDrawer);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000430 onListItemClick((ListView)parent, view, position, id);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800431 }
432 }
433
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800434 private Header findBestMatchingHeader(Header current, ArrayList<Header> from) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800435 ArrayList<Header> matches = new ArrayList<Header>();
436 for (int j=0; j<from.size(); j++) {
437 Header oh = from.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800438 if (current == oh || (current.id != HEADER_ID_UNDEFINED && current.id == oh.id)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800439 // Must be this one.
440 matches.clear();
441 matches.add(oh);
442 break;
443 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800444 if (current.fragment != null) {
445 if (current.fragment.equals(oh.fragment)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800446 matches.add(oh);
447 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800448 } else if (current.intent != null) {
449 if (current.intent.equals(oh.intent)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800450 matches.add(oh);
451 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800452 } else if (current.title != null) {
453 if (current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800454 matches.add(oh);
455 }
456 }
457 }
458 final int NM = matches.size();
459 if (NM == 1) {
460 return matches.get(0);
461 } else if (NM > 1) {
462 for (int j=0; j<NM; j++) {
463 Header oh = matches.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800464 if (current.fragmentArguments != null &&
465 current.fragmentArguments.equals(oh.fragmentArguments)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800466 return oh;
467 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800468 if (current.extras != null && current.extras.equals(oh.extras)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800469 return oh;
470 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800471 if (current.title != null && current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800472 return oh;
473 }
474 }
475 }
476 return null;
477 }
478
479 private void invalidateHeaders() {
480 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
481 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
482 }
483 }
484
485 @Override
486 protected void onPostCreate(Bundle savedInstanceState) {
487 super.onPostCreate(savedInstanceState);
488
489 // Sync the toggle state after onRestoreInstanceState has occurred.
490 if (mDrawerToggle != null) {
491 mDrawerToggle.syncState();
492 }
493 }
494
495 @Override
496 public void onConfigurationChanged(Configuration newConfig) {
497 super.onConfigurationChanged(newConfig);
498 if (mDrawerToggle != null) {
499 mDrawerToggle.onConfigurationChanged(newConfig);
500 }
501 }
502
503 @Override
504 public boolean onOptionsItemSelected(MenuItem item) {
505 if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) {
506 return true;
507 }
508 return super.onOptionsItemSelected(item);
509 }
510
511 @Override
512 protected void onCreate(Bundle savedInstanceState) {
513 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
514 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
515 }
516
517 mAuthenticatorHelper = new AuthenticatorHelper();
518 mAuthenticatorHelper.updateAuthDescriptions(this);
519 mAuthenticatorHelper.onAccountsUpdated(this, null);
520
521 DevicePolicyManager dpm =
522 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
523 mHeaderAdapter= new HeaderAdapter(this, getHeaders(), mAuthenticatorHelper, dpm);
524
525 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
526 Context.MODE_PRIVATE);
527
528 getMetaData();
529
530 super.onCreate(savedInstanceState);
531
532 setContentView(R.layout.settings_main);
533
534 getFragmentManager().addOnBackStackChangedListener(this);
535
536 mActionBar = getActionBar();
537 if (mActionBar != null) {
538 mActionBar.setDisplayHomeAsUpEnabled(true);
539 mActionBar.setHomeButtonEnabled(true);
540
541 mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800542
543 mDrawer = (ListView) findViewById(R.id.headers_drawer);
544 mDrawer.setAdapter(mHeaderAdapter);
545 mDrawer.setOnItemClickListener(new DrawerItemClickListener());
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000546 mDrawer.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800547
548 mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
549 R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
550 }
551
552 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
553 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
554
555 if (savedInstanceState != null) {
556 // We are restarting from a previous saved state; used that to
557 // initialize, instead of starting fresh.
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800558
559 ArrayList<TitlePair> titles =
560 savedInstanceState.getParcelableArrayList(SAVE_KEY_TITLES_TAG);
561 if (titles != null) {
562 mTitleStack.addAll(titles);
563 }
564 final int lastTitle = mTitleStack.size() - 1;
565 if (lastTitle >= 0) {
566 final TitlePair last = mTitleStack.get(lastTitle);
567 setTitleFromPair(last);
568 }
569
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800570 ArrayList<Header> headers =
571 savedInstanceState.getParcelableArrayList(SAVE_KEY_HEADERS_TAG);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800572 if (headers != null) {
573 mHeaders.addAll(headers);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800574 int curHeader = savedInstanceState.getInt(SAVE_KEY_CURRENT_HEADER_TAG,
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800575 (int) HEADER_ID_UNDEFINED);
576 if (curHeader >= 0 && curHeader < mHeaders.size()) {
577 setSelectedHeader(mHeaders.get(curHeader));
578 }
579 }
580
581 } else {
582 if (initialFragment != null) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000583 // If we are just showing a fragment, we want to run in
584 // new fragment mode, but don't need to compute and show
585 // the headers.
586 switchToHeader(initialFragment, initialArguments, true);
587
588 final int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
589 if (initialTitle != 0) {
590 setTitle(getText(initialTitle));
591 }
592 } else {
593 // We need to try to build the headers.
594 onBuildHeaders(mHeaders);
595
596 // If there are headers, then at this point we need to show
597 // them and, depending on the screen, we may also show in-line
598 // the currently selected preference fragment.
599 if (mHeaders.size() > 0) {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800600 Header h = onGetInitialHeader();
601 switchToHeader(h, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000602 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800603 }
604 }
605
606 // see if we should show Back/Next buttons
607 Intent intent = getIntent();
608 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
609
610 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
611 if (buttonBar != null) {
612 buttonBar.setVisibility(View.VISIBLE);
613
614 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
615 backButton.setOnClickListener(new OnClickListener() {
616 public void onClick(View v) {
617 setResult(RESULT_CANCELED);
618 finish();
619 }
620 });
621 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
622 skipButton.setOnClickListener(new OnClickListener() {
623 public void onClick(View v) {
624 setResult(RESULT_OK);
625 finish();
626 }
627 });
628 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
629 mNextButton.setOnClickListener(new OnClickListener() {
630 public void onClick(View v) {
631 setResult(RESULT_OK);
632 finish();
633 }
634 });
635
636 // set our various button parameters
637 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
638 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
639 if (TextUtils.isEmpty(buttonText)) {
640 mNextButton.setVisibility(View.GONE);
641 }
642 else {
643 mNextButton.setText(buttonText);
644 }
645 }
646 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
647 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
648 if (TextUtils.isEmpty(buttonText)) {
649 backButton.setVisibility(View.GONE);
650 }
651 else {
652 backButton.setText(buttonText);
653 }
654 }
655 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
656 skipButton.setVisibility(View.VISIBLE);
657 }
658 }
659 }
660
661 if (!onIsHidingHeaders()) {
662 highlightHeader(mTopLevelHeaderId);
663 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800664 }
665
666 @Override
667 public void onBackStackChanged() {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000668 final int count = getFragmentManager().getBackStackEntryCount() + 1;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800669 TitlePair pair = null;
670 int last;
671 while (mTitleStack.size() > count) {
672 last = mTitleStack.size() - 1;
673 pair = mTitleStack.remove(last);
674 }
675 // Check if we go back
676 if (pair != null) {
677 int size = mTitleStack.size();
678 if (size > 0) {
679 last = mTitleStack.size() - 1;
680 pair = mTitleStack.get(last);
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800681 setTitleFromPair(pair);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800682 }
683 }
684 }
685
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800686 private void setTitleFromPair(TitlePair pair) {
687 final CharSequence title;
688 if (pair.first > 0) {
689 title = getText(pair.first);
690 } else {
691 title = pair.second;
692 }
693 setTitle(title);
694 }
695
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800696 /**
697 * Returns the Header list
698 */
699 private List<Header> getHeaders() {
700 return mHeaders;
701 }
702
703 @Override
704 protected void onSaveInstanceState(Bundle outState) {
705 super.onSaveInstanceState(outState);
706
707 if (mHeaders.size() > 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800708 outState.putParcelableArrayList(SAVE_KEY_HEADERS_TAG, mHeaders);
709 if (mCurrentHeader != null) {
710 int index = mHeaders.indexOf(mCurrentHeader);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800711 if (index >= 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800712 outState.putInt(SAVE_KEY_CURRENT_HEADER_TAG, index);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800713 }
714 }
715 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800716
717 if (mTitleStack.size() > 0) {
718 outState.putParcelableList(SAVE_KEY_TITLES_TAG, mTitleStack);
719 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800720 }
721
722 @Override
723 public void onResume() {
724 super.onResume();
725
726 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
727 @Override
728 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
729 invalidateHeaders();
730 }
731 };
732 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
733 mDevelopmentPreferencesListener);
734
735 mHeaderAdapter.resume();
736 invalidateHeaders();
737
738 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800739
740 mDrawerLayout.setDrawerListener(new DrawerListener());
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800741 }
742
743 @Override
744 public void onPause() {
745 super.onPause();
746
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800747 mDrawerLayout.setDrawerListener(null);
748
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800749 unregisterReceiver(mBatteryInfoReceiver);
750
751 mHeaderAdapter.pause();
752
753 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
754 mDevelopmentPreferencesListener);
755
756 mDevelopmentPreferencesListener = null;
757 }
758
759 @Override
760 public void onDestroy() {
761 super.onDestroy();
762 if (mListeningToAccountUpdates) {
763 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
764 }
765 }
766
767 /**
768 * @hide
769 */
770 protected boolean isValidFragment(String fragmentName) {
771 // Almost all fragments are wrapped in this,
772 // except for a few that have their own activities.
773 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
774 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
775 }
776 return false;
777 }
778
779 /**
780 * When in two-pane mode, switch to the fragment pane to show the given
781 * preference fragment.
782 *
783 * @param header The new header to display.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000784 * @param validate true means that the fragment's Header needs to be validated
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800785 */
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000786 private void switchToHeader(Header header, boolean validate) {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800787 if (header == null) {
788 return;
789 }
790 if (header != null && mCurrentHeader != null && header.id == mCurrentHeader.id &&
Fabrice Di Meglio61a77ab2014-02-19 14:07:18 -0800791 header.id != R.id.account_add &&
792 !header.fragment.equals(ManageAccountsSettings.class.getName())) {
793 // This is the header we are currently displaying (except "Add Account" or
794 // "Corporate"/"Google" Account entries that share the same fragment). Just make sure
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000795 // to pop the stack up to its root state.
796 getFragmentManager().popBackStack(BACK_STACK_PREFS,
797 FragmentManager.POP_BACK_STACK_INCLUSIVE);
798 } else {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800799 if (header.fragment != null) {
800 mTitleStack.clear();
801 switchToHeaderInner(header.fragment, header.fragmentArguments, validate);
802 setSelectedHeader(header);
803 final TitlePair pair = new TitlePair(0, getHeaderTitle(header));
804 mTitleStack.add(pair);
805 setTitle(pair.second);
806 } else if (header.intent != null) {
807 setSelectedHeader(header);
808 mTitleStack.clear();
809 startActivity(header.intent);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800810 } else {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800811 throw new IllegalStateException(
812 "Can't switch to header that has no Fragment nor Intent");
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800813 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800814 }
815 }
816
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800817 private CharSequence getHeaderTitle(Header header) {
818 final CharSequence title;
819 if (header.fragment.equals(DashboardSummary.class.getName())) {
820 title = getResources().getString(R.string.settings_label);
821 } else {
822 title = header.getTitle(getResources());
823 }
824 return title;
825 }
826
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800827 private void setSelectedHeader(Header header) {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800828 if (header == null) {
829 mCurrentHeader = null;
830 return;
831 }
832 // Update selected Header into Drawer only if it is not "Add Account"
833 if (header.id == R.id.account_add) {
834 mDrawer.clearChoices();
835 return;
836 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800837 mCurrentHeader = header;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800838 int index = mHeaders.indexOf(header);
839 if (mDrawer != null) {
840 if (index >= 0) {
841 mDrawer.setItemChecked(index, true);
842 } else {
843 mDrawer.clearChoices();
844 }
845 }
846 }
847
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000848 public Header onGetInitialHeader() {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800849 String fragmentClass = getStartingFragmentClass(super.getIntent());
850 if (fragmentClass != null) {
851 Header header = new Header();
852 header.fragment = fragmentClass;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000853 header.title = getTitle();
854 header.fragmentArguments = getIntent().getExtras();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800855 return header;
856 }
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000857
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800858 return mFirstHeader;
859 }
860
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000861 /**
862 * When in two-pane mode, switch the fragment pane to show the given
863 * preference fragment.
864 *
865 * @param fragmentName The name of the fragment to display.
866 * @param args Optional arguments to supply to the fragment.
867 * @param validate true means that the fragment's Header needs to be validated
868 */
869 private void switchToHeader(String fragmentName, Bundle args, boolean validate) {
870 setSelectedHeader(null);
871 switchToHeaderInner(fragmentName, args, validate);
872 }
873
874 private void switchToHeaderInner(String fragmentName, Bundle args, boolean validate) {
875 getFragmentManager().popBackStack(BACK_STACK_PREFS,
876 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800877 if (validate && !isValidFragment(fragmentName)) {
878 throw new IllegalArgumentException("Invalid fragment for this activity: "
879 + fragmentName);
880 }
881 Fragment f = Fragment.instantiate(this, fragmentName, args);
882 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800883 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800884 transaction.replace(R.id.prefs, f);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800885 transaction.commitAllowingStateLoss();
886 }
887
888 @Override
889 public void onNewIntent(Intent intent) {
890 super.onNewIntent(intent);
891
892 // If it is not launched from history, then reset to top-level
893 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
894 if (mDrawer != null) {
895 mDrawer.setSelectionFromTop(0, 0);
896 }
897 }
898 }
899
900 /**
901 * Called to determine whether the header list should be hidden.
902 * The default implementation returns the
903 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
904 * This is set to false, for example, when the activity is being re-launched
905 * to show a particular preference activity.
906 */
907 public boolean onIsHidingHeaders() {
908 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
909 }
910
911 private void highlightHeader(int id) {
912 if (id != 0) {
913 Integer index = mHeaderIndexMap.get(id);
914 if (index != null && mDrawer != null) {
915 mDrawer.setItemChecked(index, true);
916 if (mDrawer.getVisibility() == View.VISIBLE) {
917 mDrawer.smoothScrollToPosition(index);
918 }
919 }
920 }
921 }
922
923 @Override
924 public Intent getIntent() {
925 Intent superIntent = super.getIntent();
926 String startingFragment = getStartingFragmentClass(superIntent);
927 // This is called from super.onCreate, isMultiPane() is not yet reliable
928 // Do not use onIsHidingHeaders either, which relies itself on this method
929 if (startingFragment != null) {
930 Intent modIntent = new Intent(superIntent);
931 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
932 Bundle args = superIntent.getExtras();
933 if (args != null) {
934 args = new Bundle(args);
935 } else {
936 args = new Bundle();
937 }
938 args.putParcelable("intent", superIntent);
939 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
940 return modIntent;
941 }
942 return superIntent;
943 }
944
945 /**
946 * Checks if the component name in the intent is different from the Settings class and
947 * returns the class name to load as a fragment.
948 */
949 private String getStartingFragmentClass(Intent intent) {
950 if (mFragmentClass != null) return mFragmentClass;
951
952 String intentClass = intent.getComponent().getClassName();
953 if (intentClass.equals(getClass().getName())) return null;
954
955 if ("com.android.settings.ManageApplications".equals(intentClass)
956 || "com.android.settings.RunningServices".equals(intentClass)
957 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
958 // Old names of manage apps.
959 intentClass = com.android.settings.applications.ManageApplications.class.getName();
960 }
961
962 return intentClass;
963 }
964
965 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000966 * Start a new fragment containing a preference panel. If the preferences
967 * are being displayed in multi-pane mode, the given fragment class will
968 * be instantiated and placed in the appropriate pane. If running in
969 * single-pane mode, a new activity will be launched in which to show the
970 * fragment.
971 *
972 * @param fragmentClass Full name of the class implementing the fragment.
973 * @param args Any desired arguments to supply to the fragment.
974 * @param titleRes Optional resource identifier of the title of this
975 * fragment.
976 * @param titleText Optional text of the title of this fragment.
977 * @param resultTo Optional fragment that result data should be sent to.
978 * If non-null, resultTo.onActivityResult() will be called when this
979 * preference panel is done. The launched panel must use
980 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
981 * @param resultRequestCode If resultTo is non-null, this is the caller's
982 * request code to be received with the resut.
983 */
984 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
985 CharSequence titleText, Fragment resultTo,
986 int resultRequestCode) {
987 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
988 }
989
990 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800991 * Called by a preference panel fragment to finish itself.
992 *
993 * @param caller The fragment that is asking to be finished.
994 * @param resultCode Optional result code to send back to the original
995 * launching fragment.
996 * @param resultData Optional result data to send back to the original
997 * launching fragment.
998 */
999 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
1000 setResult(resultCode, resultData);
1001 }
1002
1003 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001004 * Start a new fragment.
1005 *
1006 * @param fragment The fragment to start
1007 * @param push If true, the current fragment will be pushed onto the back stack. If false,
1008 * the current fragment will be replaced.
1009 */
1010 public void startPreferenceFragment(Fragment fragment, boolean push) {
1011 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1012 transaction.replace(R.id.prefs, fragment);
1013 if (push) {
1014 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1015 transaction.addToBackStack(BACK_STACK_PREFS);
1016 } else {
1017 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
1018 }
1019 transaction.commitAllowingStateLoss();
1020 }
1021
1022 /**
1023 * Start a new fragment.
1024 *
1025 * @param fragmentName The name of the fragment to display.
1026 * @param args Optional arguments to supply to the fragment.
1027 * @param resultTo Option fragment that should receive the result of
1028 * the activity launch.
1029 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
1030 * report the result.
1031 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
1032 * valid one then it will be used to get the title. Otherwise the titleText
1033 * argument will be used as the title.
1034 * @param titleText string to display for the title of.
1035 */
1036 private void startWithFragment(String fragmentName, Bundle args, Fragment resultTo,
1037 int resultRequestCode, int titleRes, CharSequence titleText) {
1038 Fragment f = Fragment.instantiate(this, fragmentName, args);
1039 if (resultTo != null) {
1040 f.setTargetFragment(resultTo, resultRequestCode);
1041 }
1042 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1043 transaction.replace(R.id.prefs, f);
1044 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1045 transaction.addToBackStack(BACK_STACK_PREFS);
1046 transaction.commitAllowingStateLoss();
1047
1048 final TitlePair pair;
1049 final CharSequence cs;
1050 if (titleRes != 0) {
1051 pair = new TitlePair(titleRes, null);
1052 cs = getText(titleRes);
1053 } else {
1054 pair = new TitlePair(0, titleText);
1055 cs = titleText;
1056 }
1057 setTitle(cs);
1058 mTitleStack.add(pair);
1059 }
1060
1061 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001062 * Called when the activity needs its list of headers build. By
1063 * implementing this and adding at least one item to the list, you
1064 * will cause the activity to run in its modern fragment mode. Note
1065 * that this function may not always be called; for example, if the
1066 * activity has been asked to display a particular fragment without
1067 * the header list, there is no need to build the headers.
1068 *
1069 * <p>Typical implementations will use {@link #loadHeadersFromResource}
1070 * to fill in the list from a resource.
1071 *
1072 * @param headers The list in which to place the headers.
1073 */
1074 private void onBuildHeaders(List<Header> headers) {
1075 loadHeadersFromResource(R.xml.settings_headers, headers);
1076 updateHeaderList(headers);
1077 }
1078
1079 /**
1080 * Parse the given XML file as a header description, adding each
1081 * parsed Header into the target list.
1082 *
1083 * @param resid The XML resource to load and parse.
1084 * @param target The list in which the parsed headers should be placed.
1085 */
1086 private void loadHeadersFromResource(int resid, List<Header> target) {
1087 XmlResourceParser parser = null;
1088 try {
1089 parser = getResources().getXml(resid);
1090 AttributeSet attrs = Xml.asAttributeSet(parser);
1091
1092 int type;
1093 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1094 && type != XmlPullParser.START_TAG) {
1095 // Parse next until start tag is found
1096 }
1097
1098 String nodeName = parser.getName();
1099 if (!"preference-headers".equals(nodeName)) {
1100 throw new RuntimeException(
1101 "XML document must start with <preference-headers> tag; found"
1102 + nodeName + " at " + parser.getPositionDescription());
1103 }
1104
1105 Bundle curBundle = null;
1106
1107 final int outerDepth = parser.getDepth();
1108 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1109 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1110 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1111 continue;
1112 }
1113
1114 nodeName = parser.getName();
1115 if ("header".equals(nodeName)) {
1116 Header header = new Header();
1117
1118 TypedArray sa = obtainStyledAttributes(
1119 attrs, com.android.internal.R.styleable.PreferenceHeader);
1120 header.id = sa.getResourceId(
1121 com.android.internal.R.styleable.PreferenceHeader_id,
1122 (int)HEADER_ID_UNDEFINED);
1123 TypedValue tv = sa.peekValue(
1124 com.android.internal.R.styleable.PreferenceHeader_title);
1125 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1126 if (tv.resourceId != 0) {
1127 header.titleRes = tv.resourceId;
1128 } else {
1129 header.title = tv.string;
1130 }
1131 }
1132 tv = sa.peekValue(
1133 com.android.internal.R.styleable.PreferenceHeader_summary);
1134 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1135 if (tv.resourceId != 0) {
1136 header.summaryRes = tv.resourceId;
1137 } else {
1138 header.summary = tv.string;
1139 }
1140 }
1141 header.iconRes = sa.getResourceId(
1142 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
1143 header.fragment = sa.getString(
1144 com.android.internal.R.styleable.PreferenceHeader_fragment);
1145 sa.recycle();
1146
1147 if (curBundle == null) {
1148 curBundle = new Bundle();
1149 }
1150
1151 final int innerDepth = parser.getDepth();
1152 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1153 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
1154 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1155 continue;
1156 }
1157
1158 String innerNodeName = parser.getName();
1159 if (innerNodeName.equals("extra")) {
1160 getResources().parseBundleExtra("extra", attrs, curBundle);
1161 XmlUtils.skipCurrentTag(parser);
1162
1163 } else if (innerNodeName.equals("intent")) {
1164 header.intent = Intent.parseIntent(getResources(), parser, attrs);
1165
1166 } else {
1167 XmlUtils.skipCurrentTag(parser);
1168 }
1169 }
1170
1171 if (curBundle.size() > 0) {
1172 header.fragmentArguments = curBundle;
1173 curBundle = null;
1174 }
1175
1176 target.add(header);
1177 } else {
1178 XmlUtils.skipCurrentTag(parser);
1179 }
1180 }
1181
1182 } catch (XmlPullParserException e) {
1183 throw new RuntimeException("Error parsing headers", e);
1184 } catch (IOException e) {
1185 throw new RuntimeException("Error parsing headers", e);
1186 } finally {
1187 if (parser != null) parser.close();
1188 }
1189 }
1190
1191 private void updateHeaderList(List<Header> target) {
1192 final boolean showDev = mDevelopmentPreferences.getBoolean(
1193 DevelopmentSettings.PREF_SHOW,
1194 android.os.Build.TYPE.equals("eng"));
1195 int i = 0;
1196
1197 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1198 mHeaderIndexMap.clear();
1199 while (i < target.size()) {
1200 Header header = target.get(i);
1201 // Ids are integers, so downcasting
1202 int id = (int) header.id;
1203 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1204 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1205 } else if (id == R.id.wifi_settings) {
1206 // Remove WiFi Settings if WiFi service is not available.
1207 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1208 target.remove(i);
1209 }
1210 } else if (id == R.id.bluetooth_settings) {
1211 // Remove Bluetooth Settings if Bluetooth service is not available.
1212 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1213 target.remove(i);
1214 }
1215 } else if (id == R.id.data_usage_settings) {
1216 // Remove data usage when kernel module not enabled
1217 final INetworkManagementService netManager = INetworkManagementService.Stub
1218 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1219 try {
1220 if (!netManager.isBandwidthControlEnabled()) {
1221 target.remove(i);
1222 }
1223 } catch (RemoteException e) {
1224 // ignored
1225 }
1226 } else if (id == R.id.battery_settings) {
1227 // Remove battery settings when battery is not available. (e.g. TV)
1228
1229 if (!mBatteryPresent) {
1230 target.remove(i);
1231 }
1232 } else if (id == R.id.account_settings) {
1233 int headerIndex = i + 1;
1234 i = insertAccountsHeaders(target, headerIndex);
1235 } else if (id == R.id.home_settings) {
1236 if (!updateHomeSettingHeaders(header)) {
1237 target.remove(i);
1238 }
1239 } else if (id == R.id.user_settings) {
1240 if (!UserHandle.MU_ENABLED
1241 || !UserManager.supportsMultipleUsers()
1242 || Utils.isMonkeyRunning()) {
1243 target.remove(i);
1244 }
1245 } else if (id == R.id.nfc_payment_settings) {
1246 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1247 target.remove(i);
1248 } else {
1249 // Only show if NFC is on and we have the HCE feature
1250 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1251 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1252 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1253 target.remove(i);
1254 }
1255 }
1256 } else if (id == R.id.development_settings) {
1257 if (!showDev) {
1258 target.remove(i);
1259 }
1260 } else if (id == R.id.account_add) {
1261 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1262 target.remove(i);
1263 }
1264 }
1265
1266 if (i < target.size() && target.get(i) == header
1267 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1268 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1269 target.remove(i);
1270 }
1271
1272 // Increment if the current one wasn't removed by the Utils code.
1273 if (i < target.size() && target.get(i) == header) {
1274 // Hold on to the first header, when we need to reset to the top-level
1275 if (mFirstHeader == null &&
1276 HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
1277 mFirstHeader = header;
1278 }
1279 mHeaderIndexMap.put(id, i);
1280 i++;
1281 }
1282 }
1283 }
1284
1285 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1286 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1287 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1288 for (String accountType : accountTypes) {
1289 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1290 if (label == null) {
1291 continue;
1292 }
1293
1294 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1295 boolean skipToAccount = accounts.length == 1
1296 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1297 Header accHeader = new Header();
1298 accHeader.title = label;
1299 if (accHeader.extras == null) {
1300 accHeader.extras = new Bundle();
1301 }
1302 if (skipToAccount) {
1303 accHeader.fragment = AccountSyncSettings.class.getName();
1304 accHeader.fragmentArguments = new Bundle();
1305 // Need this for the icon
1306 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1307 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1308 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1309 accounts[0]);
1310 } else {
1311 accHeader.fragment = ManageAccountsSettings.class.getName();
1312 accHeader.fragmentArguments = new Bundle();
1313 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1314 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1315 accountType);
1316 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1317 label.toString());
1318 }
1319 accountHeaders.add(accHeader);
1320 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1321 }
1322
1323 // Sort by label
1324 Collections.sort(accountHeaders, new Comparator<Header>() {
1325 @Override
1326 public int compare(Header h1, Header h2) {
1327 return h1.title.toString().compareTo(h2.title.toString());
1328 }
1329 });
1330
1331 for (Header header : accountHeaders) {
1332 target.add(headerIndex++, header);
1333 }
1334 if (!mListeningToAccountUpdates) {
1335 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1336 mListeningToAccountUpdates = true;
1337 }
1338 return headerIndex;
1339 }
1340
1341 private boolean updateHomeSettingHeaders(Header header) {
1342 // Once we decide to show Home settings, keep showing it forever
1343 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1344 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1345 return true;
1346 }
1347
1348 try {
1349 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1350 getPackageManager().getHomeActivities(homeApps);
1351 if (homeApps.size() < 2) {
1352 // When there's only one available home app, omit this settings
1353 // category entirely at the top level UI. If the user just
1354 // uninstalled the penultimate home app candidiate, we also
1355 // now tell them about why they aren't seeing 'Home' in the list.
1356 if (sShowNoHomeNotice) {
1357 sShowNoHomeNotice = false;
1358 NoHomeDialogFragment.show(this);
1359 }
1360 return false;
1361 } else {
1362 // Okay, we're allowing the Home settings category. Tell it, when
1363 // invoked via this front door, that we'll need to be told about the
1364 // case when the user uninstalls all but one home app.
1365 if (header.fragmentArguments == null) {
1366 header.fragmentArguments = new Bundle();
1367 }
1368 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1369 }
1370 } catch (Exception e) {
1371 // Can't look up the home activity; bail on configuring the icon
1372 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1373 }
1374
1375 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1376 return true;
1377 }
1378
1379 private void getMetaData() {
1380 try {
1381 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1382 PackageManager.GET_META_DATA);
1383 if (ai == null || ai.metaData == null) return;
1384 mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
1385 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1386 } catch (NameNotFoundException nnfe) {
1387 // No recovery
1388 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1389 }
1390 }
1391
1392 // give subclasses access to the Next button
1393 public boolean hasNextButton() {
1394 return mNextButton != null;
1395 }
1396
1397 public Button getNextButton() {
1398 return mNextButton;
1399 }
1400
1401 public static class NoHomeDialogFragment extends DialogFragment {
1402 public static void show(Activity parent) {
1403 final NoHomeDialogFragment dialog = new NoHomeDialogFragment();
1404 dialog.show(parent.getFragmentManager(), null);
1405 }
1406
1407 @Override
1408 public Dialog onCreateDialog(Bundle savedInstanceState) {
1409 return new AlertDialog.Builder(getActivity())
1410 .setMessage(R.string.only_one_home_message)
1411 .setPositiveButton(android.R.string.ok, null)
1412 .create();
1413 }
1414 }
1415
1416 private static class HeaderAdapter extends ArrayAdapter<Header> {
1417 static final int HEADER_TYPE_CATEGORY = 0;
1418 static final int HEADER_TYPE_NORMAL = 1;
1419 static final int HEADER_TYPE_SWITCH = 2;
1420 static final int HEADER_TYPE_BUTTON = 3;
1421 private static final int HEADER_TYPE_COUNT = HEADER_TYPE_BUTTON + 1;
1422
1423 private final WifiEnabler mWifiEnabler;
1424 private final BluetoothEnabler mBluetoothEnabler;
1425 private AuthenticatorHelper mAuthHelper;
1426 private DevicePolicyManager mDevicePolicyManager;
1427
1428 private static class HeaderViewHolder {
1429 ImageView mIcon;
1430 TextView mTitle;
1431 TextView mSummary;
1432 Switch mSwitch;
1433 ImageButton mButton;
1434 View mDivider;
1435 }
1436
1437 private LayoutInflater mInflater;
1438
1439 static int getHeaderType(Header header) {
1440 if (header.fragment == null && header.intent == null) {
1441 return HEADER_TYPE_CATEGORY;
1442 } else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings) {
1443 return HEADER_TYPE_SWITCH;
1444 } else if (header.id == R.id.security_settings) {
1445 return HEADER_TYPE_BUTTON;
1446 } else {
1447 return HEADER_TYPE_NORMAL;
1448 }
1449 }
1450
1451 @Override
1452 public int getItemViewType(int position) {
1453 Header header = getItem(position);
1454 return getHeaderType(header);
1455 }
1456
1457 @Override
1458 public boolean areAllItemsEnabled() {
1459 return false; // because of categories
1460 }
1461
1462 @Override
1463 public boolean isEnabled(int position) {
1464 return getItemViewType(position) != HEADER_TYPE_CATEGORY;
1465 }
1466
1467 @Override
1468 public int getViewTypeCount() {
1469 return HEADER_TYPE_COUNT;
1470 }
1471
1472 @Override
1473 public boolean hasStableIds() {
1474 return true;
1475 }
1476
1477 public HeaderAdapter(Context context, List<Header> objects,
1478 AuthenticatorHelper authenticatorHelper, DevicePolicyManager dpm) {
1479 super(context, 0, objects);
1480
1481 mAuthHelper = authenticatorHelper;
1482 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1483
1484 // Temp Switches provided as placeholder until the adapter replaces these with actual
1485 // Switches inflated from their layouts. Must be done before adapter is set in super
1486 mWifiEnabler = new WifiEnabler(context, new Switch(context));
1487 mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
1488 mDevicePolicyManager = dpm;
1489 }
1490
1491 @Override
1492 public View getView(int position, View convertView, ViewGroup parent) {
1493 HeaderViewHolder holder;
1494 Header header = getItem(position);
1495 int headerType = getHeaderType(header);
1496 View view = null;
1497
1498 if (convertView == null) {
1499 holder = new HeaderViewHolder();
1500 switch (headerType) {
1501 case HEADER_TYPE_CATEGORY:
1502 view = new TextView(getContext(), null,
1503 android.R.attr.listSeparatorTextViewStyle);
1504 holder.mTitle = (TextView) view;
1505 break;
1506
1507 case HEADER_TYPE_SWITCH:
1508 view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
1509 false);
1510 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1511 holder.mTitle = (TextView)
1512 view.findViewById(com.android.internal.R.id.title);
1513 holder.mSummary = (TextView)
1514 view.findViewById(com.android.internal.R.id.summary);
1515 holder.mSwitch = (Switch) view.findViewById(R.id.switchWidget);
1516 break;
1517
1518 case HEADER_TYPE_BUTTON:
1519 view = mInflater.inflate(R.layout.preference_header_button_item, parent,
1520 false);
1521 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1522 holder.mTitle = (TextView)
1523 view.findViewById(com.android.internal.R.id.title);
1524 holder.mSummary = (TextView)
1525 view.findViewById(com.android.internal.R.id.summary);
1526 holder.mButton = (ImageButton) view.findViewById(R.id.buttonWidget);
1527 holder.mDivider = view.findViewById(R.id.divider);
1528 break;
1529
1530 case HEADER_TYPE_NORMAL:
1531 view = mInflater.inflate(
1532 R.layout.preference_header_item, parent,
1533 false);
1534 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1535 holder.mTitle = (TextView)
1536 view.findViewById(com.android.internal.R.id.title);
1537 holder.mSummary = (TextView)
1538 view.findViewById(com.android.internal.R.id.summary);
1539 break;
1540 }
1541 view.setTag(holder);
1542 } else {
1543 view = convertView;
1544 holder = (HeaderViewHolder) view.getTag();
1545 }
1546
1547 // All view fields must be updated every time, because the view may be recycled
1548 switch (headerType) {
1549 case HEADER_TYPE_CATEGORY:
1550 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1551 break;
1552
1553 case HEADER_TYPE_SWITCH:
1554 // Would need a different treatment if the main menu had more switches
1555 if (header.id == R.id.wifi_settings) {
1556 mWifiEnabler.setSwitch(holder.mSwitch);
1557 } else {
1558 mBluetoothEnabler.setSwitch(holder.mSwitch);
1559 }
1560 updateCommonHeaderView(header, holder);
1561 break;
1562
1563 case HEADER_TYPE_BUTTON:
1564 if (header.id == R.id.security_settings) {
1565 boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled();
1566 if (hasCert) {
1567 holder.mButton.setVisibility(View.VISIBLE);
1568 holder.mDivider.setVisibility(View.VISIBLE);
1569 boolean isManaged = mDevicePolicyManager.getDeviceOwner() != null;
1570 if (isManaged) {
1571 holder.mButton.setImageResource(R.drawable.ic_settings_about);
1572 } else {
1573 holder.mButton.setImageResource(
1574 android.R.drawable.stat_notify_error);
1575 }
1576 holder.mButton.setOnClickListener(new OnClickListener() {
1577 @Override
1578 public void onClick(View v) {
1579 Intent intent = new Intent(
1580 android.provider.Settings.ACTION_MONITORING_CERT_INFO);
1581 getContext().startActivity(intent);
1582 }
1583 });
1584 } else {
1585 holder.mButton.setVisibility(View.GONE);
1586 holder.mDivider.setVisibility(View.GONE);
1587 }
1588 }
1589 updateCommonHeaderView(header, holder);
1590 break;
1591
1592 case HEADER_TYPE_NORMAL:
1593 updateCommonHeaderView(header, holder);
1594 break;
1595 }
1596
1597 return view;
1598 }
1599
1600 private void updateCommonHeaderView(Header header, HeaderViewHolder holder) {
1601 if (header.extras != null
1602 && header.extras.containsKey(ManageAccountsSettings.KEY_ACCOUNT_TYPE)) {
1603 String accType = header.extras.getString(
1604 ManageAccountsSettings.KEY_ACCOUNT_TYPE);
1605 Drawable icon = mAuthHelper.getDrawableForType(getContext(), accType);
1606 setHeaderIcon(holder, icon);
1607 } else {
1608 holder.mIcon.setImageResource(header.iconRes);
1609 }
1610 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1611 CharSequence summary = header.getSummary(getContext().getResources());
1612 if (!TextUtils.isEmpty(summary)) {
1613 holder.mSummary.setVisibility(View.VISIBLE);
1614 holder.mSummary.setText(summary);
1615 } else {
1616 holder.mSummary.setVisibility(View.GONE);
1617 }
1618 }
1619
1620 private void setHeaderIcon(HeaderViewHolder holder, Drawable icon) {
1621 ViewGroup.LayoutParams lp = holder.mIcon.getLayoutParams();
1622 lp.width = getContext().getResources().getDimensionPixelSize(
1623 R.dimen.header_icon_width);
1624 lp.height = lp.width;
1625 holder.mIcon.setLayoutParams(lp);
1626 holder.mIcon.setImageDrawable(icon);
1627 }
1628
1629 public void resume() {
1630 mWifiEnabler.resume();
1631 mBluetoothEnabler.resume();
1632 }
1633
1634 public void pause() {
1635 mWifiEnabler.pause();
1636 mBluetoothEnabler.pause();
1637 }
1638 }
1639
1640 private void onListItemClick(ListView l, View v, int position, long id) {
1641 if (!isResumed()) {
1642 return;
1643 }
1644 Object item = mHeaderAdapter.getItem(position);
1645 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001646 mSelectedHeader = (Header) item;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001647 }
1648 }
1649
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001650 @Override
1651 public boolean shouldUpRecreateTask(Intent targetIntent) {
1652 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1653 }
1654
1655 @Override
1656 public void onAccountsUpdated(Account[] accounts) {
1657 // TODO: watch for package upgrades to invalidate cache; see 7206643
1658 mAuthenticatorHelper.updateAuthDescriptions(this);
1659 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1660 invalidateHeaders();
1661 }
1662
1663 public static void requestHomeNotice() {
1664 sShowNoHomeNotice = true;
1665 }
1666
1667 /**
1668 * Default value for {@link Header#id Header.id} indicating that no
1669 * identifier value is set. All other values (including those below -1)
1670 * are valid.
1671 */
1672 private static final long HEADER_ID_UNDEFINED = -1;
1673
1674 /**
1675 * Description of a single Header item that the user can select.
1676 */
1677 static final class Header implements Parcelable {
1678 /**
1679 * Identifier for this header, to correlate with a new list when
1680 * it is updated. The default value is
1681 * {@link SettingsActivity#HEADER_ID_UNDEFINED}, meaning no id.
1682 * @attr ref android.R.styleable#PreferenceHeader_id
1683 */
1684 public long id = HEADER_ID_UNDEFINED;
1685
1686 /**
1687 * Resource ID of title of the header that is shown to the user.
1688 * @attr ref android.R.styleable#PreferenceHeader_title
1689 */
1690 public int titleRes;
1691
1692 /**
1693 * Title of the header that is shown to the user.
1694 * @attr ref android.R.styleable#PreferenceHeader_title
1695 */
1696 public CharSequence title;
1697
1698 /**
1699 * Resource ID of optional summary describing what this header controls.
1700 * @attr ref android.R.styleable#PreferenceHeader_summary
1701 */
1702 public int summaryRes;
1703
1704 /**
1705 * Optional summary describing what this header controls.
1706 * @attr ref android.R.styleable#PreferenceHeader_summary
1707 */
1708 public CharSequence summary;
1709
1710 /**
1711 * Optional icon resource to show for this header.
1712 * @attr ref android.R.styleable#PreferenceHeader_icon
1713 */
1714 public int iconRes;
1715
1716 /**
1717 * Full class name of the fragment to display when this header is
1718 * selected.
1719 * @attr ref android.R.styleable#PreferenceHeader_fragment
1720 */
1721 public String fragment;
1722
1723 /**
1724 * Optional arguments to supply to the fragment when it is
1725 * instantiated.
1726 */
1727 public Bundle fragmentArguments;
1728
1729 /**
1730 * Intent to launch when the preference is selected.
1731 */
1732 public Intent intent;
1733
1734 /**
1735 * Optional additional data for use by subclasses of the activity
1736 */
1737 public Bundle extras;
1738
1739 public Header() {
1740 // Empty
1741 }
1742
1743 /**
1744 * Return the currently set title. If {@link #titleRes} is set,
1745 * this resource is loaded from <var>res</var> and returned. Otherwise
1746 * {@link #title} is returned.
1747 */
1748 public CharSequence getTitle(Resources res) {
1749 if (titleRes != 0) {
1750 return res.getText(titleRes);
1751 }
1752 return title;
1753 }
1754
1755 /**
1756 * Return the currently set summary. If {@link #summaryRes} is set,
1757 * this resource is loaded from <var>res</var> and returned. Otherwise
1758 * {@link #summary} is returned.
1759 */
1760 public CharSequence getSummary(Resources res) {
1761 if (summaryRes != 0) {
1762 return res.getText(summaryRes);
1763 }
1764 return summary;
1765 }
1766
1767 @Override
1768 public int describeContents() {
1769 return 0;
1770 }
1771
1772 @Override
1773 public void writeToParcel(Parcel dest, int flags) {
1774 dest.writeLong(id);
1775 dest.writeInt(titleRes);
1776 TextUtils.writeToParcel(title, dest, flags);
1777 dest.writeInt(summaryRes);
1778 TextUtils.writeToParcel(summary, dest, flags);
1779 dest.writeInt(iconRes);
1780 dest.writeString(fragment);
1781 dest.writeBundle(fragmentArguments);
1782 if (intent != null) {
1783 dest.writeInt(1);
1784 intent.writeToParcel(dest, flags);
1785 } else {
1786 dest.writeInt(0);
1787 }
1788 dest.writeBundle(extras);
1789 }
1790
1791 public void readFromParcel(Parcel in) {
1792 id = in.readLong();
1793 titleRes = in.readInt();
1794 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1795 summaryRes = in.readInt();
1796 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1797 iconRes = in.readInt();
1798 fragment = in.readString();
1799 fragmentArguments = in.readBundle();
1800 if (in.readInt() != 0) {
1801 intent = Intent.CREATOR.createFromParcel(in);
1802 }
1803 extras = in.readBundle();
1804 }
1805
1806 Header(Parcel in) {
1807 readFromParcel(in);
1808 }
1809
1810 public static final Creator<Header> CREATOR = new Creator<Header>() {
1811 public Header createFromParcel(Parcel source) {
1812 return new Header(source);
1813 }
1814 public Header[] newArray(int size) {
1815 return new Header[size];
1816 }
1817 };
1818 }
1819}