blob: af0d8bb157bb28f13789ba356597ee8342f4a4fa [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
134 private static final String HEADERS_TAG = ":settings:headers";
135 private static final String CUR_HEADER_TAG = ":settings:cur_header";
136
137 /**
138 * When starting this activity, the invoking Intent can contain this extra
139 * string to specify which fragment should be initially displayed.
140 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
141 * will call isValidFragment() to confirm that the fragment class name is valid for this
142 * activity.
143 */
144 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
145
146 /**
147 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
148 * this extra can also be specified to supply a Bundle of arguments to pass
149 * to that fragment when it is instantiated during the initial creation
150 * of the activity.
151 */
152 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
153
154 /**
155 * When starting this activity, the invoking Intent can contain this extra
156 * boolean that the header list should not be displayed. This is most often
157 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
158 * the activity to display a specific fragment that the user has navigated
159 * to.
160 */
161 public static final String EXTRA_NO_HEADERS = ":settings:no_headers";
162
163 // extras that allow any preference activity to be launched as part of a wizard
164
165 // show Back and Next buttons? takes boolean parameter
166 // Back will then return RESULT_CANCELED and Next RESULT_OK
167 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
168
169 // add a Skip button?
170 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
171
172 // specify custom text for the Back or Next buttons, or cause a button to not appear
173 // at all by setting it to null
174 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
175 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
176
177 /**
178 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
179 * this extra can also be specify to supply the title to be shown for
180 * that fragment.
181 */
182 protected static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
183
184 private static final String BACK_STACK_PREFS = ":settings:prefs";
185
186 private static final String META_DATA_KEY_HEADER_ID =
187 "com.android.settings.TOP_LEVEL_HEADER_ID";
188
189 private static final String META_DATA_KEY_FRAGMENT_CLASS =
190 "com.android.settings.FRAGMENT_CLASS";
191
192 private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
193
194 private static final String SAVE_KEY_CURRENT_HEADER = "com.android.settings.CURRENT_HEADER";
195
196 private static boolean sShowNoHomeNotice = false;
197
198 private String mFragmentClass;
199 private int mTopLevelHeaderId;
200 private Header mFirstHeader;
201 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 Meglioca915662014-02-06 15:46:19 -0800281 DashboardSummary.class.getName(),
282 ApnSettings.class.getName()
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800283 };
284
285 private SharedPreferences mDevelopmentPreferences;
286 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
287
288 // TODO: Update Call Settings based on airplane mode state.
289
290 protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
291
292 private AuthenticatorHelper mAuthenticatorHelper;
293 private boolean mListeningToAccountUpdates;
294
295 private Button mNextButton;
296
297 private boolean mBatteryPresent = true;
298 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
299
300 @Override
301 public void onReceive(Context context, Intent intent) {
302 String action = intent.getAction();
303 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
304 boolean batteryPresent = Utils.isBatteryPresent(intent);
305
306 if (mBatteryPresent != batteryPresent) {
307 mBatteryPresent = batteryPresent;
308 invalidateHeaders();
309 }
310 }
311 }
312 };
313
314 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
315 private Header mCurHeader;
316 private HeaderAdapter mHeaderAdapter;
317
318 private class TitlePair extends Pair<Integer, CharSequence> {
319
320 public TitlePair(Integer first, CharSequence second) {
321 super(first, second);
322 }
323 }
324
325 private final ArrayList<TitlePair> mTitleStack = new ArrayList<TitlePair>();
326
327 private DrawerLayout mDrawerLayout;
328 private ListView mDrawer;
329 private ActionBarDrawerToggle mDrawerToggle;
330 private ActionBar mActionBar;
331
332 private static final int MSG_BUILD_HEADERS = 1;
333 private Handler mHandler = new Handler() {
334 @Override
335 public void handleMessage(Message msg) {
336 switch (msg.what) {
337 case MSG_BUILD_HEADERS: {
338 mHeaders.clear();
339 onBuildHeaders(mHeaders);
340 mHeaderAdapter.notifyDataSetChanged();
341 if (mCurHeader != null) {
342 Header mappedHeader = findBestMatchingHeader(mCurHeader, mHeaders);
343 if (mappedHeader != null) {
344 setSelectedHeader(mappedHeader);
345 }
346 }
347 } break;
348 }
349 }
350 };
351
352 @Override
353 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
354 // Override the fragment title for Wallpaper settings
355 int titleRes = pref.getTitleRes();
356 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
357 titleRes = R.string.wallpaper_settings_fragment_title;
358 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
359 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
360 if (UserManager.get(this).isLinkedUser()) {
361 titleRes = R.string.profile_info_settings_title;
362 } else {
363 titleRes = R.string.user_info_settings_title;
364 }
365 }
366 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
367 null, 0);
368 return true;
369 }
370
371 @Override
372 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
373 return false;
374 }
375
376 private class DrawerListener implements DrawerLayout.DrawerListener {
377 @Override
378 public void onDrawerOpened(View drawerView) {
379 mDrawerToggle.onDrawerOpened(drawerView);
380 }
381
382 @Override
383 public void onDrawerClosed(View drawerView) {
384 mDrawerToggle.onDrawerClosed(drawerView);
385 onHeaderClick(mCurrentHeader);
386 }
387
388 @Override
389 public void onDrawerSlide(View drawerView, float slideOffset) {
390 mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
391 }
392
393 @Override
394 public void onDrawerStateChanged(int newState) {
395 mDrawerToggle.onDrawerStateChanged(newState);
396 }
397 }
398
399 private class DrawerItemClickListener implements ListView.OnItemClickListener {
400 @Override
401 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
402 mDrawerLayout.closeDrawer(mDrawer);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000403 onListItemClick((ListView)parent, view, position, id);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800404 }
405 }
406
407 private Header findBestMatchingHeader(Header cur, ArrayList<Header> from) {
408 ArrayList<Header> matches = new ArrayList<Header>();
409 for (int j=0; j<from.size(); j++) {
410 Header oh = from.get(j);
411 if (cur == oh || (cur.id != HEADER_ID_UNDEFINED && cur.id == oh.id)) {
412 // Must be this one.
413 matches.clear();
414 matches.add(oh);
415 break;
416 }
417 if (cur.fragment != null) {
418 if (cur.fragment.equals(oh.fragment)) {
419 matches.add(oh);
420 }
421 } else if (cur.intent != null) {
422 if (cur.intent.equals(oh.intent)) {
423 matches.add(oh);
424 }
425 } else if (cur.title != null) {
426 if (cur.title.equals(oh.title)) {
427 matches.add(oh);
428 }
429 }
430 }
431 final int NM = matches.size();
432 if (NM == 1) {
433 return matches.get(0);
434 } else if (NM > 1) {
435 for (int j=0; j<NM; j++) {
436 Header oh = matches.get(j);
437 if (cur.fragmentArguments != null &&
438 cur.fragmentArguments.equals(oh.fragmentArguments)) {
439 return oh;
440 }
441 if (cur.extras != null && cur.extras.equals(oh.extras)) {
442 return oh;
443 }
444 if (cur.title != null && cur.title.equals(oh.title)) {
445 return oh;
446 }
447 }
448 }
449 return null;
450 }
451
452 private void invalidateHeaders() {
453 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
454 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
455 }
456 }
457
458 @Override
459 protected void onPostCreate(Bundle savedInstanceState) {
460 super.onPostCreate(savedInstanceState);
461
462 // Sync the toggle state after onRestoreInstanceState has occurred.
463 if (mDrawerToggle != null) {
464 mDrawerToggle.syncState();
465 }
466 }
467
468 @Override
469 public void onConfigurationChanged(Configuration newConfig) {
470 super.onConfigurationChanged(newConfig);
471 if (mDrawerToggle != null) {
472 mDrawerToggle.onConfigurationChanged(newConfig);
473 }
474 }
475
476 @Override
477 public boolean onOptionsItemSelected(MenuItem item) {
478 if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) {
479 return true;
480 }
481 return super.onOptionsItemSelected(item);
482 }
483
484 @Override
485 protected void onCreate(Bundle savedInstanceState) {
486 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
487 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
488 }
489
490 mAuthenticatorHelper = new AuthenticatorHelper();
491 mAuthenticatorHelper.updateAuthDescriptions(this);
492 mAuthenticatorHelper.onAccountsUpdated(this, null);
493
494 DevicePolicyManager dpm =
495 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
496 mHeaderAdapter= new HeaderAdapter(this, getHeaders(), mAuthenticatorHelper, dpm);
497
498 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
499 Context.MODE_PRIVATE);
500
501 getMetaData();
502
503 super.onCreate(savedInstanceState);
504
505 setContentView(R.layout.settings_main);
506
507 getFragmentManager().addOnBackStackChangedListener(this);
508
509 mActionBar = getActionBar();
510 if (mActionBar != null) {
511 mActionBar.setDisplayHomeAsUpEnabled(true);
512 mActionBar.setHomeButtonEnabled(true);
513
514 mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
515 mDrawerLayout.setDrawerListener(new DrawerListener());
516
517 mDrawer = (ListView) findViewById(R.id.headers_drawer);
518 mDrawer.setAdapter(mHeaderAdapter);
519 mDrawer.setOnItemClickListener(new DrawerItemClickListener());
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000520 mDrawer.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800521
522 mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
523 R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
524 }
525
526 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
527 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
528
529 if (savedInstanceState != null) {
530 // We are restarting from a previous saved state; used that to
531 // initialize, instead of starting fresh.
532 ArrayList<Header> headers = savedInstanceState.getParcelableArrayList(HEADERS_TAG);
533 if (headers != null) {
534 mHeaders.addAll(headers);
535 int curHeader = savedInstanceState.getInt(CUR_HEADER_TAG,
536 (int) HEADER_ID_UNDEFINED);
537 if (curHeader >= 0 && curHeader < mHeaders.size()) {
538 setSelectedHeader(mHeaders.get(curHeader));
539 }
540 }
541
542 } else {
543 if (initialFragment != null) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000544 // If we are just showing a fragment, we want to run in
545 // new fragment mode, but don't need to compute and show
546 // the headers.
547 switchToHeader(initialFragment, initialArguments, true);
548
549 final int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
550 if (initialTitle != 0) {
551 setTitle(getText(initialTitle));
552 }
553 } else {
554 // We need to try to build the headers.
555 onBuildHeaders(mHeaders);
556
557 // If there are headers, then at this point we need to show
558 // them and, depending on the screen, we may also show in-line
559 // the currently selected preference fragment.
560 if (mHeaders.size() > 0) {
561 if (initialFragment == null) {
562 Header h = onGetInitialHeader();
563 switchToHeader(h, false);
564 } else {
565 switchToHeader(initialFragment, initialArguments, false);
566 }
567 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800568 }
569 }
570
571 // see if we should show Back/Next buttons
572 Intent intent = getIntent();
573 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
574
575 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
576 if (buttonBar != null) {
577 buttonBar.setVisibility(View.VISIBLE);
578
579 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
580 backButton.setOnClickListener(new OnClickListener() {
581 public void onClick(View v) {
582 setResult(RESULT_CANCELED);
583 finish();
584 }
585 });
586 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
587 skipButton.setOnClickListener(new OnClickListener() {
588 public void onClick(View v) {
589 setResult(RESULT_OK);
590 finish();
591 }
592 });
593 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
594 mNextButton.setOnClickListener(new OnClickListener() {
595 public void onClick(View v) {
596 setResult(RESULT_OK);
597 finish();
598 }
599 });
600
601 // set our various button parameters
602 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
603 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
604 if (TextUtils.isEmpty(buttonText)) {
605 mNextButton.setVisibility(View.GONE);
606 }
607 else {
608 mNextButton.setText(buttonText);
609 }
610 }
611 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
612 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
613 if (TextUtils.isEmpty(buttonText)) {
614 backButton.setVisibility(View.GONE);
615 }
616 else {
617 backButton.setText(buttonText);
618 }
619 }
620 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
621 skipButton.setVisibility(View.VISIBLE);
622 }
623 }
624 }
625
626 if (!onIsHidingHeaders()) {
627 highlightHeader(mTopLevelHeaderId);
628 }
629
630 // Retrieve any saved state
631 if (savedInstanceState != null) {
632 mCurrentHeader = savedInstanceState.getParcelable(SAVE_KEY_CURRENT_HEADER);
633 }
634 }
635
636 @Override
637 public void onBackStackChanged() {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000638 final int count = getFragmentManager().getBackStackEntryCount() + 1;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800639 TitlePair pair = null;
640 int last;
641 while (mTitleStack.size() > count) {
642 last = mTitleStack.size() - 1;
643 pair = mTitleStack.remove(last);
644 }
645 // Check if we go back
646 if (pair != null) {
647 int size = mTitleStack.size();
648 if (size > 0) {
649 last = mTitleStack.size() - 1;
650 pair = mTitleStack.get(last);
651 if (pair != null) {
652 final CharSequence title;
653 if (pair.first > 0) {
654 title = getText(pair.first);
655 } else {
656 title = pair.second;
657 }
658 setTitle(title);
659 }
660 }
661 }
662 }
663
664 /**
665 * Returns the Header list
666 */
667 private List<Header> getHeaders() {
668 return mHeaders;
669 }
670
671 @Override
672 protected void onSaveInstanceState(Bundle outState) {
673 super.onSaveInstanceState(outState);
674
675 if (mHeaders.size() > 0) {
676 outState.putParcelableArrayList(HEADERS_TAG, mHeaders);
677 if (mCurHeader != null) {
678 int index = mHeaders.indexOf(mCurHeader);
679 if (index >= 0) {
680 outState.putInt(CUR_HEADER_TAG, index);
681 }
682 }
683 }
684
685 // Save the current fragment, if it is the same as originally launched
686 if (mCurrentHeader != null) {
687 outState.putParcelable(SAVE_KEY_CURRENT_HEADER, mCurrentHeader);
688 }
689 }
690
691 @Override
692 public void onResume() {
693 super.onResume();
694
695 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
696 @Override
697 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
698 invalidateHeaders();
699 }
700 };
701 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
702 mDevelopmentPreferencesListener);
703
704 mHeaderAdapter.resume();
705 invalidateHeaders();
706
707 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
708 }
709
710 @Override
711 public void onPause() {
712 super.onPause();
713
714 unregisterReceiver(mBatteryInfoReceiver);
715
716 mHeaderAdapter.pause();
717
718 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
719 mDevelopmentPreferencesListener);
720
721 mDevelopmentPreferencesListener = null;
722 }
723
724 @Override
725 public void onDestroy() {
726 super.onDestroy();
727 if (mListeningToAccountUpdates) {
728 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
729 }
730 }
731
732 /**
733 * @hide
734 */
735 protected boolean isValidFragment(String fragmentName) {
736 // Almost all fragments are wrapped in this,
737 // except for a few that have their own activities.
738 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
739 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
740 }
741 return false;
742 }
743
744 /**
745 * When in two-pane mode, switch to the fragment pane to show the given
746 * preference fragment.
747 *
748 * @param header The new header to display.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000749 * @param validate true means that the fragment's Header needs to be validated
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800750 */
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000751 private void switchToHeader(Header header, boolean validate) {
752 if (mCurHeader == header) {
753 // This is the header we are currently displaying. Just make sure
754 // to pop the stack up to its root state.
755 getFragmentManager().popBackStack(BACK_STACK_PREFS,
756 FragmentManager.POP_BACK_STACK_INCLUSIVE);
757 } else {
758 mTitleStack.clear();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800759 if (header.fragment == null) {
760 throw new IllegalStateException("can't switch to header that has no fragment");
761 }
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000762 switchToHeaderInner(header.fragment, header.fragmentArguments, validate);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800763 setSelectedHeader(header);
764 final CharSequence title;
765 if (header.fragment.equals("com.android.settings.dashboard.DashboardSummary")) {
766 title = getResources().getString(R.string.settings_label);
767 } else {
768 title = header.getTitle(getResources());
769 }
770 final TitlePair pair = new TitlePair(0, title);
771 mTitleStack.add(pair);
772 setTitle(title);
773 }
774 }
775
776 private void setSelectedHeader(Header header) {
777 mCurHeader = header;
778 int index = mHeaders.indexOf(header);
779 if (mDrawer != null) {
780 if (index >= 0) {
781 mDrawer.setItemChecked(index, true);
782 } else {
783 mDrawer.clearChoices();
784 }
785 }
786 }
787
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000788 public Header onGetInitialHeader() {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800789 String fragmentClass = getStartingFragmentClass(super.getIntent());
790 if (fragmentClass != null) {
791 Header header = new Header();
792 header.fragment = fragmentClass;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000793 header.title = getTitle();
794 header.fragmentArguments = getIntent().getExtras();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800795 mCurrentHeader = header;
796 return header;
797 }
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000798
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800799 return mFirstHeader;
800 }
801
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000802 /**
803 * When in two-pane mode, switch the fragment pane to show the given
804 * preference fragment.
805 *
806 * @param fragmentName The name of the fragment to display.
807 * @param args Optional arguments to supply to the fragment.
808 * @param validate true means that the fragment's Header needs to be validated
809 */
810 private void switchToHeader(String fragmentName, Bundle args, boolean validate) {
811 setSelectedHeader(null);
812 switchToHeaderInner(fragmentName, args, validate);
813 }
814
815 private void switchToHeaderInner(String fragmentName, Bundle args, boolean validate) {
816 getFragmentManager().popBackStack(BACK_STACK_PREFS,
817 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800818 if (validate && !isValidFragment(fragmentName)) {
819 throw new IllegalArgumentException("Invalid fragment for this activity: "
820 + fragmentName);
821 }
822 Fragment f = Fragment.instantiate(this, fragmentName, args);
823 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800824 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800825 transaction.replace(R.id.prefs, f);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800826 transaction.commitAllowingStateLoss();
827 }
828
829 @Override
830 public void onNewIntent(Intent intent) {
831 super.onNewIntent(intent);
832
833 // If it is not launched from history, then reset to top-level
834 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
835 if (mDrawer != null) {
836 mDrawer.setSelectionFromTop(0, 0);
837 }
838 }
839 }
840
841 /**
842 * Called to determine whether the header list should be hidden.
843 * The default implementation returns the
844 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
845 * This is set to false, for example, when the activity is being re-launched
846 * to show a particular preference activity.
847 */
848 public boolean onIsHidingHeaders() {
849 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
850 }
851
852 private void highlightHeader(int id) {
853 if (id != 0) {
854 Integer index = mHeaderIndexMap.get(id);
855 if (index != null && mDrawer != null) {
856 mDrawer.setItemChecked(index, true);
857 if (mDrawer.getVisibility() == View.VISIBLE) {
858 mDrawer.smoothScrollToPosition(index);
859 }
860 }
861 }
862 }
863
864 @Override
865 public Intent getIntent() {
866 Intent superIntent = super.getIntent();
867 String startingFragment = getStartingFragmentClass(superIntent);
868 // This is called from super.onCreate, isMultiPane() is not yet reliable
869 // Do not use onIsHidingHeaders either, which relies itself on this method
870 if (startingFragment != null) {
871 Intent modIntent = new Intent(superIntent);
872 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
873 Bundle args = superIntent.getExtras();
874 if (args != null) {
875 args = new Bundle(args);
876 } else {
877 args = new Bundle();
878 }
879 args.putParcelable("intent", superIntent);
880 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
881 return modIntent;
882 }
883 return superIntent;
884 }
885
886 /**
887 * Checks if the component name in the intent is different from the Settings class and
888 * returns the class name to load as a fragment.
889 */
890 private String getStartingFragmentClass(Intent intent) {
891 if (mFragmentClass != null) return mFragmentClass;
892
893 String intentClass = intent.getComponent().getClassName();
894 if (intentClass.equals(getClass().getName())) return null;
895
896 if ("com.android.settings.ManageApplications".equals(intentClass)
897 || "com.android.settings.RunningServices".equals(intentClass)
898 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
899 // Old names of manage apps.
900 intentClass = com.android.settings.applications.ManageApplications.class.getName();
901 }
902
903 return intentClass;
904 }
905
906 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000907 * Start a new fragment containing a preference panel. If the preferences
908 * are being displayed in multi-pane mode, the given fragment class will
909 * be instantiated and placed in the appropriate pane. If running in
910 * single-pane mode, a new activity will be launched in which to show the
911 * fragment.
912 *
913 * @param fragmentClass Full name of the class implementing the fragment.
914 * @param args Any desired arguments to supply to the fragment.
915 * @param titleRes Optional resource identifier of the title of this
916 * fragment.
917 * @param titleText Optional text of the title of this fragment.
918 * @param resultTo Optional fragment that result data should be sent to.
919 * If non-null, resultTo.onActivityResult() will be called when this
920 * preference panel is done. The launched panel must use
921 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
922 * @param resultRequestCode If resultTo is non-null, this is the caller's
923 * request code to be received with the resut.
924 */
925 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
926 CharSequence titleText, Fragment resultTo,
927 int resultRequestCode) {
928 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
929 }
930
931 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800932 * Called by a preference panel fragment to finish itself.
933 *
934 * @param caller The fragment that is asking to be finished.
935 * @param resultCode Optional result code to send back to the original
936 * launching fragment.
937 * @param resultData Optional result data to send back to the original
938 * launching fragment.
939 */
940 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
941 setResult(resultCode, resultData);
942 }
943
944 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000945 * Start a new fragment.
946 *
947 * @param fragment The fragment to start
948 * @param push If true, the current fragment will be pushed onto the back stack. If false,
949 * the current fragment will be replaced.
950 */
951 public void startPreferenceFragment(Fragment fragment, boolean push) {
952 FragmentTransaction transaction = getFragmentManager().beginTransaction();
953 transaction.replace(R.id.prefs, fragment);
954 if (push) {
955 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
956 transaction.addToBackStack(BACK_STACK_PREFS);
957 } else {
958 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
959 }
960 transaction.commitAllowingStateLoss();
961 }
962
963 /**
964 * Start a new fragment.
965 *
966 * @param fragmentName The name of the fragment to display.
967 * @param args Optional arguments to supply to the fragment.
968 * @param resultTo Option fragment that should receive the result of
969 * the activity launch.
970 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
971 * report the result.
972 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
973 * valid one then it will be used to get the title. Otherwise the titleText
974 * argument will be used as the title.
975 * @param titleText string to display for the title of.
976 */
977 private void startWithFragment(String fragmentName, Bundle args, Fragment resultTo,
978 int resultRequestCode, int titleRes, CharSequence titleText) {
979 Fragment f = Fragment.instantiate(this, fragmentName, args);
980 if (resultTo != null) {
981 f.setTargetFragment(resultTo, resultRequestCode);
982 }
983 FragmentTransaction transaction = getFragmentManager().beginTransaction();
984 transaction.replace(R.id.prefs, f);
985 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
986 transaction.addToBackStack(BACK_STACK_PREFS);
987 transaction.commitAllowingStateLoss();
988
989 final TitlePair pair;
990 final CharSequence cs;
991 if (titleRes != 0) {
992 pair = new TitlePair(titleRes, null);
993 cs = getText(titleRes);
994 } else {
995 pair = new TitlePair(0, titleText);
996 cs = titleText;
997 }
998 setTitle(cs);
999 mTitleStack.add(pair);
1000 }
1001
1002 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001003 * Called when the activity needs its list of headers build. By
1004 * implementing this and adding at least one item to the list, you
1005 * will cause the activity to run in its modern fragment mode. Note
1006 * that this function may not always be called; for example, if the
1007 * activity has been asked to display a particular fragment without
1008 * the header list, there is no need to build the headers.
1009 *
1010 * <p>Typical implementations will use {@link #loadHeadersFromResource}
1011 * to fill in the list from a resource.
1012 *
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;
1383 } else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings) {
1384 return HEADER_TYPE_SWITCH;
1385 } else if (header.id == R.id.security_settings) {
1386 return HEADER_TYPE_BUTTON;
1387 } else {
1388 return HEADER_TYPE_NORMAL;
1389 }
1390 }
1391
1392 @Override
1393 public int getItemViewType(int position) {
1394 Header header = getItem(position);
1395 return getHeaderType(header);
1396 }
1397
1398 @Override
1399 public boolean areAllItemsEnabled() {
1400 return false; // because of categories
1401 }
1402
1403 @Override
1404 public boolean isEnabled(int position) {
1405 return getItemViewType(position) != HEADER_TYPE_CATEGORY;
1406 }
1407
1408 @Override
1409 public int getViewTypeCount() {
1410 return HEADER_TYPE_COUNT;
1411 }
1412
1413 @Override
1414 public boolean hasStableIds() {
1415 return true;
1416 }
1417
1418 public HeaderAdapter(Context context, List<Header> objects,
1419 AuthenticatorHelper authenticatorHelper, DevicePolicyManager dpm) {
1420 super(context, 0, objects);
1421
1422 mAuthHelper = authenticatorHelper;
1423 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1424
1425 // Temp Switches provided as placeholder until the adapter replaces these with actual
1426 // Switches inflated from their layouts. Must be done before adapter is set in super
1427 mWifiEnabler = new WifiEnabler(context, new Switch(context));
1428 mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
1429 mDevicePolicyManager = dpm;
1430 }
1431
1432 @Override
1433 public View getView(int position, View convertView, ViewGroup parent) {
1434 HeaderViewHolder holder;
1435 Header header = getItem(position);
1436 int headerType = getHeaderType(header);
1437 View view = null;
1438
1439 if (convertView == null) {
1440 holder = new HeaderViewHolder();
1441 switch (headerType) {
1442 case HEADER_TYPE_CATEGORY:
1443 view = new TextView(getContext(), null,
1444 android.R.attr.listSeparatorTextViewStyle);
1445 holder.mTitle = (TextView) view;
1446 break;
1447
1448 case HEADER_TYPE_SWITCH:
1449 view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
1450 false);
1451 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1452 holder.mTitle = (TextView)
1453 view.findViewById(com.android.internal.R.id.title);
1454 holder.mSummary = (TextView)
1455 view.findViewById(com.android.internal.R.id.summary);
1456 holder.mSwitch = (Switch) view.findViewById(R.id.switchWidget);
1457 break;
1458
1459 case HEADER_TYPE_BUTTON:
1460 view = mInflater.inflate(R.layout.preference_header_button_item, parent,
1461 false);
1462 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1463 holder.mTitle = (TextView)
1464 view.findViewById(com.android.internal.R.id.title);
1465 holder.mSummary = (TextView)
1466 view.findViewById(com.android.internal.R.id.summary);
1467 holder.mButton = (ImageButton) view.findViewById(R.id.buttonWidget);
1468 holder.mDivider = view.findViewById(R.id.divider);
1469 break;
1470
1471 case HEADER_TYPE_NORMAL:
1472 view = mInflater.inflate(
1473 R.layout.preference_header_item, parent,
1474 false);
1475 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1476 holder.mTitle = (TextView)
1477 view.findViewById(com.android.internal.R.id.title);
1478 holder.mSummary = (TextView)
1479 view.findViewById(com.android.internal.R.id.summary);
1480 break;
1481 }
1482 view.setTag(holder);
1483 } else {
1484 view = convertView;
1485 holder = (HeaderViewHolder) view.getTag();
1486 }
1487
1488 // All view fields must be updated every time, because the view may be recycled
1489 switch (headerType) {
1490 case HEADER_TYPE_CATEGORY:
1491 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1492 break;
1493
1494 case HEADER_TYPE_SWITCH:
1495 // Would need a different treatment if the main menu had more switches
1496 if (header.id == R.id.wifi_settings) {
1497 mWifiEnabler.setSwitch(holder.mSwitch);
1498 } else {
1499 mBluetoothEnabler.setSwitch(holder.mSwitch);
1500 }
1501 updateCommonHeaderView(header, holder);
1502 break;
1503
1504 case HEADER_TYPE_BUTTON:
1505 if (header.id == R.id.security_settings) {
1506 boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled();
1507 if (hasCert) {
1508 holder.mButton.setVisibility(View.VISIBLE);
1509 holder.mDivider.setVisibility(View.VISIBLE);
1510 boolean isManaged = mDevicePolicyManager.getDeviceOwner() != null;
1511 if (isManaged) {
1512 holder.mButton.setImageResource(R.drawable.ic_settings_about);
1513 } else {
1514 holder.mButton.setImageResource(
1515 android.R.drawable.stat_notify_error);
1516 }
1517 holder.mButton.setOnClickListener(new OnClickListener() {
1518 @Override
1519 public void onClick(View v) {
1520 Intent intent = new Intent(
1521 android.provider.Settings.ACTION_MONITORING_CERT_INFO);
1522 getContext().startActivity(intent);
1523 }
1524 });
1525 } else {
1526 holder.mButton.setVisibility(View.GONE);
1527 holder.mDivider.setVisibility(View.GONE);
1528 }
1529 }
1530 updateCommonHeaderView(header, holder);
1531 break;
1532
1533 case HEADER_TYPE_NORMAL:
1534 updateCommonHeaderView(header, holder);
1535 break;
1536 }
1537
1538 return view;
1539 }
1540
1541 private void updateCommonHeaderView(Header header, HeaderViewHolder holder) {
1542 if (header.extras != null
1543 && header.extras.containsKey(ManageAccountsSettings.KEY_ACCOUNT_TYPE)) {
1544 String accType = header.extras.getString(
1545 ManageAccountsSettings.KEY_ACCOUNT_TYPE);
1546 Drawable icon = mAuthHelper.getDrawableForType(getContext(), accType);
1547 setHeaderIcon(holder, icon);
1548 } else {
1549 holder.mIcon.setImageResource(header.iconRes);
1550 }
1551 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1552 CharSequence summary = header.getSummary(getContext().getResources());
1553 if (!TextUtils.isEmpty(summary)) {
1554 holder.mSummary.setVisibility(View.VISIBLE);
1555 holder.mSummary.setText(summary);
1556 } else {
1557 holder.mSummary.setVisibility(View.GONE);
1558 }
1559 }
1560
1561 private void setHeaderIcon(HeaderViewHolder holder, Drawable icon) {
1562 ViewGroup.LayoutParams lp = holder.mIcon.getLayoutParams();
1563 lp.width = getContext().getResources().getDimensionPixelSize(
1564 R.dimen.header_icon_width);
1565 lp.height = lp.width;
1566 holder.mIcon.setLayoutParams(lp);
1567 holder.mIcon.setImageDrawable(icon);
1568 }
1569
1570 public void resume() {
1571 mWifiEnabler.resume();
1572 mBluetoothEnabler.resume();
1573 }
1574
1575 public void pause() {
1576 mWifiEnabler.pause();
1577 mBluetoothEnabler.pause();
1578 }
1579 }
1580
1581 private void onListItemClick(ListView l, View v, int position, long id) {
1582 if (!isResumed()) {
1583 return;
1584 }
1585 Object item = mHeaderAdapter.getItem(position);
1586 if (item instanceof Header) {
1587 mCurrentHeader = (Header) item;
1588 }
1589 }
1590
1591 /**
1592 * Called when the user selects an item in the header list. The default
1593 * implementation will call either
Fabrice Di Megliodc77b732014-02-04 12:41:30 -08001594 * {@link #startWithFragment(String, android.os.Bundle, android.app.Fragment, int, int, CharSequence)}
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001595 * or {@link #switchToHeader(com.android.settings.SettingsActivity.Header, boolean)}
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001596 * as appropriate.
1597 *
1598 * @param header The header that was selected.
1599 */
1600 private void onHeaderClick(Header header) {
1601 if (header == null) return;
1602 if (header.fragment != null) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001603 switchToHeader(header, false);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001604 } else if (header.intent != null) {
1605 startActivity(header.intent);
1606 }
1607 }
1608
1609 @Override
1610 public boolean shouldUpRecreateTask(Intent targetIntent) {
1611 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1612 }
1613
1614 @Override
1615 public void onAccountsUpdated(Account[] accounts) {
1616 // TODO: watch for package upgrades to invalidate cache; see 7206643
1617 mAuthenticatorHelper.updateAuthDescriptions(this);
1618 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1619 invalidateHeaders();
1620 }
1621
1622 public static void requestHomeNotice() {
1623 sShowNoHomeNotice = true;
1624 }
1625
1626 /**
1627 * Default value for {@link Header#id Header.id} indicating that no
1628 * identifier value is set. All other values (including those below -1)
1629 * are valid.
1630 */
1631 private static final long HEADER_ID_UNDEFINED = -1;
1632
1633 /**
1634 * Description of a single Header item that the user can select.
1635 */
1636 static final class Header implements Parcelable {
1637 /**
1638 * Identifier for this header, to correlate with a new list when
1639 * it is updated. The default value is
1640 * {@link SettingsActivity#HEADER_ID_UNDEFINED}, meaning no id.
1641 * @attr ref android.R.styleable#PreferenceHeader_id
1642 */
1643 public long id = HEADER_ID_UNDEFINED;
1644
1645 /**
1646 * Resource ID of title of the header that is shown to the user.
1647 * @attr ref android.R.styleable#PreferenceHeader_title
1648 */
1649 public int titleRes;
1650
1651 /**
1652 * Title of the header that is shown to the user.
1653 * @attr ref android.R.styleable#PreferenceHeader_title
1654 */
1655 public CharSequence title;
1656
1657 /**
1658 * Resource ID of optional summary describing what this header controls.
1659 * @attr ref android.R.styleable#PreferenceHeader_summary
1660 */
1661 public int summaryRes;
1662
1663 /**
1664 * Optional summary describing what this header controls.
1665 * @attr ref android.R.styleable#PreferenceHeader_summary
1666 */
1667 public CharSequence summary;
1668
1669 /**
1670 * Optional icon resource to show for this header.
1671 * @attr ref android.R.styleable#PreferenceHeader_icon
1672 */
1673 public int iconRes;
1674
1675 /**
1676 * Full class name of the fragment to display when this header is
1677 * selected.
1678 * @attr ref android.R.styleable#PreferenceHeader_fragment
1679 */
1680 public String fragment;
1681
1682 /**
1683 * Optional arguments to supply to the fragment when it is
1684 * instantiated.
1685 */
1686 public Bundle fragmentArguments;
1687
1688 /**
1689 * Intent to launch when the preference is selected.
1690 */
1691 public Intent intent;
1692
1693 /**
1694 * Optional additional data for use by subclasses of the activity
1695 */
1696 public Bundle extras;
1697
1698 public Header() {
1699 // Empty
1700 }
1701
1702 /**
1703 * Return the currently set title. If {@link #titleRes} is set,
1704 * this resource is loaded from <var>res</var> and returned. Otherwise
1705 * {@link #title} is returned.
1706 */
1707 public CharSequence getTitle(Resources res) {
1708 if (titleRes != 0) {
1709 return res.getText(titleRes);
1710 }
1711 return title;
1712 }
1713
1714 /**
1715 * Return the currently set summary. If {@link #summaryRes} is set,
1716 * this resource is loaded from <var>res</var> and returned. Otherwise
1717 * {@link #summary} is returned.
1718 */
1719 public CharSequence getSummary(Resources res) {
1720 if (summaryRes != 0) {
1721 return res.getText(summaryRes);
1722 }
1723 return summary;
1724 }
1725
1726 @Override
1727 public int describeContents() {
1728 return 0;
1729 }
1730
1731 @Override
1732 public void writeToParcel(Parcel dest, int flags) {
1733 dest.writeLong(id);
1734 dest.writeInt(titleRes);
1735 TextUtils.writeToParcel(title, dest, flags);
1736 dest.writeInt(summaryRes);
1737 TextUtils.writeToParcel(summary, dest, flags);
1738 dest.writeInt(iconRes);
1739 dest.writeString(fragment);
1740 dest.writeBundle(fragmentArguments);
1741 if (intent != null) {
1742 dest.writeInt(1);
1743 intent.writeToParcel(dest, flags);
1744 } else {
1745 dest.writeInt(0);
1746 }
1747 dest.writeBundle(extras);
1748 }
1749
1750 public void readFromParcel(Parcel in) {
1751 id = in.readLong();
1752 titleRes = in.readInt();
1753 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1754 summaryRes = in.readInt();
1755 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1756 iconRes = in.readInt();
1757 fragment = in.readString();
1758 fragmentArguments = in.readBundle();
1759 if (in.readInt() != 0) {
1760 intent = Intent.CREATOR.createFromParcel(in);
1761 }
1762 extras = in.readBundle();
1763 }
1764
1765 Header(Parcel in) {
1766 readFromParcel(in);
1767 }
1768
1769 public static final Creator<Header> CREATOR = new Creator<Header>() {
1770 public Header createFromParcel(Parcel source) {
1771 return new Header(source);
1772 }
1773 public Header[] newArray(int size) {
1774 return new Header[size];
1775 }
1776 };
1777 }
1778}