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