blob: b4970c19cde46fe536744f88bcc532e4593a1472 [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,
Fabrice Di Meglio2858b792014-02-18 19:35:08 -0800229 R.id.home_settings,
230 R.id.dashboard
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800231 };
232
233 private static final String[] ENTRY_FRAGMENTS = {
234 WirelessSettings.class.getName(),
235 WifiSettings.class.getName(),
236 AdvancedWifiSettings.class.getName(),
237 BluetoothSettings.class.getName(),
238 TetherSettings.class.getName(),
239 WifiP2pSettings.class.getName(),
240 VpnSettings.class.getName(),
241 DateTimeSettings.class.getName(),
242 LocalePicker.class.getName(),
243 InputMethodAndLanguageSettings.class.getName(),
244 SpellCheckersSettings.class.getName(),
245 UserDictionaryList.class.getName(),
246 UserDictionarySettings.class.getName(),
247 SoundSettings.class.getName(),
248 DisplaySettings.class.getName(),
249 DeviceInfoSettings.class.getName(),
250 ManageApplications.class.getName(),
251 ProcessStatsUi.class.getName(),
252 NotificationStation.class.getName(),
253 LocationSettings.class.getName(),
254 SecuritySettings.class.getName(),
255 PrivacySettings.class.getName(),
256 DeviceAdminSettings.class.getName(),
257 AccessibilitySettings.class.getName(),
258 CaptionPropertiesFragment.class.getName(),
259 com.android.settings.accessibility.ToggleInversionPreferenceFragment.class.getName(),
260 com.android.settings.accessibility.ToggleContrastPreferenceFragment.class.getName(),
261 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
262 TextToSpeechSettings.class.getName(),
263 Memory.class.getName(),
264 DevelopmentSettings.class.getName(),
265 UsbSettings.class.getName(),
266 AndroidBeam.class.getName(),
267 WifiDisplaySettings.class.getName(),
268 PowerUsageSummary.class.getName(),
269 AccountSyncSettings.class.getName(),
270 CryptKeeperSettings.class.getName(),
271 DataUsageSummary.class.getName(),
272 DreamSettings.class.getName(),
273 UserSettings.class.getName(),
274 NotificationAccessSettings.class.getName(),
275 ManageAccountsSettings.class.getName(),
276 PrintSettingsFragment.class.getName(),
277 PrintJobSettingsFragment.class.getName(),
278 TrustedCredentialsSettings.class.getName(),
279 PaymentSettings.class.getName(),
280 KeyboardLayoutPickerFragment.class.getName(),
281 ChooseAccountFragment.class.getName(),
Fabrice Di Meglioe6c9a5d2014-02-11 19:03:27 -0800282 DashboardSummary.class.getName()
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800283 };
284
285 private SharedPreferences mDevelopmentPreferences;
286 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
287
288 // TODO: Update Call Settings based on airplane mode state.
289
290 protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
291
292 private AuthenticatorHelper mAuthenticatorHelper;
293 private boolean mListeningToAccountUpdates;
294
295 private Button mNextButton;
296
297 private boolean mBatteryPresent = true;
298 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
299
300 @Override
301 public void onReceive(Context context, Intent intent) {
302 String action = intent.getAction();
303 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
304 boolean batteryPresent = Utils.isBatteryPresent(intent);
305
306 if (mBatteryPresent != batteryPresent) {
307 mBatteryPresent = batteryPresent;
308 invalidateHeaders();
309 }
310 }
311 }
312 };
313
314 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800315 private HeaderAdapter mHeaderAdapter;
316
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800317 static private class TitlePair extends Pair<Integer, CharSequence> implements Parcelable {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800318
319 public TitlePair(Integer first, CharSequence second) {
320 super(first, second);
321 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800322
323 @Override
324 public int describeContents() {
325 return 0;
326 }
327
328 @Override
329 public void writeToParcel(Parcel dest, int flags) {
330 dest.writeInt(first);
331 TextUtils.writeToParcel(second, dest, flags);
332 }
333
334 TitlePair(Parcel in) {
335 super(in.readInt(), TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
336 }
337
338 public static final Creator<TitlePair> CREATOR = new Creator<TitlePair>() {
339 public TitlePair createFromParcel(Parcel source) {
340 return new TitlePair(source);
341 }
342 public TitlePair[] newArray(int size) {
343 return new TitlePair[size];
344 }
345 };
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800346 }
347
348 private final ArrayList<TitlePair> mTitleStack = new ArrayList<TitlePair>();
349
350 private DrawerLayout mDrawerLayout;
351 private ListView mDrawer;
352 private ActionBarDrawerToggle mDrawerToggle;
353 private ActionBar mActionBar;
354
355 private static final int MSG_BUILD_HEADERS = 1;
356 private Handler mHandler = new Handler() {
357 @Override
358 public void handleMessage(Message msg) {
359 switch (msg.what) {
360 case MSG_BUILD_HEADERS: {
361 mHeaders.clear();
362 onBuildHeaders(mHeaders);
363 mHeaderAdapter.notifyDataSetChanged();
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800364 if (mCurrentHeader != null) {
365 Header mappedHeader = findBestMatchingHeader(mCurrentHeader, mHeaders);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800366 if (mappedHeader != null) {
367 setSelectedHeader(mappedHeader);
368 }
369 }
370 } break;
371 }
372 }
373 };
374
375 @Override
376 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
377 // Override the fragment title for Wallpaper settings
378 int titleRes = pref.getTitleRes();
379 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
380 titleRes = R.string.wallpaper_settings_fragment_title;
381 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
382 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
383 if (UserManager.get(this).isLinkedUser()) {
384 titleRes = R.string.profile_info_settings_title;
385 } else {
386 titleRes = R.string.user_info_settings_title;
387 }
388 }
389 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
390 null, 0);
391 return true;
392 }
393
394 @Override
395 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
396 return false;
397 }
398
399 private class DrawerListener implements DrawerLayout.DrawerListener {
400 @Override
401 public void onDrawerOpened(View drawerView) {
402 mDrawerToggle.onDrawerOpened(drawerView);
403 }
404
405 @Override
406 public void onDrawerClosed(View drawerView) {
407 mDrawerToggle.onDrawerClosed(drawerView);
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800408 // Cannot process clicks when the App is finishing
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800409 if (isFinishing() || mSelectedHeader == null) {
410 return;
411 }
412 switchToHeader(mSelectedHeader, false);
413 mSelectedHeader = null;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800414 }
415
416 @Override
417 public void onDrawerSlide(View drawerView, float slideOffset) {
418 mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
419 }
420
421 @Override
422 public void onDrawerStateChanged(int newState) {
423 mDrawerToggle.onDrawerStateChanged(newState);
424 }
425 }
426
427 private class DrawerItemClickListener implements ListView.OnItemClickListener {
428 @Override
429 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
430 mDrawerLayout.closeDrawer(mDrawer);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000431 onListItemClick((ListView)parent, view, position, id);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800432 }
433 }
434
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800435 private Header findBestMatchingHeader(Header current, ArrayList<Header> from) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800436 ArrayList<Header> matches = new ArrayList<Header>();
437 for (int j=0; j<from.size(); j++) {
438 Header oh = from.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800439 if (current == oh || (current.id != HEADER_ID_UNDEFINED && current.id == oh.id)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800440 // Must be this one.
441 matches.clear();
442 matches.add(oh);
443 break;
444 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800445 if (current.fragment != null) {
446 if (current.fragment.equals(oh.fragment)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800447 matches.add(oh);
448 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800449 } else if (current.intent != null) {
450 if (current.intent.equals(oh.intent)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800451 matches.add(oh);
452 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800453 } else if (current.title != null) {
454 if (current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800455 matches.add(oh);
456 }
457 }
458 }
459 final int NM = matches.size();
460 if (NM == 1) {
461 return matches.get(0);
462 } else if (NM > 1) {
463 for (int j=0; j<NM; j++) {
464 Header oh = matches.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800465 if (current.fragmentArguments != null &&
466 current.fragmentArguments.equals(oh.fragmentArguments)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800467 return oh;
468 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800469 if (current.extras != null && current.extras.equals(oh.extras)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800470 return oh;
471 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800472 if (current.title != null && current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800473 return oh;
474 }
475 }
476 }
477 return null;
478 }
479
480 private void invalidateHeaders() {
481 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
482 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
483 }
484 }
485
486 @Override
487 protected void onPostCreate(Bundle savedInstanceState) {
488 super.onPostCreate(savedInstanceState);
489
490 // Sync the toggle state after onRestoreInstanceState has occurred.
491 if (mDrawerToggle != null) {
492 mDrawerToggle.syncState();
493 }
494 }
495
496 @Override
497 public void onConfigurationChanged(Configuration newConfig) {
498 super.onConfigurationChanged(newConfig);
499 if (mDrawerToggle != null) {
500 mDrawerToggle.onConfigurationChanged(newConfig);
501 }
502 }
503
504 @Override
505 public boolean onOptionsItemSelected(MenuItem item) {
506 if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) {
507 return true;
508 }
509 return super.onOptionsItemSelected(item);
510 }
511
512 @Override
513 protected void onCreate(Bundle savedInstanceState) {
514 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
515 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
516 }
517
518 mAuthenticatorHelper = new AuthenticatorHelper();
519 mAuthenticatorHelper.updateAuthDescriptions(this);
520 mAuthenticatorHelper.onAccountsUpdated(this, null);
521
522 DevicePolicyManager dpm =
523 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
524 mHeaderAdapter= new HeaderAdapter(this, getHeaders(), mAuthenticatorHelper, dpm);
525
526 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
527 Context.MODE_PRIVATE);
528
529 getMetaData();
530
531 super.onCreate(savedInstanceState);
532
533 setContentView(R.layout.settings_main);
534
535 getFragmentManager().addOnBackStackChangedListener(this);
536
537 mActionBar = getActionBar();
538 if (mActionBar != null) {
539 mActionBar.setDisplayHomeAsUpEnabled(true);
540 mActionBar.setHomeButtonEnabled(true);
541
542 mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800543
544 mDrawer = (ListView) findViewById(R.id.headers_drawer);
545 mDrawer.setAdapter(mHeaderAdapter);
546 mDrawer.setOnItemClickListener(new DrawerItemClickListener());
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000547 mDrawer.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800548
549 mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
550 R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
551 }
552
553 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
554 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
555
556 if (savedInstanceState != null) {
557 // We are restarting from a previous saved state; used that to
558 // initialize, instead of starting fresh.
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800559
560 ArrayList<TitlePair> titles =
561 savedInstanceState.getParcelableArrayList(SAVE_KEY_TITLES_TAG);
562 if (titles != null) {
563 mTitleStack.addAll(titles);
564 }
565 final int lastTitle = mTitleStack.size() - 1;
566 if (lastTitle >= 0) {
567 final TitlePair last = mTitleStack.get(lastTitle);
568 setTitleFromPair(last);
569 }
570
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800571 ArrayList<Header> headers =
572 savedInstanceState.getParcelableArrayList(SAVE_KEY_HEADERS_TAG);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800573 if (headers != null) {
574 mHeaders.addAll(headers);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800575 int curHeader = savedInstanceState.getInt(SAVE_KEY_CURRENT_HEADER_TAG,
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800576 (int) HEADER_ID_UNDEFINED);
577 if (curHeader >= 0 && curHeader < mHeaders.size()) {
578 setSelectedHeader(mHeaders.get(curHeader));
579 }
580 }
581
582 } else {
583 if (initialFragment != null) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000584 // If we are just showing a fragment, we want to run in
585 // new fragment mode, but don't need to compute and show
586 // the headers.
587 switchToHeader(initialFragment, initialArguments, true);
588
589 final int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
590 if (initialTitle != 0) {
591 setTitle(getText(initialTitle));
592 }
593 } else {
594 // We need to try to build the headers.
595 onBuildHeaders(mHeaders);
596
597 // If there are headers, then at this point we need to show
598 // them and, depending on the screen, we may also show in-line
599 // the currently selected preference fragment.
600 if (mHeaders.size() > 0) {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800601 Header h = onGetInitialHeader();
602 switchToHeader(h, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000603 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800604 }
605 }
606
607 // see if we should show Back/Next buttons
608 Intent intent = getIntent();
609 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
610
611 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
612 if (buttonBar != null) {
613 buttonBar.setVisibility(View.VISIBLE);
614
615 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
616 backButton.setOnClickListener(new OnClickListener() {
617 public void onClick(View v) {
618 setResult(RESULT_CANCELED);
619 finish();
620 }
621 });
622 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
623 skipButton.setOnClickListener(new OnClickListener() {
624 public void onClick(View v) {
625 setResult(RESULT_OK);
626 finish();
627 }
628 });
629 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
630 mNextButton.setOnClickListener(new OnClickListener() {
631 public void onClick(View v) {
632 setResult(RESULT_OK);
633 finish();
634 }
635 });
636
637 // set our various button parameters
638 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
639 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
640 if (TextUtils.isEmpty(buttonText)) {
641 mNextButton.setVisibility(View.GONE);
642 }
643 else {
644 mNextButton.setText(buttonText);
645 }
646 }
647 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
648 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
649 if (TextUtils.isEmpty(buttonText)) {
650 backButton.setVisibility(View.GONE);
651 }
652 else {
653 backButton.setText(buttonText);
654 }
655 }
656 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
657 skipButton.setVisibility(View.VISIBLE);
658 }
659 }
660 }
661
662 if (!onIsHidingHeaders()) {
663 highlightHeader(mTopLevelHeaderId);
664 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800665 }
666
667 @Override
668 public void onBackStackChanged() {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000669 final int count = getFragmentManager().getBackStackEntryCount() + 1;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800670 TitlePair pair = null;
671 int last;
672 while (mTitleStack.size() > count) {
673 last = mTitleStack.size() - 1;
674 pair = mTitleStack.remove(last);
675 }
676 // Check if we go back
677 if (pair != null) {
678 int size = mTitleStack.size();
679 if (size > 0) {
680 last = mTitleStack.size() - 1;
681 pair = mTitleStack.get(last);
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800682 setTitleFromPair(pair);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800683 }
684 }
685 }
686
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800687 private void setTitleFromPair(TitlePair pair) {
688 final CharSequence title;
689 if (pair.first > 0) {
690 title = getText(pair.first);
691 } else {
692 title = pair.second;
693 }
694 setTitle(title);
695 }
696
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800697 /**
698 * Returns the Header list
699 */
700 private List<Header> getHeaders() {
701 return mHeaders;
702 }
703
704 @Override
705 protected void onSaveInstanceState(Bundle outState) {
706 super.onSaveInstanceState(outState);
707
708 if (mHeaders.size() > 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800709 outState.putParcelableArrayList(SAVE_KEY_HEADERS_TAG, mHeaders);
710 if (mCurrentHeader != null) {
711 int index = mHeaders.indexOf(mCurrentHeader);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800712 if (index >= 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800713 outState.putInt(SAVE_KEY_CURRENT_HEADER_TAG, index);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800714 }
715 }
716 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800717
718 if (mTitleStack.size() > 0) {
719 outState.putParcelableList(SAVE_KEY_TITLES_TAG, mTitleStack);
720 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800721 }
722
723 @Override
724 public void onResume() {
725 super.onResume();
726
727 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
728 @Override
729 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
730 invalidateHeaders();
731 }
732 };
733 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
734 mDevelopmentPreferencesListener);
735
736 mHeaderAdapter.resume();
737 invalidateHeaders();
738
739 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800740
741 mDrawerLayout.setDrawerListener(new DrawerListener());
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800742 }
743
744 @Override
745 public void onPause() {
746 super.onPause();
747
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800748 mDrawerLayout.setDrawerListener(null);
749
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800750 unregisterReceiver(mBatteryInfoReceiver);
751
752 mHeaderAdapter.pause();
753
754 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
755 mDevelopmentPreferencesListener);
756
757 mDevelopmentPreferencesListener = null;
758 }
759
760 @Override
761 public void onDestroy() {
762 super.onDestroy();
763 if (mListeningToAccountUpdates) {
764 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
765 }
766 }
767
768 /**
769 * @hide
770 */
771 protected boolean isValidFragment(String fragmentName) {
772 // Almost all fragments are wrapped in this,
773 // except for a few that have their own activities.
774 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
775 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
776 }
777 return false;
778 }
779
780 /**
781 * When in two-pane mode, switch to the fragment pane to show the given
782 * preference fragment.
783 *
784 * @param header The new header to display.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000785 * @param validate true means that the fragment's Header needs to be validated
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800786 */
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000787 private void switchToHeader(Header header, boolean validate) {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800788 if (header == null) {
789 return;
790 }
791 if (header != null && mCurrentHeader != null && header.id == mCurrentHeader.id &&
792 header.id != R.id.account_add) {
793 // This is the header we are currently displaying (except "Add Account"). Just make sure
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000794 // to pop the stack up to its root state.
795 getFragmentManager().popBackStack(BACK_STACK_PREFS,
796 FragmentManager.POP_BACK_STACK_INCLUSIVE);
797 } else {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800798 if (header.fragment != null) {
799 mTitleStack.clear();
800 switchToHeaderInner(header.fragment, header.fragmentArguments, validate);
801 setSelectedHeader(header);
802 final TitlePair pair = new TitlePair(0, getHeaderTitle(header));
803 mTitleStack.add(pair);
804 setTitle(pair.second);
805 } else if (header.intent != null) {
806 setSelectedHeader(header);
807 mTitleStack.clear();
808 startActivity(header.intent);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800809 } else {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800810 throw new IllegalStateException(
811 "Can't switch to header that has no Fragment nor Intent");
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800812 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800813 }
814 }
815
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800816 private CharSequence getHeaderTitle(Header header) {
817 final CharSequence title;
818 if (header.fragment.equals(DashboardSummary.class.getName())) {
819 title = getResources().getString(R.string.settings_label);
820 } else {
821 title = header.getTitle(getResources());
822 }
823 return title;
824 }
825
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800826 private void setSelectedHeader(Header header) {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800827 if (header == null) {
828 mCurrentHeader = null;
829 return;
830 }
831 // Update selected Header into Drawer only if it is not "Add Account"
832 if (header.id == R.id.account_add) {
833 mDrawer.clearChoices();
834 return;
835 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800836 mCurrentHeader = header;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800837 int index = mHeaders.indexOf(header);
838 if (mDrawer != null) {
839 if (index >= 0) {
840 mDrawer.setItemChecked(index, true);
841 } else {
842 mDrawer.clearChoices();
843 }
844 }
845 }
846
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000847 public Header onGetInitialHeader() {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800848 String fragmentClass = getStartingFragmentClass(super.getIntent());
849 if (fragmentClass != null) {
850 Header header = new Header();
851 header.fragment = fragmentClass;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000852 header.title = getTitle();
853 header.fragmentArguments = getIntent().getExtras();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800854 return header;
855 }
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000856
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800857 return mFirstHeader;
858 }
859
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000860 /**
861 * When in two-pane mode, switch the fragment pane to show the given
862 * preference fragment.
863 *
864 * @param fragmentName The name of the fragment to display.
865 * @param args Optional arguments to supply to the fragment.
866 * @param validate true means that the fragment's Header needs to be validated
867 */
868 private void switchToHeader(String fragmentName, Bundle args, boolean validate) {
869 setSelectedHeader(null);
870 switchToHeaderInner(fragmentName, args, validate);
871 }
872
873 private void switchToHeaderInner(String fragmentName, Bundle args, boolean validate) {
874 getFragmentManager().popBackStack(BACK_STACK_PREFS,
875 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800876 if (validate && !isValidFragment(fragmentName)) {
877 throw new IllegalArgumentException("Invalid fragment for this activity: "
878 + fragmentName);
879 }
880 Fragment f = Fragment.instantiate(this, fragmentName, args);
881 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800882 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800883 transaction.replace(R.id.prefs, f);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800884 transaction.commitAllowingStateLoss();
885 }
886
887 @Override
888 public void onNewIntent(Intent intent) {
889 super.onNewIntent(intent);
890
891 // If it is not launched from history, then reset to top-level
892 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
893 if (mDrawer != null) {
894 mDrawer.setSelectionFromTop(0, 0);
895 }
896 }
897 }
898
899 /**
900 * Called to determine whether the header list should be hidden.
901 * The default implementation returns the
902 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
903 * This is set to false, for example, when the activity is being re-launched
904 * to show a particular preference activity.
905 */
906 public boolean onIsHidingHeaders() {
907 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
908 }
909
910 private void highlightHeader(int id) {
911 if (id != 0) {
912 Integer index = mHeaderIndexMap.get(id);
913 if (index != null && mDrawer != null) {
914 mDrawer.setItemChecked(index, true);
915 if (mDrawer.getVisibility() == View.VISIBLE) {
916 mDrawer.smoothScrollToPosition(index);
917 }
918 }
919 }
920 }
921
922 @Override
923 public Intent getIntent() {
924 Intent superIntent = super.getIntent();
925 String startingFragment = getStartingFragmentClass(superIntent);
926 // This is called from super.onCreate, isMultiPane() is not yet reliable
927 // Do not use onIsHidingHeaders either, which relies itself on this method
928 if (startingFragment != null) {
929 Intent modIntent = new Intent(superIntent);
930 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
931 Bundle args = superIntent.getExtras();
932 if (args != null) {
933 args = new Bundle(args);
934 } else {
935 args = new Bundle();
936 }
937 args.putParcelable("intent", superIntent);
938 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
939 return modIntent;
940 }
941 return superIntent;
942 }
943
944 /**
945 * Checks if the component name in the intent is different from the Settings class and
946 * returns the class name to load as a fragment.
947 */
948 private String getStartingFragmentClass(Intent intent) {
949 if (mFragmentClass != null) return mFragmentClass;
950
951 String intentClass = intent.getComponent().getClassName();
952 if (intentClass.equals(getClass().getName())) return null;
953
954 if ("com.android.settings.ManageApplications".equals(intentClass)
955 || "com.android.settings.RunningServices".equals(intentClass)
956 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
957 // Old names of manage apps.
958 intentClass = com.android.settings.applications.ManageApplications.class.getName();
959 }
960
961 return intentClass;
962 }
963
964 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000965 * Start a new fragment containing a preference panel. If the preferences
966 * are being displayed in multi-pane mode, the given fragment class will
967 * be instantiated and placed in the appropriate pane. If running in
968 * single-pane mode, a new activity will be launched in which to show the
969 * fragment.
970 *
971 * @param fragmentClass Full name of the class implementing the fragment.
972 * @param args Any desired arguments to supply to the fragment.
973 * @param titleRes Optional resource identifier of the title of this
974 * fragment.
975 * @param titleText Optional text of the title of this fragment.
976 * @param resultTo Optional fragment that result data should be sent to.
977 * If non-null, resultTo.onActivityResult() will be called when this
978 * preference panel is done. The launched panel must use
979 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
980 * @param resultRequestCode If resultTo is non-null, this is the caller's
981 * request code to be received with the resut.
982 */
983 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
984 CharSequence titleText, Fragment resultTo,
985 int resultRequestCode) {
986 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
987 }
988
989 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800990 * Called by a preference panel fragment to finish itself.
991 *
992 * @param caller The fragment that is asking to be finished.
993 * @param resultCode Optional result code to send back to the original
994 * launching fragment.
995 * @param resultData Optional result data to send back to the original
996 * launching fragment.
997 */
998 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
999 setResult(resultCode, resultData);
1000 }
1001
1002 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001003 * Start a new fragment.
1004 *
1005 * @param fragment The fragment to start
1006 * @param push If true, the current fragment will be pushed onto the back stack. If false,
1007 * the current fragment will be replaced.
1008 */
1009 public void startPreferenceFragment(Fragment fragment, boolean push) {
1010 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1011 transaction.replace(R.id.prefs, fragment);
1012 if (push) {
1013 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1014 transaction.addToBackStack(BACK_STACK_PREFS);
1015 } else {
1016 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
1017 }
1018 transaction.commitAllowingStateLoss();
1019 }
1020
1021 /**
1022 * Start a new fragment.
1023 *
1024 * @param fragmentName The name of the fragment to display.
1025 * @param args Optional arguments to supply to the fragment.
1026 * @param resultTo Option fragment that should receive the result of
1027 * the activity launch.
1028 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
1029 * report the result.
1030 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
1031 * valid one then it will be used to get the title. Otherwise the titleText
1032 * argument will be used as the title.
1033 * @param titleText string to display for the title of.
1034 */
1035 private void startWithFragment(String fragmentName, Bundle args, Fragment resultTo,
1036 int resultRequestCode, int titleRes, CharSequence titleText) {
1037 Fragment f = Fragment.instantiate(this, fragmentName, args);
1038 if (resultTo != null) {
1039 f.setTargetFragment(resultTo, resultRequestCode);
1040 }
1041 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1042 transaction.replace(R.id.prefs, f);
1043 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1044 transaction.addToBackStack(BACK_STACK_PREFS);
1045 transaction.commitAllowingStateLoss();
1046
1047 final TitlePair pair;
1048 final CharSequence cs;
1049 if (titleRes != 0) {
1050 pair = new TitlePair(titleRes, null);
1051 cs = getText(titleRes);
1052 } else {
1053 pair = new TitlePair(0, titleText);
1054 cs = titleText;
1055 }
1056 setTitle(cs);
1057 mTitleStack.add(pair);
1058 }
1059
1060 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001061 * Called when the activity needs its list of headers build. By
1062 * implementing this and adding at least one item to the list, you
1063 * will cause the activity to run in its modern fragment mode. Note
1064 * that this function may not always be called; for example, if the
1065 * activity has been asked to display a particular fragment without
1066 * the header list, there is no need to build the headers.
1067 *
1068 * <p>Typical implementations will use {@link #loadHeadersFromResource}
1069 * to fill in the list from a resource.
1070 *
1071 * @param headers The list in which to place the headers.
1072 */
1073 private void onBuildHeaders(List<Header> headers) {
1074 loadHeadersFromResource(R.xml.settings_headers, headers);
1075 updateHeaderList(headers);
1076 }
1077
1078 /**
1079 * Parse the given XML file as a header description, adding each
1080 * parsed Header into the target list.
1081 *
1082 * @param resid The XML resource to load and parse.
1083 * @param target The list in which the parsed headers should be placed.
1084 */
1085 private void loadHeadersFromResource(int resid, List<Header> target) {
1086 XmlResourceParser parser = null;
1087 try {
1088 parser = getResources().getXml(resid);
1089 AttributeSet attrs = Xml.asAttributeSet(parser);
1090
1091 int type;
1092 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1093 && type != XmlPullParser.START_TAG) {
1094 // Parse next until start tag is found
1095 }
1096
1097 String nodeName = parser.getName();
1098 if (!"preference-headers".equals(nodeName)) {
1099 throw new RuntimeException(
1100 "XML document must start with <preference-headers> tag; found"
1101 + nodeName + " at " + parser.getPositionDescription());
1102 }
1103
1104 Bundle curBundle = null;
1105
1106 final int outerDepth = parser.getDepth();
1107 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1108 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1109 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1110 continue;
1111 }
1112
1113 nodeName = parser.getName();
1114 if ("header".equals(nodeName)) {
1115 Header header = new Header();
1116
1117 TypedArray sa = obtainStyledAttributes(
1118 attrs, com.android.internal.R.styleable.PreferenceHeader);
1119 header.id = sa.getResourceId(
1120 com.android.internal.R.styleable.PreferenceHeader_id,
1121 (int)HEADER_ID_UNDEFINED);
1122 TypedValue tv = sa.peekValue(
1123 com.android.internal.R.styleable.PreferenceHeader_title);
1124 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1125 if (tv.resourceId != 0) {
1126 header.titleRes = tv.resourceId;
1127 } else {
1128 header.title = tv.string;
1129 }
1130 }
1131 tv = sa.peekValue(
1132 com.android.internal.R.styleable.PreferenceHeader_summary);
1133 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1134 if (tv.resourceId != 0) {
1135 header.summaryRes = tv.resourceId;
1136 } else {
1137 header.summary = tv.string;
1138 }
1139 }
1140 header.iconRes = sa.getResourceId(
1141 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
1142 header.fragment = sa.getString(
1143 com.android.internal.R.styleable.PreferenceHeader_fragment);
1144 sa.recycle();
1145
1146 if (curBundle == null) {
1147 curBundle = new Bundle();
1148 }
1149
1150 final int innerDepth = parser.getDepth();
1151 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1152 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
1153 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1154 continue;
1155 }
1156
1157 String innerNodeName = parser.getName();
1158 if (innerNodeName.equals("extra")) {
1159 getResources().parseBundleExtra("extra", attrs, curBundle);
1160 XmlUtils.skipCurrentTag(parser);
1161
1162 } else if (innerNodeName.equals("intent")) {
1163 header.intent = Intent.parseIntent(getResources(), parser, attrs);
1164
1165 } else {
1166 XmlUtils.skipCurrentTag(parser);
1167 }
1168 }
1169
1170 if (curBundle.size() > 0) {
1171 header.fragmentArguments = curBundle;
1172 curBundle = null;
1173 }
1174
1175 target.add(header);
1176 } else {
1177 XmlUtils.skipCurrentTag(parser);
1178 }
1179 }
1180
1181 } catch (XmlPullParserException e) {
1182 throw new RuntimeException("Error parsing headers", e);
1183 } catch (IOException e) {
1184 throw new RuntimeException("Error parsing headers", e);
1185 } finally {
1186 if (parser != null) parser.close();
1187 }
1188 }
1189
1190 private void updateHeaderList(List<Header> target) {
1191 final boolean showDev = mDevelopmentPreferences.getBoolean(
1192 DevelopmentSettings.PREF_SHOW,
1193 android.os.Build.TYPE.equals("eng"));
1194 int i = 0;
1195
1196 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1197 mHeaderIndexMap.clear();
1198 while (i < target.size()) {
1199 Header header = target.get(i);
1200 // Ids are integers, so downcasting
1201 int id = (int) header.id;
1202 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1203 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1204 } else if (id == R.id.wifi_settings) {
1205 // Remove WiFi Settings if WiFi service is not available.
1206 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1207 target.remove(i);
1208 }
1209 } else if (id == R.id.bluetooth_settings) {
1210 // Remove Bluetooth Settings if Bluetooth service is not available.
1211 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1212 target.remove(i);
1213 }
1214 } else if (id == R.id.data_usage_settings) {
1215 // Remove data usage when kernel module not enabled
1216 final INetworkManagementService netManager = INetworkManagementService.Stub
1217 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1218 try {
1219 if (!netManager.isBandwidthControlEnabled()) {
1220 target.remove(i);
1221 }
1222 } catch (RemoteException e) {
1223 // ignored
1224 }
1225 } else if (id == R.id.battery_settings) {
1226 // Remove battery settings when battery is not available. (e.g. TV)
1227
1228 if (!mBatteryPresent) {
1229 target.remove(i);
1230 }
1231 } else if (id == R.id.account_settings) {
1232 int headerIndex = i + 1;
1233 i = insertAccountsHeaders(target, headerIndex);
1234 } else if (id == R.id.home_settings) {
1235 if (!updateHomeSettingHeaders(header)) {
1236 target.remove(i);
1237 }
1238 } else if (id == R.id.user_settings) {
1239 if (!UserHandle.MU_ENABLED
1240 || !UserManager.supportsMultipleUsers()
1241 || Utils.isMonkeyRunning()) {
1242 target.remove(i);
1243 }
1244 } else if (id == R.id.nfc_payment_settings) {
1245 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1246 target.remove(i);
1247 } else {
1248 // Only show if NFC is on and we have the HCE feature
1249 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1250 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1251 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1252 target.remove(i);
1253 }
1254 }
1255 } else if (id == R.id.development_settings) {
1256 if (!showDev) {
1257 target.remove(i);
1258 }
1259 } else if (id == R.id.account_add) {
1260 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1261 target.remove(i);
1262 }
1263 }
1264
1265 if (i < target.size() && target.get(i) == header
1266 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1267 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1268 target.remove(i);
1269 }
1270
1271 // Increment if the current one wasn't removed by the Utils code.
1272 if (i < target.size() && target.get(i) == header) {
1273 // Hold on to the first header, when we need to reset to the top-level
1274 if (mFirstHeader == null &&
1275 HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
1276 mFirstHeader = header;
1277 }
1278 mHeaderIndexMap.put(id, i);
1279 i++;
1280 }
1281 }
1282 }
1283
1284 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1285 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1286 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1287 for (String accountType : accountTypes) {
1288 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1289 if (label == null) {
1290 continue;
1291 }
1292
1293 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1294 boolean skipToAccount = accounts.length == 1
1295 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1296 Header accHeader = new Header();
1297 accHeader.title = label;
1298 if (accHeader.extras == null) {
1299 accHeader.extras = new Bundle();
1300 }
1301 if (skipToAccount) {
1302 accHeader.fragment = AccountSyncSettings.class.getName();
1303 accHeader.fragmentArguments = new Bundle();
1304 // Need this for the icon
1305 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1306 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1307 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1308 accounts[0]);
1309 } else {
1310 accHeader.fragment = ManageAccountsSettings.class.getName();
1311 accHeader.fragmentArguments = new Bundle();
1312 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1313 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1314 accountType);
1315 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1316 label.toString());
1317 }
1318 accountHeaders.add(accHeader);
1319 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1320 }
1321
1322 // Sort by label
1323 Collections.sort(accountHeaders, new Comparator<Header>() {
1324 @Override
1325 public int compare(Header h1, Header h2) {
1326 return h1.title.toString().compareTo(h2.title.toString());
1327 }
1328 });
1329
1330 for (Header header : accountHeaders) {
1331 target.add(headerIndex++, header);
1332 }
1333 if (!mListeningToAccountUpdates) {
1334 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1335 mListeningToAccountUpdates = true;
1336 }
1337 return headerIndex;
1338 }
1339
1340 private boolean updateHomeSettingHeaders(Header header) {
1341 // Once we decide to show Home settings, keep showing it forever
1342 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1343 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1344 return true;
1345 }
1346
1347 try {
1348 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1349 getPackageManager().getHomeActivities(homeApps);
1350 if (homeApps.size() < 2) {
1351 // When there's only one available home app, omit this settings
1352 // category entirely at the top level UI. If the user just
1353 // uninstalled the penultimate home app candidiate, we also
1354 // now tell them about why they aren't seeing 'Home' in the list.
1355 if (sShowNoHomeNotice) {
1356 sShowNoHomeNotice = false;
1357 NoHomeDialogFragment.show(this);
1358 }
1359 return false;
1360 } else {
1361 // Okay, we're allowing the Home settings category. Tell it, when
1362 // invoked via this front door, that we'll need to be told about the
1363 // case when the user uninstalls all but one home app.
1364 if (header.fragmentArguments == null) {
1365 header.fragmentArguments = new Bundle();
1366 }
1367 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1368 }
1369 } catch (Exception e) {
1370 // Can't look up the home activity; bail on configuring the icon
1371 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1372 }
1373
1374 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1375 return true;
1376 }
1377
1378 private void getMetaData() {
1379 try {
1380 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1381 PackageManager.GET_META_DATA);
1382 if (ai == null || ai.metaData == null) return;
1383 mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
1384 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1385 } catch (NameNotFoundException nnfe) {
1386 // No recovery
1387 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1388 }
1389 }
1390
1391 // give subclasses access to the Next button
1392 public boolean hasNextButton() {
1393 return mNextButton != null;
1394 }
1395
1396 public Button getNextButton() {
1397 return mNextButton;
1398 }
1399
1400 public static class NoHomeDialogFragment extends DialogFragment {
1401 public static void show(Activity parent) {
1402 final NoHomeDialogFragment dialog = new NoHomeDialogFragment();
1403 dialog.show(parent.getFragmentManager(), null);
1404 }
1405
1406 @Override
1407 public Dialog onCreateDialog(Bundle savedInstanceState) {
1408 return new AlertDialog.Builder(getActivity())
1409 .setMessage(R.string.only_one_home_message)
1410 .setPositiveButton(android.R.string.ok, null)
1411 .create();
1412 }
1413 }
1414
1415 private static class HeaderAdapter extends ArrayAdapter<Header> {
1416 static final int HEADER_TYPE_CATEGORY = 0;
1417 static final int HEADER_TYPE_NORMAL = 1;
1418 static final int HEADER_TYPE_SWITCH = 2;
1419 static final int HEADER_TYPE_BUTTON = 3;
1420 private static final int HEADER_TYPE_COUNT = HEADER_TYPE_BUTTON + 1;
1421
1422 private final WifiEnabler mWifiEnabler;
1423 private final BluetoothEnabler mBluetoothEnabler;
1424 private AuthenticatorHelper mAuthHelper;
1425 private DevicePolicyManager mDevicePolicyManager;
1426
1427 private static class HeaderViewHolder {
1428 ImageView mIcon;
1429 TextView mTitle;
1430 TextView mSummary;
1431 Switch mSwitch;
1432 ImageButton mButton;
1433 View mDivider;
1434 }
1435
1436 private LayoutInflater mInflater;
1437
1438 static int getHeaderType(Header header) {
1439 if (header.fragment == null && header.intent == null) {
1440 return HEADER_TYPE_CATEGORY;
1441 } else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings) {
1442 return HEADER_TYPE_SWITCH;
1443 } else if (header.id == R.id.security_settings) {
1444 return HEADER_TYPE_BUTTON;
1445 } else {
1446 return HEADER_TYPE_NORMAL;
1447 }
1448 }
1449
1450 @Override
1451 public int getItemViewType(int position) {
1452 Header header = getItem(position);
1453 return getHeaderType(header);
1454 }
1455
1456 @Override
1457 public boolean areAllItemsEnabled() {
1458 return false; // because of categories
1459 }
1460
1461 @Override
1462 public boolean isEnabled(int position) {
1463 return getItemViewType(position) != HEADER_TYPE_CATEGORY;
1464 }
1465
1466 @Override
1467 public int getViewTypeCount() {
1468 return HEADER_TYPE_COUNT;
1469 }
1470
1471 @Override
1472 public boolean hasStableIds() {
1473 return true;
1474 }
1475
1476 public HeaderAdapter(Context context, List<Header> objects,
1477 AuthenticatorHelper authenticatorHelper, DevicePolicyManager dpm) {
1478 super(context, 0, objects);
1479
1480 mAuthHelper = authenticatorHelper;
1481 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1482
1483 // Temp Switches provided as placeholder until the adapter replaces these with actual
1484 // Switches inflated from their layouts. Must be done before adapter is set in super
1485 mWifiEnabler = new WifiEnabler(context, new Switch(context));
1486 mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
1487 mDevicePolicyManager = dpm;
1488 }
1489
1490 @Override
1491 public View getView(int position, View convertView, ViewGroup parent) {
1492 HeaderViewHolder holder;
1493 Header header = getItem(position);
1494 int headerType = getHeaderType(header);
1495 View view = null;
1496
1497 if (convertView == null) {
1498 holder = new HeaderViewHolder();
1499 switch (headerType) {
1500 case HEADER_TYPE_CATEGORY:
1501 view = new TextView(getContext(), null,
1502 android.R.attr.listSeparatorTextViewStyle);
1503 holder.mTitle = (TextView) view;
1504 break;
1505
1506 case HEADER_TYPE_SWITCH:
1507 view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
1508 false);
1509 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1510 holder.mTitle = (TextView)
1511 view.findViewById(com.android.internal.R.id.title);
1512 holder.mSummary = (TextView)
1513 view.findViewById(com.android.internal.R.id.summary);
1514 holder.mSwitch = (Switch) view.findViewById(R.id.switchWidget);
1515 break;
1516
1517 case HEADER_TYPE_BUTTON:
1518 view = mInflater.inflate(R.layout.preference_header_button_item, parent,
1519 false);
1520 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1521 holder.mTitle = (TextView)
1522 view.findViewById(com.android.internal.R.id.title);
1523 holder.mSummary = (TextView)
1524 view.findViewById(com.android.internal.R.id.summary);
1525 holder.mButton = (ImageButton) view.findViewById(R.id.buttonWidget);
1526 holder.mDivider = view.findViewById(R.id.divider);
1527 break;
1528
1529 case HEADER_TYPE_NORMAL:
1530 view = mInflater.inflate(
1531 R.layout.preference_header_item, parent,
1532 false);
1533 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1534 holder.mTitle = (TextView)
1535 view.findViewById(com.android.internal.R.id.title);
1536 holder.mSummary = (TextView)
1537 view.findViewById(com.android.internal.R.id.summary);
1538 break;
1539 }
1540 view.setTag(holder);
1541 } else {
1542 view = convertView;
1543 holder = (HeaderViewHolder) view.getTag();
1544 }
1545
1546 // All view fields must be updated every time, because the view may be recycled
1547 switch (headerType) {
1548 case HEADER_TYPE_CATEGORY:
1549 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1550 break;
1551
1552 case HEADER_TYPE_SWITCH:
1553 // Would need a different treatment if the main menu had more switches
1554 if (header.id == R.id.wifi_settings) {
1555 mWifiEnabler.setSwitch(holder.mSwitch);
1556 } else {
1557 mBluetoothEnabler.setSwitch(holder.mSwitch);
1558 }
1559 updateCommonHeaderView(header, holder);
1560 break;
1561
1562 case HEADER_TYPE_BUTTON:
1563 if (header.id == R.id.security_settings) {
1564 boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled();
1565 if (hasCert) {
1566 holder.mButton.setVisibility(View.VISIBLE);
1567 holder.mDivider.setVisibility(View.VISIBLE);
1568 boolean isManaged = mDevicePolicyManager.getDeviceOwner() != null;
1569 if (isManaged) {
1570 holder.mButton.setImageResource(R.drawable.ic_settings_about);
1571 } else {
1572 holder.mButton.setImageResource(
1573 android.R.drawable.stat_notify_error);
1574 }
1575 holder.mButton.setOnClickListener(new OnClickListener() {
1576 @Override
1577 public void onClick(View v) {
1578 Intent intent = new Intent(
1579 android.provider.Settings.ACTION_MONITORING_CERT_INFO);
1580 getContext().startActivity(intent);
1581 }
1582 });
1583 } else {
1584 holder.mButton.setVisibility(View.GONE);
1585 holder.mDivider.setVisibility(View.GONE);
1586 }
1587 }
1588 updateCommonHeaderView(header, holder);
1589 break;
1590
1591 case HEADER_TYPE_NORMAL:
1592 updateCommonHeaderView(header, holder);
1593 break;
1594 }
1595
1596 return view;
1597 }
1598
1599 private void updateCommonHeaderView(Header header, HeaderViewHolder holder) {
1600 if (header.extras != null
1601 && header.extras.containsKey(ManageAccountsSettings.KEY_ACCOUNT_TYPE)) {
1602 String accType = header.extras.getString(
1603 ManageAccountsSettings.KEY_ACCOUNT_TYPE);
1604 Drawable icon = mAuthHelper.getDrawableForType(getContext(), accType);
1605 setHeaderIcon(holder, icon);
1606 } else {
1607 holder.mIcon.setImageResource(header.iconRes);
1608 }
1609 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1610 CharSequence summary = header.getSummary(getContext().getResources());
1611 if (!TextUtils.isEmpty(summary)) {
1612 holder.mSummary.setVisibility(View.VISIBLE);
1613 holder.mSummary.setText(summary);
1614 } else {
1615 holder.mSummary.setVisibility(View.GONE);
1616 }
1617 }
1618
1619 private void setHeaderIcon(HeaderViewHolder holder, Drawable icon) {
1620 ViewGroup.LayoutParams lp = holder.mIcon.getLayoutParams();
1621 lp.width = getContext().getResources().getDimensionPixelSize(
1622 R.dimen.header_icon_width);
1623 lp.height = lp.width;
1624 holder.mIcon.setLayoutParams(lp);
1625 holder.mIcon.setImageDrawable(icon);
1626 }
1627
1628 public void resume() {
1629 mWifiEnabler.resume();
1630 mBluetoothEnabler.resume();
1631 }
1632
1633 public void pause() {
1634 mWifiEnabler.pause();
1635 mBluetoothEnabler.pause();
1636 }
1637 }
1638
1639 private void onListItemClick(ListView l, View v, int position, long id) {
1640 if (!isResumed()) {
1641 return;
1642 }
1643 Object item = mHeaderAdapter.getItem(position);
1644 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001645 mSelectedHeader = (Header) item;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001646 }
1647 }
1648
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001649 @Override
1650 public boolean shouldUpRecreateTask(Intent targetIntent) {
1651 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1652 }
1653
1654 @Override
1655 public void onAccountsUpdated(Account[] accounts) {
1656 // TODO: watch for package upgrades to invalidate cache; see 7206643
1657 mAuthenticatorHelper.updateAuthDescriptions(this);
1658 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1659 invalidateHeaders();
1660 }
1661
1662 public static void requestHomeNotice() {
1663 sShowNoHomeNotice = true;
1664 }
1665
1666 /**
1667 * Default value for {@link Header#id Header.id} indicating that no
1668 * identifier value is set. All other values (including those below -1)
1669 * are valid.
1670 */
1671 private static final long HEADER_ID_UNDEFINED = -1;
1672
1673 /**
1674 * Description of a single Header item that the user can select.
1675 */
1676 static final class Header implements Parcelable {
1677 /**
1678 * Identifier for this header, to correlate with a new list when
1679 * it is updated. The default value is
1680 * {@link SettingsActivity#HEADER_ID_UNDEFINED}, meaning no id.
1681 * @attr ref android.R.styleable#PreferenceHeader_id
1682 */
1683 public long id = HEADER_ID_UNDEFINED;
1684
1685 /**
1686 * Resource ID of title of the header that is shown to the user.
1687 * @attr ref android.R.styleable#PreferenceHeader_title
1688 */
1689 public int titleRes;
1690
1691 /**
1692 * Title of the header that is shown to the user.
1693 * @attr ref android.R.styleable#PreferenceHeader_title
1694 */
1695 public CharSequence title;
1696
1697 /**
1698 * Resource ID of optional summary describing what this header controls.
1699 * @attr ref android.R.styleable#PreferenceHeader_summary
1700 */
1701 public int summaryRes;
1702
1703 /**
1704 * Optional summary describing what this header controls.
1705 * @attr ref android.R.styleable#PreferenceHeader_summary
1706 */
1707 public CharSequence summary;
1708
1709 /**
1710 * Optional icon resource to show for this header.
1711 * @attr ref android.R.styleable#PreferenceHeader_icon
1712 */
1713 public int iconRes;
1714
1715 /**
1716 * Full class name of the fragment to display when this header is
1717 * selected.
1718 * @attr ref android.R.styleable#PreferenceHeader_fragment
1719 */
1720 public String fragment;
1721
1722 /**
1723 * Optional arguments to supply to the fragment when it is
1724 * instantiated.
1725 */
1726 public Bundle fragmentArguments;
1727
1728 /**
1729 * Intent to launch when the preference is selected.
1730 */
1731 public Intent intent;
1732
1733 /**
1734 * Optional additional data for use by subclasses of the activity
1735 */
1736 public Bundle extras;
1737
1738 public Header() {
1739 // Empty
1740 }
1741
1742 /**
1743 * Return the currently set title. If {@link #titleRes} is set,
1744 * this resource is loaded from <var>res</var> and returned. Otherwise
1745 * {@link #title} is returned.
1746 */
1747 public CharSequence getTitle(Resources res) {
1748 if (titleRes != 0) {
1749 return res.getText(titleRes);
1750 }
1751 return title;
1752 }
1753
1754 /**
1755 * Return the currently set summary. If {@link #summaryRes} is set,
1756 * this resource is loaded from <var>res</var> and returned. Otherwise
1757 * {@link #summary} is returned.
1758 */
1759 public CharSequence getSummary(Resources res) {
1760 if (summaryRes != 0) {
1761 return res.getText(summaryRes);
1762 }
1763 return summary;
1764 }
1765
1766 @Override
1767 public int describeContents() {
1768 return 0;
1769 }
1770
1771 @Override
1772 public void writeToParcel(Parcel dest, int flags) {
1773 dest.writeLong(id);
1774 dest.writeInt(titleRes);
1775 TextUtils.writeToParcel(title, dest, flags);
1776 dest.writeInt(summaryRes);
1777 TextUtils.writeToParcel(summary, dest, flags);
1778 dest.writeInt(iconRes);
1779 dest.writeString(fragment);
1780 dest.writeBundle(fragmentArguments);
1781 if (intent != null) {
1782 dest.writeInt(1);
1783 intent.writeToParcel(dest, flags);
1784 } else {
1785 dest.writeInt(0);
1786 }
1787 dest.writeBundle(extras);
1788 }
1789
1790 public void readFromParcel(Parcel in) {
1791 id = in.readLong();
1792 titleRes = in.readInt();
1793 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1794 summaryRes = in.readInt();
1795 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1796 iconRes = in.readInt();
1797 fragment = in.readString();
1798 fragmentArguments = in.readBundle();
1799 if (in.readInt() != 0) {
1800 intent = Intent.CREATOR.createFromParcel(in);
1801 }
1802 extras = in.readBundle();
1803 }
1804
1805 Header(Parcel in) {
1806 readFromParcel(in);
1807 }
1808
1809 public static final Creator<Header> CREATOR = new Creator<Header>() {
1810 public Header createFromParcel(Parcel source) {
1811 return new Header(source);
1812 }
1813 public Header[] newArray(int size) {
1814 return new Header[size];
1815 }
1816 };
1817 }
1818}