blob: 32ab523ac82b32eebc371e09c23b7d2faf8477c0 [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
408 if (isFinishing()) return;
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800409 onHeaderClick(mSelectedHeader);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800410 }
411
412 @Override
413 public void onDrawerSlide(View drawerView, float slideOffset) {
414 mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
415 }
416
417 @Override
418 public void onDrawerStateChanged(int newState) {
419 mDrawerToggle.onDrawerStateChanged(newState);
420 }
421 }
422
423 private class DrawerItemClickListener implements ListView.OnItemClickListener {
424 @Override
425 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
426 mDrawerLayout.closeDrawer(mDrawer);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000427 onListItemClick((ListView)parent, view, position, id);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800428 }
429 }
430
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800431 private Header findBestMatchingHeader(Header current, ArrayList<Header> from) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800432 ArrayList<Header> matches = new ArrayList<Header>();
433 for (int j=0; j<from.size(); j++) {
434 Header oh = from.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800435 if (current == oh || (current.id != HEADER_ID_UNDEFINED && current.id == oh.id)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800436 // Must be this one.
437 matches.clear();
438 matches.add(oh);
439 break;
440 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800441 if (current.fragment != null) {
442 if (current.fragment.equals(oh.fragment)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800443 matches.add(oh);
444 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800445 } else if (current.intent != null) {
446 if (current.intent.equals(oh.intent)) {
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.title != null) {
450 if (current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800451 matches.add(oh);
452 }
453 }
454 }
455 final int NM = matches.size();
456 if (NM == 1) {
457 return matches.get(0);
458 } else if (NM > 1) {
459 for (int j=0; j<NM; j++) {
460 Header oh = matches.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800461 if (current.fragmentArguments != null &&
462 current.fragmentArguments.equals(oh.fragmentArguments)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800463 return oh;
464 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800465 if (current.extras != null && current.extras.equals(oh.extras)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800466 return oh;
467 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800468 if (current.title != null && current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800469 return oh;
470 }
471 }
472 }
473 return null;
474 }
475
476 private void invalidateHeaders() {
477 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
478 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
479 }
480 }
481
482 @Override
483 protected void onPostCreate(Bundle savedInstanceState) {
484 super.onPostCreate(savedInstanceState);
485
486 // Sync the toggle state after onRestoreInstanceState has occurred.
487 if (mDrawerToggle != null) {
488 mDrawerToggle.syncState();
489 }
490 }
491
492 @Override
493 public void onConfigurationChanged(Configuration newConfig) {
494 super.onConfigurationChanged(newConfig);
495 if (mDrawerToggle != null) {
496 mDrawerToggle.onConfigurationChanged(newConfig);
497 }
498 }
499
500 @Override
501 public boolean onOptionsItemSelected(MenuItem item) {
502 if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) {
503 return true;
504 }
505 return super.onOptionsItemSelected(item);
506 }
507
508 @Override
509 protected void onCreate(Bundle savedInstanceState) {
510 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
511 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
512 }
513
514 mAuthenticatorHelper = new AuthenticatorHelper();
515 mAuthenticatorHelper.updateAuthDescriptions(this);
516 mAuthenticatorHelper.onAccountsUpdated(this, null);
517
518 DevicePolicyManager dpm =
519 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
520 mHeaderAdapter= new HeaderAdapter(this, getHeaders(), mAuthenticatorHelper, dpm);
521
522 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
523 Context.MODE_PRIVATE);
524
525 getMetaData();
526
527 super.onCreate(savedInstanceState);
528
529 setContentView(R.layout.settings_main);
530
531 getFragmentManager().addOnBackStackChangedListener(this);
532
533 mActionBar = getActionBar();
534 if (mActionBar != null) {
535 mActionBar.setDisplayHomeAsUpEnabled(true);
536 mActionBar.setHomeButtonEnabled(true);
537
538 mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800539
540 mDrawer = (ListView) findViewById(R.id.headers_drawer);
541 mDrawer.setAdapter(mHeaderAdapter);
542 mDrawer.setOnItemClickListener(new DrawerItemClickListener());
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000543 mDrawer.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800544
545 mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
546 R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
547 }
548
549 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
550 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
551
552 if (savedInstanceState != null) {
553 // We are restarting from a previous saved state; used that to
554 // initialize, instead of starting fresh.
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800555
556 ArrayList<TitlePair> titles =
557 savedInstanceState.getParcelableArrayList(SAVE_KEY_TITLES_TAG);
558 if (titles != null) {
559 mTitleStack.addAll(titles);
560 }
561 final int lastTitle = mTitleStack.size() - 1;
562 if (lastTitle >= 0) {
563 final TitlePair last = mTitleStack.get(lastTitle);
564 setTitleFromPair(last);
565 }
566
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800567 ArrayList<Header> headers =
568 savedInstanceState.getParcelableArrayList(SAVE_KEY_HEADERS_TAG);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800569 if (headers != null) {
570 mHeaders.addAll(headers);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800571 int curHeader = savedInstanceState.getInt(SAVE_KEY_CURRENT_HEADER_TAG,
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800572 (int) HEADER_ID_UNDEFINED);
573 if (curHeader >= 0 && curHeader < mHeaders.size()) {
574 setSelectedHeader(mHeaders.get(curHeader));
575 }
576 }
577
578 } else {
579 if (initialFragment != null) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000580 // If we are just showing a fragment, we want to run in
581 // new fragment mode, but don't need to compute and show
582 // the headers.
583 switchToHeader(initialFragment, initialArguments, true);
584
585 final int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
586 if (initialTitle != 0) {
587 setTitle(getText(initialTitle));
588 }
589 } else {
590 // We need to try to build the headers.
591 onBuildHeaders(mHeaders);
592
593 // If there are headers, then at this point we need to show
594 // them and, depending on the screen, we may also show in-line
595 // the currently selected preference fragment.
596 if (mHeaders.size() > 0) {
597 if (initialFragment == null) {
598 Header h = onGetInitialHeader();
599 switchToHeader(h, false);
600 } else {
601 switchToHeader(initialFragment, initialArguments, false);
602 }
603 }
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 Meglio65027202014-02-11 15:19:46 -0800788 if (mCurrentHeader == header) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000789 // This is the header we are currently displaying. Just make sure
790 // to pop the stack up to its root state.
791 getFragmentManager().popBackStack(BACK_STACK_PREFS,
792 FragmentManager.POP_BACK_STACK_INCLUSIVE);
793 } else {
794 mTitleStack.clear();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800795 if (header.fragment == null) {
796 throw new IllegalStateException("can't switch to header that has no fragment");
797 }
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000798 switchToHeaderInner(header.fragment, header.fragmentArguments, validate);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800799 setSelectedHeader(header);
800 final CharSequence title;
801 if (header.fragment.equals("com.android.settings.dashboard.DashboardSummary")) {
802 title = getResources().getString(R.string.settings_label);
803 } else {
804 title = header.getTitle(getResources());
805 }
806 final TitlePair pair = new TitlePair(0, title);
807 mTitleStack.add(pair);
808 setTitle(title);
809 }
810 }
811
812 private void setSelectedHeader(Header header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800813 mCurrentHeader = header;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800814 int index = mHeaders.indexOf(header);
815 if (mDrawer != null) {
816 if (index >= 0) {
817 mDrawer.setItemChecked(index, true);
818 } else {
819 mDrawer.clearChoices();
820 }
821 }
822 }
823
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000824 public Header onGetInitialHeader() {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800825 String fragmentClass = getStartingFragmentClass(super.getIntent());
826 if (fragmentClass != null) {
827 Header header = new Header();
828 header.fragment = fragmentClass;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000829 header.title = getTitle();
830 header.fragmentArguments = getIntent().getExtras();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800831 return header;
832 }
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000833
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800834 return mFirstHeader;
835 }
836
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000837 /**
838 * When in two-pane mode, switch the fragment pane to show the given
839 * preference fragment.
840 *
841 * @param fragmentName The name of the fragment to display.
842 * @param args Optional arguments to supply to the fragment.
843 * @param validate true means that the fragment's Header needs to be validated
844 */
845 private void switchToHeader(String fragmentName, Bundle args, boolean validate) {
846 setSelectedHeader(null);
847 switchToHeaderInner(fragmentName, args, validate);
848 }
849
850 private void switchToHeaderInner(String fragmentName, Bundle args, boolean validate) {
851 getFragmentManager().popBackStack(BACK_STACK_PREFS,
852 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800853 if (validate && !isValidFragment(fragmentName)) {
854 throw new IllegalArgumentException("Invalid fragment for this activity: "
855 + fragmentName);
856 }
857 Fragment f = Fragment.instantiate(this, fragmentName, args);
858 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800859 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800860 transaction.replace(R.id.prefs, f);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800861 transaction.commitAllowingStateLoss();
862 }
863
864 @Override
865 public void onNewIntent(Intent intent) {
866 super.onNewIntent(intent);
867
868 // If it is not launched from history, then reset to top-level
869 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
870 if (mDrawer != null) {
871 mDrawer.setSelectionFromTop(0, 0);
872 }
873 }
874 }
875
876 /**
877 * Called to determine whether the header list should be hidden.
878 * The default implementation returns the
879 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
880 * This is set to false, for example, when the activity is being re-launched
881 * to show a particular preference activity.
882 */
883 public boolean onIsHidingHeaders() {
884 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
885 }
886
887 private void highlightHeader(int id) {
888 if (id != 0) {
889 Integer index = mHeaderIndexMap.get(id);
890 if (index != null && mDrawer != null) {
891 mDrawer.setItemChecked(index, true);
892 if (mDrawer.getVisibility() == View.VISIBLE) {
893 mDrawer.smoothScrollToPosition(index);
894 }
895 }
896 }
897 }
898
899 @Override
900 public Intent getIntent() {
901 Intent superIntent = super.getIntent();
902 String startingFragment = getStartingFragmentClass(superIntent);
903 // This is called from super.onCreate, isMultiPane() is not yet reliable
904 // Do not use onIsHidingHeaders either, which relies itself on this method
905 if (startingFragment != null) {
906 Intent modIntent = new Intent(superIntent);
907 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
908 Bundle args = superIntent.getExtras();
909 if (args != null) {
910 args = new Bundle(args);
911 } else {
912 args = new Bundle();
913 }
914 args.putParcelable("intent", superIntent);
915 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
916 return modIntent;
917 }
918 return superIntent;
919 }
920
921 /**
922 * Checks if the component name in the intent is different from the Settings class and
923 * returns the class name to load as a fragment.
924 */
925 private String getStartingFragmentClass(Intent intent) {
926 if (mFragmentClass != null) return mFragmentClass;
927
928 String intentClass = intent.getComponent().getClassName();
929 if (intentClass.equals(getClass().getName())) return null;
930
931 if ("com.android.settings.ManageApplications".equals(intentClass)
932 || "com.android.settings.RunningServices".equals(intentClass)
933 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
934 // Old names of manage apps.
935 intentClass = com.android.settings.applications.ManageApplications.class.getName();
936 }
937
938 return intentClass;
939 }
940
941 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000942 * Start a new fragment containing a preference panel. If the preferences
943 * are being displayed in multi-pane mode, the given fragment class will
944 * be instantiated and placed in the appropriate pane. If running in
945 * single-pane mode, a new activity will be launched in which to show the
946 * fragment.
947 *
948 * @param fragmentClass Full name of the class implementing the fragment.
949 * @param args Any desired arguments to supply to the fragment.
950 * @param titleRes Optional resource identifier of the title of this
951 * fragment.
952 * @param titleText Optional text of the title of this fragment.
953 * @param resultTo Optional fragment that result data should be sent to.
954 * If non-null, resultTo.onActivityResult() will be called when this
955 * preference panel is done. The launched panel must use
956 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
957 * @param resultRequestCode If resultTo is non-null, this is the caller's
958 * request code to be received with the resut.
959 */
960 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
961 CharSequence titleText, Fragment resultTo,
962 int resultRequestCode) {
963 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
964 }
965
966 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800967 * Called by a preference panel fragment to finish itself.
968 *
969 * @param caller The fragment that is asking to be finished.
970 * @param resultCode Optional result code to send back to the original
971 * launching fragment.
972 * @param resultData Optional result data to send back to the original
973 * launching fragment.
974 */
975 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
976 setResult(resultCode, resultData);
977 }
978
979 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000980 * Start a new fragment.
981 *
982 * @param fragment The fragment to start
983 * @param push If true, the current fragment will be pushed onto the back stack. If false,
984 * the current fragment will be replaced.
985 */
986 public void startPreferenceFragment(Fragment fragment, boolean push) {
987 FragmentTransaction transaction = getFragmentManager().beginTransaction();
988 transaction.replace(R.id.prefs, fragment);
989 if (push) {
990 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
991 transaction.addToBackStack(BACK_STACK_PREFS);
992 } else {
993 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
994 }
995 transaction.commitAllowingStateLoss();
996 }
997
998 /**
999 * Start a new fragment.
1000 *
1001 * @param fragmentName The name of the fragment to display.
1002 * @param args Optional arguments to supply to the fragment.
1003 * @param resultTo Option fragment that should receive the result of
1004 * the activity launch.
1005 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
1006 * report the result.
1007 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
1008 * valid one then it will be used to get the title. Otherwise the titleText
1009 * argument will be used as the title.
1010 * @param titleText string to display for the title of.
1011 */
1012 private void startWithFragment(String fragmentName, Bundle args, Fragment resultTo,
1013 int resultRequestCode, int titleRes, CharSequence titleText) {
1014 Fragment f = Fragment.instantiate(this, fragmentName, args);
1015 if (resultTo != null) {
1016 f.setTargetFragment(resultTo, resultRequestCode);
1017 }
1018 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1019 transaction.replace(R.id.prefs, f);
1020 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1021 transaction.addToBackStack(BACK_STACK_PREFS);
1022 transaction.commitAllowingStateLoss();
1023
1024 final TitlePair pair;
1025 final CharSequence cs;
1026 if (titleRes != 0) {
1027 pair = new TitlePair(titleRes, null);
1028 cs = getText(titleRes);
1029 } else {
1030 pair = new TitlePair(0, titleText);
1031 cs = titleText;
1032 }
1033 setTitle(cs);
1034 mTitleStack.add(pair);
1035 }
1036
1037 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001038 * Called when the activity needs its list of headers build. By
1039 * implementing this and adding at least one item to the list, you
1040 * will cause the activity to run in its modern fragment mode. Note
1041 * that this function may not always be called; for example, if the
1042 * activity has been asked to display a particular fragment without
1043 * the header list, there is no need to build the headers.
1044 *
1045 * <p>Typical implementations will use {@link #loadHeadersFromResource}
1046 * to fill in the list from a resource.
1047 *
1048 * @param headers The list in which to place the headers.
1049 */
1050 private void onBuildHeaders(List<Header> headers) {
1051 loadHeadersFromResource(R.xml.settings_headers, headers);
1052 updateHeaderList(headers);
1053 }
1054
1055 /**
1056 * Parse the given XML file as a header description, adding each
1057 * parsed Header into the target list.
1058 *
1059 * @param resid The XML resource to load and parse.
1060 * @param target The list in which the parsed headers should be placed.
1061 */
1062 private void loadHeadersFromResource(int resid, List<Header> target) {
1063 XmlResourceParser parser = null;
1064 try {
1065 parser = getResources().getXml(resid);
1066 AttributeSet attrs = Xml.asAttributeSet(parser);
1067
1068 int type;
1069 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1070 && type != XmlPullParser.START_TAG) {
1071 // Parse next until start tag is found
1072 }
1073
1074 String nodeName = parser.getName();
1075 if (!"preference-headers".equals(nodeName)) {
1076 throw new RuntimeException(
1077 "XML document must start with <preference-headers> tag; found"
1078 + nodeName + " at " + parser.getPositionDescription());
1079 }
1080
1081 Bundle curBundle = null;
1082
1083 final int outerDepth = parser.getDepth();
1084 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1085 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1086 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1087 continue;
1088 }
1089
1090 nodeName = parser.getName();
1091 if ("header".equals(nodeName)) {
1092 Header header = new Header();
1093
1094 TypedArray sa = obtainStyledAttributes(
1095 attrs, com.android.internal.R.styleable.PreferenceHeader);
1096 header.id = sa.getResourceId(
1097 com.android.internal.R.styleable.PreferenceHeader_id,
1098 (int)HEADER_ID_UNDEFINED);
1099 TypedValue tv = sa.peekValue(
1100 com.android.internal.R.styleable.PreferenceHeader_title);
1101 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1102 if (tv.resourceId != 0) {
1103 header.titleRes = tv.resourceId;
1104 } else {
1105 header.title = tv.string;
1106 }
1107 }
1108 tv = sa.peekValue(
1109 com.android.internal.R.styleable.PreferenceHeader_summary);
1110 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1111 if (tv.resourceId != 0) {
1112 header.summaryRes = tv.resourceId;
1113 } else {
1114 header.summary = tv.string;
1115 }
1116 }
1117 header.iconRes = sa.getResourceId(
1118 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
1119 header.fragment = sa.getString(
1120 com.android.internal.R.styleable.PreferenceHeader_fragment);
1121 sa.recycle();
1122
1123 if (curBundle == null) {
1124 curBundle = new Bundle();
1125 }
1126
1127 final int innerDepth = parser.getDepth();
1128 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1129 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
1130 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1131 continue;
1132 }
1133
1134 String innerNodeName = parser.getName();
1135 if (innerNodeName.equals("extra")) {
1136 getResources().parseBundleExtra("extra", attrs, curBundle);
1137 XmlUtils.skipCurrentTag(parser);
1138
1139 } else if (innerNodeName.equals("intent")) {
1140 header.intent = Intent.parseIntent(getResources(), parser, attrs);
1141
1142 } else {
1143 XmlUtils.skipCurrentTag(parser);
1144 }
1145 }
1146
1147 if (curBundle.size() > 0) {
1148 header.fragmentArguments = curBundle;
1149 curBundle = null;
1150 }
1151
1152 target.add(header);
1153 } else {
1154 XmlUtils.skipCurrentTag(parser);
1155 }
1156 }
1157
1158 } catch (XmlPullParserException e) {
1159 throw new RuntimeException("Error parsing headers", e);
1160 } catch (IOException e) {
1161 throw new RuntimeException("Error parsing headers", e);
1162 } finally {
1163 if (parser != null) parser.close();
1164 }
1165 }
1166
1167 private void updateHeaderList(List<Header> target) {
1168 final boolean showDev = mDevelopmentPreferences.getBoolean(
1169 DevelopmentSettings.PREF_SHOW,
1170 android.os.Build.TYPE.equals("eng"));
1171 int i = 0;
1172
1173 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1174 mHeaderIndexMap.clear();
1175 while (i < target.size()) {
1176 Header header = target.get(i);
1177 // Ids are integers, so downcasting
1178 int id = (int) header.id;
1179 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1180 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1181 } else if (id == R.id.wifi_settings) {
1182 // Remove WiFi Settings if WiFi service is not available.
1183 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1184 target.remove(i);
1185 }
1186 } else if (id == R.id.bluetooth_settings) {
1187 // Remove Bluetooth Settings if Bluetooth service is not available.
1188 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1189 target.remove(i);
1190 }
1191 } else if (id == R.id.data_usage_settings) {
1192 // Remove data usage when kernel module not enabled
1193 final INetworkManagementService netManager = INetworkManagementService.Stub
1194 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1195 try {
1196 if (!netManager.isBandwidthControlEnabled()) {
1197 target.remove(i);
1198 }
1199 } catch (RemoteException e) {
1200 // ignored
1201 }
1202 } else if (id == R.id.battery_settings) {
1203 // Remove battery settings when battery is not available. (e.g. TV)
1204
1205 if (!mBatteryPresent) {
1206 target.remove(i);
1207 }
1208 } else if (id == R.id.account_settings) {
1209 int headerIndex = i + 1;
1210 i = insertAccountsHeaders(target, headerIndex);
1211 } else if (id == R.id.home_settings) {
1212 if (!updateHomeSettingHeaders(header)) {
1213 target.remove(i);
1214 }
1215 } else if (id == R.id.user_settings) {
1216 if (!UserHandle.MU_ENABLED
1217 || !UserManager.supportsMultipleUsers()
1218 || Utils.isMonkeyRunning()) {
1219 target.remove(i);
1220 }
1221 } else if (id == R.id.nfc_payment_settings) {
1222 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1223 target.remove(i);
1224 } else {
1225 // Only show if NFC is on and we have the HCE feature
1226 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1227 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1228 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1229 target.remove(i);
1230 }
1231 }
1232 } else if (id == R.id.development_settings) {
1233 if (!showDev) {
1234 target.remove(i);
1235 }
1236 } else if (id == R.id.account_add) {
1237 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1238 target.remove(i);
1239 }
1240 }
1241
1242 if (i < target.size() && target.get(i) == header
1243 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1244 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1245 target.remove(i);
1246 }
1247
1248 // Increment if the current one wasn't removed by the Utils code.
1249 if (i < target.size() && target.get(i) == header) {
1250 // Hold on to the first header, when we need to reset to the top-level
1251 if (mFirstHeader == null &&
1252 HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
1253 mFirstHeader = header;
1254 }
1255 mHeaderIndexMap.put(id, i);
1256 i++;
1257 }
1258 }
1259 }
1260
1261 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1262 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1263 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1264 for (String accountType : accountTypes) {
1265 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1266 if (label == null) {
1267 continue;
1268 }
1269
1270 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1271 boolean skipToAccount = accounts.length == 1
1272 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1273 Header accHeader = new Header();
1274 accHeader.title = label;
1275 if (accHeader.extras == null) {
1276 accHeader.extras = new Bundle();
1277 }
1278 if (skipToAccount) {
1279 accHeader.fragment = AccountSyncSettings.class.getName();
1280 accHeader.fragmentArguments = new Bundle();
1281 // Need this for the icon
1282 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1283 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1284 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1285 accounts[0]);
1286 } else {
1287 accHeader.fragment = ManageAccountsSettings.class.getName();
1288 accHeader.fragmentArguments = new Bundle();
1289 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1290 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1291 accountType);
1292 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1293 label.toString());
1294 }
1295 accountHeaders.add(accHeader);
1296 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1297 }
1298
1299 // Sort by label
1300 Collections.sort(accountHeaders, new Comparator<Header>() {
1301 @Override
1302 public int compare(Header h1, Header h2) {
1303 return h1.title.toString().compareTo(h2.title.toString());
1304 }
1305 });
1306
1307 for (Header header : accountHeaders) {
1308 target.add(headerIndex++, header);
1309 }
1310 if (!mListeningToAccountUpdates) {
1311 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1312 mListeningToAccountUpdates = true;
1313 }
1314 return headerIndex;
1315 }
1316
1317 private boolean updateHomeSettingHeaders(Header header) {
1318 // Once we decide to show Home settings, keep showing it forever
1319 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1320 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1321 return true;
1322 }
1323
1324 try {
1325 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1326 getPackageManager().getHomeActivities(homeApps);
1327 if (homeApps.size() < 2) {
1328 // When there's only one available home app, omit this settings
1329 // category entirely at the top level UI. If the user just
1330 // uninstalled the penultimate home app candidiate, we also
1331 // now tell them about why they aren't seeing 'Home' in the list.
1332 if (sShowNoHomeNotice) {
1333 sShowNoHomeNotice = false;
1334 NoHomeDialogFragment.show(this);
1335 }
1336 return false;
1337 } else {
1338 // Okay, we're allowing the Home settings category. Tell it, when
1339 // invoked via this front door, that we'll need to be told about the
1340 // case when the user uninstalls all but one home app.
1341 if (header.fragmentArguments == null) {
1342 header.fragmentArguments = new Bundle();
1343 }
1344 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1345 }
1346 } catch (Exception e) {
1347 // Can't look up the home activity; bail on configuring the icon
1348 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1349 }
1350
1351 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1352 return true;
1353 }
1354
1355 private void getMetaData() {
1356 try {
1357 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1358 PackageManager.GET_META_DATA);
1359 if (ai == null || ai.metaData == null) return;
1360 mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
1361 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1362 } catch (NameNotFoundException nnfe) {
1363 // No recovery
1364 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1365 }
1366 }
1367
1368 // give subclasses access to the Next button
1369 public boolean hasNextButton() {
1370 return mNextButton != null;
1371 }
1372
1373 public Button getNextButton() {
1374 return mNextButton;
1375 }
1376
1377 public static class NoHomeDialogFragment extends DialogFragment {
1378 public static void show(Activity parent) {
1379 final NoHomeDialogFragment dialog = new NoHomeDialogFragment();
1380 dialog.show(parent.getFragmentManager(), null);
1381 }
1382
1383 @Override
1384 public Dialog onCreateDialog(Bundle savedInstanceState) {
1385 return new AlertDialog.Builder(getActivity())
1386 .setMessage(R.string.only_one_home_message)
1387 .setPositiveButton(android.R.string.ok, null)
1388 .create();
1389 }
1390 }
1391
1392 private static class HeaderAdapter extends ArrayAdapter<Header> {
1393 static final int HEADER_TYPE_CATEGORY = 0;
1394 static final int HEADER_TYPE_NORMAL = 1;
1395 static final int HEADER_TYPE_SWITCH = 2;
1396 static final int HEADER_TYPE_BUTTON = 3;
1397 private static final int HEADER_TYPE_COUNT = HEADER_TYPE_BUTTON + 1;
1398
1399 private final WifiEnabler mWifiEnabler;
1400 private final BluetoothEnabler mBluetoothEnabler;
1401 private AuthenticatorHelper mAuthHelper;
1402 private DevicePolicyManager mDevicePolicyManager;
1403
1404 private static class HeaderViewHolder {
1405 ImageView mIcon;
1406 TextView mTitle;
1407 TextView mSummary;
1408 Switch mSwitch;
1409 ImageButton mButton;
1410 View mDivider;
1411 }
1412
1413 private LayoutInflater mInflater;
1414
1415 static int getHeaderType(Header header) {
1416 if (header.fragment == null && header.intent == null) {
1417 return HEADER_TYPE_CATEGORY;
1418 } else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings) {
1419 return HEADER_TYPE_SWITCH;
1420 } else if (header.id == R.id.security_settings) {
1421 return HEADER_TYPE_BUTTON;
1422 } else {
1423 return HEADER_TYPE_NORMAL;
1424 }
1425 }
1426
1427 @Override
1428 public int getItemViewType(int position) {
1429 Header header = getItem(position);
1430 return getHeaderType(header);
1431 }
1432
1433 @Override
1434 public boolean areAllItemsEnabled() {
1435 return false; // because of categories
1436 }
1437
1438 @Override
1439 public boolean isEnabled(int position) {
1440 return getItemViewType(position) != HEADER_TYPE_CATEGORY;
1441 }
1442
1443 @Override
1444 public int getViewTypeCount() {
1445 return HEADER_TYPE_COUNT;
1446 }
1447
1448 @Override
1449 public boolean hasStableIds() {
1450 return true;
1451 }
1452
1453 public HeaderAdapter(Context context, List<Header> objects,
1454 AuthenticatorHelper authenticatorHelper, DevicePolicyManager dpm) {
1455 super(context, 0, objects);
1456
1457 mAuthHelper = authenticatorHelper;
1458 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1459
1460 // Temp Switches provided as placeholder until the adapter replaces these with actual
1461 // Switches inflated from their layouts. Must be done before adapter is set in super
1462 mWifiEnabler = new WifiEnabler(context, new Switch(context));
1463 mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
1464 mDevicePolicyManager = dpm;
1465 }
1466
1467 @Override
1468 public View getView(int position, View convertView, ViewGroup parent) {
1469 HeaderViewHolder holder;
1470 Header header = getItem(position);
1471 int headerType = getHeaderType(header);
1472 View view = null;
1473
1474 if (convertView == null) {
1475 holder = new HeaderViewHolder();
1476 switch (headerType) {
1477 case HEADER_TYPE_CATEGORY:
1478 view = new TextView(getContext(), null,
1479 android.R.attr.listSeparatorTextViewStyle);
1480 holder.mTitle = (TextView) view;
1481 break;
1482
1483 case HEADER_TYPE_SWITCH:
1484 view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
1485 false);
1486 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1487 holder.mTitle = (TextView)
1488 view.findViewById(com.android.internal.R.id.title);
1489 holder.mSummary = (TextView)
1490 view.findViewById(com.android.internal.R.id.summary);
1491 holder.mSwitch = (Switch) view.findViewById(R.id.switchWidget);
1492 break;
1493
1494 case HEADER_TYPE_BUTTON:
1495 view = mInflater.inflate(R.layout.preference_header_button_item, parent,
1496 false);
1497 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1498 holder.mTitle = (TextView)
1499 view.findViewById(com.android.internal.R.id.title);
1500 holder.mSummary = (TextView)
1501 view.findViewById(com.android.internal.R.id.summary);
1502 holder.mButton = (ImageButton) view.findViewById(R.id.buttonWidget);
1503 holder.mDivider = view.findViewById(R.id.divider);
1504 break;
1505
1506 case HEADER_TYPE_NORMAL:
1507 view = mInflater.inflate(
1508 R.layout.preference_header_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 break;
1516 }
1517 view.setTag(holder);
1518 } else {
1519 view = convertView;
1520 holder = (HeaderViewHolder) view.getTag();
1521 }
1522
1523 // All view fields must be updated every time, because the view may be recycled
1524 switch (headerType) {
1525 case HEADER_TYPE_CATEGORY:
1526 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1527 break;
1528
1529 case HEADER_TYPE_SWITCH:
1530 // Would need a different treatment if the main menu had more switches
1531 if (header.id == R.id.wifi_settings) {
1532 mWifiEnabler.setSwitch(holder.mSwitch);
1533 } else {
1534 mBluetoothEnabler.setSwitch(holder.mSwitch);
1535 }
1536 updateCommonHeaderView(header, holder);
1537 break;
1538
1539 case HEADER_TYPE_BUTTON:
1540 if (header.id == R.id.security_settings) {
1541 boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled();
1542 if (hasCert) {
1543 holder.mButton.setVisibility(View.VISIBLE);
1544 holder.mDivider.setVisibility(View.VISIBLE);
1545 boolean isManaged = mDevicePolicyManager.getDeviceOwner() != null;
1546 if (isManaged) {
1547 holder.mButton.setImageResource(R.drawable.ic_settings_about);
1548 } else {
1549 holder.mButton.setImageResource(
1550 android.R.drawable.stat_notify_error);
1551 }
1552 holder.mButton.setOnClickListener(new OnClickListener() {
1553 @Override
1554 public void onClick(View v) {
1555 Intent intent = new Intent(
1556 android.provider.Settings.ACTION_MONITORING_CERT_INFO);
1557 getContext().startActivity(intent);
1558 }
1559 });
1560 } else {
1561 holder.mButton.setVisibility(View.GONE);
1562 holder.mDivider.setVisibility(View.GONE);
1563 }
1564 }
1565 updateCommonHeaderView(header, holder);
1566 break;
1567
1568 case HEADER_TYPE_NORMAL:
1569 updateCommonHeaderView(header, holder);
1570 break;
1571 }
1572
1573 return view;
1574 }
1575
1576 private void updateCommonHeaderView(Header header, HeaderViewHolder holder) {
1577 if (header.extras != null
1578 && header.extras.containsKey(ManageAccountsSettings.KEY_ACCOUNT_TYPE)) {
1579 String accType = header.extras.getString(
1580 ManageAccountsSettings.KEY_ACCOUNT_TYPE);
1581 Drawable icon = mAuthHelper.getDrawableForType(getContext(), accType);
1582 setHeaderIcon(holder, icon);
1583 } else {
1584 holder.mIcon.setImageResource(header.iconRes);
1585 }
1586 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1587 CharSequence summary = header.getSummary(getContext().getResources());
1588 if (!TextUtils.isEmpty(summary)) {
1589 holder.mSummary.setVisibility(View.VISIBLE);
1590 holder.mSummary.setText(summary);
1591 } else {
1592 holder.mSummary.setVisibility(View.GONE);
1593 }
1594 }
1595
1596 private void setHeaderIcon(HeaderViewHolder holder, Drawable icon) {
1597 ViewGroup.LayoutParams lp = holder.mIcon.getLayoutParams();
1598 lp.width = getContext().getResources().getDimensionPixelSize(
1599 R.dimen.header_icon_width);
1600 lp.height = lp.width;
1601 holder.mIcon.setLayoutParams(lp);
1602 holder.mIcon.setImageDrawable(icon);
1603 }
1604
1605 public void resume() {
1606 mWifiEnabler.resume();
1607 mBluetoothEnabler.resume();
1608 }
1609
1610 public void pause() {
1611 mWifiEnabler.pause();
1612 mBluetoothEnabler.pause();
1613 }
1614 }
1615
1616 private void onListItemClick(ListView l, View v, int position, long id) {
1617 if (!isResumed()) {
1618 return;
1619 }
1620 Object item = mHeaderAdapter.getItem(position);
1621 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001622 mSelectedHeader = (Header) item;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001623 }
1624 }
1625
1626 /**
1627 * Called when the user selects an item in the header list. The default
1628 * implementation will call either
Fabrice Di Megliodc77b732014-02-04 12:41:30 -08001629 * {@link #startWithFragment(String, android.os.Bundle, android.app.Fragment, int, int, CharSequence)}
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001630 * or {@link #switchToHeader(com.android.settings.SettingsActivity.Header, boolean)}
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001631 * as appropriate.
1632 *
1633 * @param header The header that was selected.
1634 */
1635 private void onHeaderClick(Header header) {
1636 if (header == null) return;
1637 if (header.fragment != null) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001638 switchToHeader(header, false);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001639 } else if (header.intent != null) {
1640 startActivity(header.intent);
1641 }
1642 }
1643
1644 @Override
1645 public boolean shouldUpRecreateTask(Intent targetIntent) {
1646 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1647 }
1648
1649 @Override
1650 public void onAccountsUpdated(Account[] accounts) {
1651 // TODO: watch for package upgrades to invalidate cache; see 7206643
1652 mAuthenticatorHelper.updateAuthDescriptions(this);
1653 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1654 invalidateHeaders();
1655 }
1656
1657 public static void requestHomeNotice() {
1658 sShowNoHomeNotice = true;
1659 }
1660
1661 /**
1662 * Default value for {@link Header#id Header.id} indicating that no
1663 * identifier value is set. All other values (including those below -1)
1664 * are valid.
1665 */
1666 private static final long HEADER_ID_UNDEFINED = -1;
1667
1668 /**
1669 * Description of a single Header item that the user can select.
1670 */
1671 static final class Header implements Parcelable {
1672 /**
1673 * Identifier for this header, to correlate with a new list when
1674 * it is updated. The default value is
1675 * {@link SettingsActivity#HEADER_ID_UNDEFINED}, meaning no id.
1676 * @attr ref android.R.styleable#PreferenceHeader_id
1677 */
1678 public long id = HEADER_ID_UNDEFINED;
1679
1680 /**
1681 * Resource ID of title of the header that is shown to the user.
1682 * @attr ref android.R.styleable#PreferenceHeader_title
1683 */
1684 public int titleRes;
1685
1686 /**
1687 * Title of the header that is shown to the user.
1688 * @attr ref android.R.styleable#PreferenceHeader_title
1689 */
1690 public CharSequence title;
1691
1692 /**
1693 * Resource ID of optional summary describing what this header controls.
1694 * @attr ref android.R.styleable#PreferenceHeader_summary
1695 */
1696 public int summaryRes;
1697
1698 /**
1699 * Optional summary describing what this header controls.
1700 * @attr ref android.R.styleable#PreferenceHeader_summary
1701 */
1702 public CharSequence summary;
1703
1704 /**
1705 * Optional icon resource to show for this header.
1706 * @attr ref android.R.styleable#PreferenceHeader_icon
1707 */
1708 public int iconRes;
1709
1710 /**
1711 * Full class name of the fragment to display when this header is
1712 * selected.
1713 * @attr ref android.R.styleable#PreferenceHeader_fragment
1714 */
1715 public String fragment;
1716
1717 /**
1718 * Optional arguments to supply to the fragment when it is
1719 * instantiated.
1720 */
1721 public Bundle fragmentArguments;
1722
1723 /**
1724 * Intent to launch when the preference is selected.
1725 */
1726 public Intent intent;
1727
1728 /**
1729 * Optional additional data for use by subclasses of the activity
1730 */
1731 public Bundle extras;
1732
1733 public Header() {
1734 // Empty
1735 }
1736
1737 /**
1738 * Return the currently set title. If {@link #titleRes} is set,
1739 * this resource is loaded from <var>res</var> and returned. Otherwise
1740 * {@link #title} is returned.
1741 */
1742 public CharSequence getTitle(Resources res) {
1743 if (titleRes != 0) {
1744 return res.getText(titleRes);
1745 }
1746 return title;
1747 }
1748
1749 /**
1750 * Return the currently set summary. If {@link #summaryRes} is set,
1751 * this resource is loaded from <var>res</var> and returned. Otherwise
1752 * {@link #summary} is returned.
1753 */
1754 public CharSequence getSummary(Resources res) {
1755 if (summaryRes != 0) {
1756 return res.getText(summaryRes);
1757 }
1758 return summary;
1759 }
1760
1761 @Override
1762 public int describeContents() {
1763 return 0;
1764 }
1765
1766 @Override
1767 public void writeToParcel(Parcel dest, int flags) {
1768 dest.writeLong(id);
1769 dest.writeInt(titleRes);
1770 TextUtils.writeToParcel(title, dest, flags);
1771 dest.writeInt(summaryRes);
1772 TextUtils.writeToParcel(summary, dest, flags);
1773 dest.writeInt(iconRes);
1774 dest.writeString(fragment);
1775 dest.writeBundle(fragmentArguments);
1776 if (intent != null) {
1777 dest.writeInt(1);
1778 intent.writeToParcel(dest, flags);
1779 } else {
1780 dest.writeInt(0);
1781 }
1782 dest.writeBundle(extras);
1783 }
1784
1785 public void readFromParcel(Parcel in) {
1786 id = in.readLong();
1787 titleRes = in.readInt();
1788 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1789 summaryRes = in.readInt();
1790 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1791 iconRes = in.readInt();
1792 fragment = in.readString();
1793 fragmentArguments = in.readBundle();
1794 if (in.readInt() != 0) {
1795 intent = Intent.CREATOR.createFromParcel(in);
1796 }
1797 extras = in.readBundle();
1798 }
1799
1800 Header(Parcel in) {
1801 readFromParcel(in);
1802 }
1803
1804 public static final Creator<Header> CREATOR = new Creator<Header>() {
1805 public Header createFromParcel(Parcel source) {
1806 return new Header(source);
1807 }
1808 public Header[] newArray(int size) {
1809 return new Header[size];
1810 }
1811 };
1812 }
1813}