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