blob: 522c1b1d9fe4e7f373f62196a25c4d8bf79f5466 [file] [log] [blame]
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings;
18
19import android.accounts.Account;
20import android.accounts.AccountManager;
21import android.accounts.OnAccountsUpdateListener;
22import android.app.ActionBar;
23import android.app.Activity;
24import android.app.Fragment;
25import android.app.FragmentManager;
26import android.app.FragmentTransaction;
27import android.app.AlertDialog;
28import android.app.Dialog;
29import android.app.DialogFragment;
30import android.app.admin.DevicePolicyManager;
31import android.content.BroadcastReceiver;
32import android.content.Context;
33import android.content.Intent;
34import android.content.IntentFilter;
35import android.content.SharedPreferences;
36import android.content.pm.ActivityInfo;
37import android.content.pm.PackageManager;
38import android.content.pm.PackageManager.NameNotFoundException;
39import android.content.pm.ResolveInfo;
40import android.content.res.Configuration;
41import android.content.res.Resources;
42import android.content.res.TypedArray;
43import android.content.res.XmlResourceParser;
44import android.graphics.drawable.Drawable;
45import android.nfc.NfcAdapter;
46import android.os.Bundle;
47import android.os.Handler;
48import android.os.INetworkManagementService;
49import android.os.Message;
50import android.os.Parcel;
51import android.os.Parcelable;
52import android.os.RemoteException;
53import android.os.ServiceManager;
54import android.os.UserHandle;
55import android.os.UserManager;
56import android.preference.Preference;
57import android.preference.PreferenceFragment;
58import android.preference.PreferenceManager;
59import android.preference.PreferenceScreen;
60import android.support.v4.app.ActionBarDrawerToggle;
61import android.support.v4.widget.DrawerLayout;
62import android.text.TextUtils;
63import android.util.AttributeSet;
64import android.util.Log;
65import android.util.Pair;
66import android.util.TypedValue;
67import android.util.Xml;
68import android.view.LayoutInflater;
69import android.view.MenuItem;
70import android.view.View;
71import android.view.View.OnClickListener;
72import android.view.ViewGroup;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +000073import android.widget.AbsListView;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080074import android.widget.AdapterView;
75import android.widget.ArrayAdapter;
76import android.widget.Button;
77import android.widget.ImageButton;
78import android.widget.ImageView;
79import android.widget.ListView;
80import android.widget.Switch;
81import android.widget.TextView;
82
83import com.android.internal.util.ArrayUtils;
84import com.android.internal.util.XmlUtils;
85import com.android.settings.accessibility.AccessibilitySettings;
86import com.android.settings.accessibility.CaptionPropertiesFragment;
87import com.android.settings.accounts.AccountSyncSettings;
88import com.android.settings.accounts.AuthenticatorHelper;
89import com.android.settings.accounts.ChooseAccountFragment;
90import com.android.settings.accounts.ManageAccountsSettings;
91import com.android.settings.applications.ManageApplications;
92import com.android.settings.applications.ProcessStatsUi;
93import com.android.settings.bluetooth.BluetoothEnabler;
94import com.android.settings.bluetooth.BluetoothSettings;
95import com.android.settings.dashboard.DashboardSummary;
96import com.android.settings.deviceinfo.Memory;
97import com.android.settings.deviceinfo.UsbSettings;
98import com.android.settings.fuelgauge.PowerUsageSummary;
99import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
100import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
101import com.android.settings.inputmethod.SpellCheckersSettings;
102import com.android.settings.inputmethod.UserDictionaryList;
103import com.android.settings.location.LocationSettings;
104import com.android.settings.nfc.AndroidBeam;
105import com.android.settings.nfc.PaymentSettings;
106import com.android.settings.print.PrintJobSettingsFragment;
107import com.android.settings.print.PrintSettingsFragment;
108import com.android.settings.tts.TextToSpeechSettings;
109import com.android.settings.users.UserSettings;
110import com.android.settings.vpn2.VpnSettings;
111import com.android.settings.wfd.WifiDisplaySettings;
112import com.android.settings.wifi.AdvancedWifiSettings;
113import com.android.settings.wifi.WifiEnabler;
114import com.android.settings.wifi.WifiSettings;
115import com.android.settings.wifi.p2p.WifiP2pSettings;
116import org.xmlpull.v1.XmlPullParser;
117import org.xmlpull.v1.XmlPullParserException;
118
119import java.io.IOException;
120import java.util.ArrayList;
121import java.util.Collections;
122import java.util.Comparator;
123import java.util.HashMap;
124import java.util.List;
125
126public class SettingsActivity extends Activity
127 implements PreferenceManager.OnPreferenceTreeClickListener,
128 PreferenceFragment.OnPreferenceStartFragmentCallback,
129 ButtonBarHandler, OnAccountsUpdateListener, FragmentManager.OnBackStackChangedListener {
130
131 private static final String LOG_TAG = "Settings";
132
133 // Constants for state save/restore
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800134 private static final String SAVE_KEY_HEADERS_TAG = ":settings:headers";
135 private static final String SAVE_KEY_CURRENT_HEADER_TAG = ":settings:cur_header";
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800136
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
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800194 private static boolean sShowNoHomeNotice = false;
195
196 private String mFragmentClass;
197 private int mTopLevelHeaderId;
198 private Header mFirstHeader;
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800199 private Header mSelectedHeader;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800200 private Header mCurrentHeader;
201
202 // Show only these settings for restricted users
203 private int[] SETTINGS_FOR_RESTRICTED = {
204 R.id.wireless_section,
205 R.id.wifi_settings,
206 R.id.bluetooth_settings,
207 R.id.data_usage_settings,
208 R.id.wireless_settings,
209 R.id.device_section,
210 R.id.sound_settings,
211 R.id.display_settings,
212 R.id.storage_settings,
213 R.id.application_settings,
214 R.id.battery_settings,
215 R.id.personal_section,
216 R.id.location_settings,
217 R.id.security_settings,
218 R.id.language_settings,
219 R.id.user_settings,
220 R.id.account_settings,
221 R.id.account_add,
222 R.id.system_section,
223 R.id.date_time_settings,
224 R.id.about_settings,
225 R.id.accessibility_settings,
226 R.id.print_settings,
227 R.id.nfc_payment_settings,
228 R.id.home_settings
229 };
230
231 private static final String[] ENTRY_FRAGMENTS = {
232 WirelessSettings.class.getName(),
233 WifiSettings.class.getName(),
234 AdvancedWifiSettings.class.getName(),
235 BluetoothSettings.class.getName(),
236 TetherSettings.class.getName(),
237 WifiP2pSettings.class.getName(),
238 VpnSettings.class.getName(),
239 DateTimeSettings.class.getName(),
240 LocalePicker.class.getName(),
241 InputMethodAndLanguageSettings.class.getName(),
242 SpellCheckersSettings.class.getName(),
243 UserDictionaryList.class.getName(),
244 UserDictionarySettings.class.getName(),
245 SoundSettings.class.getName(),
246 DisplaySettings.class.getName(),
247 DeviceInfoSettings.class.getName(),
248 ManageApplications.class.getName(),
249 ProcessStatsUi.class.getName(),
250 NotificationStation.class.getName(),
251 LocationSettings.class.getName(),
252 SecuritySettings.class.getName(),
253 PrivacySettings.class.getName(),
254 DeviceAdminSettings.class.getName(),
255 AccessibilitySettings.class.getName(),
256 CaptionPropertiesFragment.class.getName(),
257 com.android.settings.accessibility.ToggleInversionPreferenceFragment.class.getName(),
258 com.android.settings.accessibility.ToggleContrastPreferenceFragment.class.getName(),
259 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
260 TextToSpeechSettings.class.getName(),
261 Memory.class.getName(),
262 DevelopmentSettings.class.getName(),
263 UsbSettings.class.getName(),
264 AndroidBeam.class.getName(),
265 WifiDisplaySettings.class.getName(),
266 PowerUsageSummary.class.getName(),
267 AccountSyncSettings.class.getName(),
268 CryptKeeperSettings.class.getName(),
269 DataUsageSummary.class.getName(),
270 DreamSettings.class.getName(),
271 UserSettings.class.getName(),
272 NotificationAccessSettings.class.getName(),
273 ManageAccountsSettings.class.getName(),
274 PrintSettingsFragment.class.getName(),
275 PrintJobSettingsFragment.class.getName(),
276 TrustedCredentialsSettings.class.getName(),
277 PaymentSettings.class.getName(),
278 KeyboardLayoutPickerFragment.class.getName(),
279 ChooseAccountFragment.class.getName(),
Fabrice Di Meglioe6c9a5d2014-02-11 19:03:27 -0800280 DashboardSummary.class.getName()
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800281 };
282
283 private SharedPreferences mDevelopmentPreferences;
284 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
285
286 // TODO: Update Call Settings based on airplane mode state.
287
288 protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
289
290 private AuthenticatorHelper mAuthenticatorHelper;
291 private boolean mListeningToAccountUpdates;
292
293 private Button mNextButton;
294
295 private boolean mBatteryPresent = true;
296 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
297
298 @Override
299 public void onReceive(Context context, Intent intent) {
300 String action = intent.getAction();
301 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
302 boolean batteryPresent = Utils.isBatteryPresent(intent);
303
304 if (mBatteryPresent != batteryPresent) {
305 mBatteryPresent = batteryPresent;
306 invalidateHeaders();
307 }
308 }
309 }
310 };
311
312 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800313 private HeaderAdapter mHeaderAdapter;
314
315 private class TitlePair extends Pair<Integer, CharSequence> {
316
317 public TitlePair(Integer first, CharSequence second) {
318 super(first, second);
319 }
320 }
321
322 private final ArrayList<TitlePair> mTitleStack = new ArrayList<TitlePair>();
323
324 private DrawerLayout mDrawerLayout;
325 private ListView mDrawer;
326 private ActionBarDrawerToggle mDrawerToggle;
327 private ActionBar mActionBar;
328
329 private static final int MSG_BUILD_HEADERS = 1;
330 private Handler mHandler = new Handler() {
331 @Override
332 public void handleMessage(Message msg) {
333 switch (msg.what) {
334 case MSG_BUILD_HEADERS: {
335 mHeaders.clear();
336 onBuildHeaders(mHeaders);
337 mHeaderAdapter.notifyDataSetChanged();
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800338 if (mCurrentHeader != null) {
339 Header mappedHeader = findBestMatchingHeader(mCurrentHeader, mHeaders);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800340 if (mappedHeader != null) {
341 setSelectedHeader(mappedHeader);
342 }
343 }
344 } break;
345 }
346 }
347 };
348
349 @Override
350 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
351 // Override the fragment title for Wallpaper settings
352 int titleRes = pref.getTitleRes();
353 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
354 titleRes = R.string.wallpaper_settings_fragment_title;
355 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
356 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
357 if (UserManager.get(this).isLinkedUser()) {
358 titleRes = R.string.profile_info_settings_title;
359 } else {
360 titleRes = R.string.user_info_settings_title;
361 }
362 }
363 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
364 null, 0);
365 return true;
366 }
367
368 @Override
369 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
370 return false;
371 }
372
373 private class DrawerListener implements DrawerLayout.DrawerListener {
374 @Override
375 public void onDrawerOpened(View drawerView) {
376 mDrawerToggle.onDrawerOpened(drawerView);
377 }
378
379 @Override
380 public void onDrawerClosed(View drawerView) {
381 mDrawerToggle.onDrawerClosed(drawerView);
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800382 // Cannot process clicks when the App is finishing
383 if (isFinishing()) return;
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800384 onHeaderClick(mSelectedHeader);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800385 }
386
387 @Override
388 public void onDrawerSlide(View drawerView, float slideOffset) {
389 mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
390 }
391
392 @Override
393 public void onDrawerStateChanged(int newState) {
394 mDrawerToggle.onDrawerStateChanged(newState);
395 }
396 }
397
398 private class DrawerItemClickListener implements ListView.OnItemClickListener {
399 @Override
400 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
401 mDrawerLayout.closeDrawer(mDrawer);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000402 onListItemClick((ListView)parent, view, position, id);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800403 }
404 }
405
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800406 private Header findBestMatchingHeader(Header current, ArrayList<Header> from) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800407 ArrayList<Header> matches = new ArrayList<Header>();
408 for (int j=0; j<from.size(); j++) {
409 Header oh = from.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800410 if (current == oh || (current.id != HEADER_ID_UNDEFINED && current.id == oh.id)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800411 // Must be this one.
412 matches.clear();
413 matches.add(oh);
414 break;
415 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800416 if (current.fragment != null) {
417 if (current.fragment.equals(oh.fragment)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800418 matches.add(oh);
419 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800420 } else if (current.intent != null) {
421 if (current.intent.equals(oh.intent)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800422 matches.add(oh);
423 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800424 } else if (current.title != null) {
425 if (current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800426 matches.add(oh);
427 }
428 }
429 }
430 final int NM = matches.size();
431 if (NM == 1) {
432 return matches.get(0);
433 } else if (NM > 1) {
434 for (int j=0; j<NM; j++) {
435 Header oh = matches.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800436 if (current.fragmentArguments != null &&
437 current.fragmentArguments.equals(oh.fragmentArguments)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800438 return oh;
439 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800440 if (current.extras != null && current.extras.equals(oh.extras)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800441 return oh;
442 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800443 if (current.title != null && current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800444 return oh;
445 }
446 }
447 }
448 return null;
449 }
450
451 private void invalidateHeaders() {
452 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
453 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
454 }
455 }
456
457 @Override
458 protected void onPostCreate(Bundle savedInstanceState) {
459 super.onPostCreate(savedInstanceState);
460
461 // Sync the toggle state after onRestoreInstanceState has occurred.
462 if (mDrawerToggle != null) {
463 mDrawerToggle.syncState();
464 }
465 }
466
467 @Override
468 public void onConfigurationChanged(Configuration newConfig) {
469 super.onConfigurationChanged(newConfig);
470 if (mDrawerToggle != null) {
471 mDrawerToggle.onConfigurationChanged(newConfig);
472 }
473 }
474
475 @Override
476 public boolean onOptionsItemSelected(MenuItem item) {
477 if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) {
478 return true;
479 }
480 return super.onOptionsItemSelected(item);
481 }
482
483 @Override
484 protected void onCreate(Bundle savedInstanceState) {
485 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
486 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
487 }
488
489 mAuthenticatorHelper = new AuthenticatorHelper();
490 mAuthenticatorHelper.updateAuthDescriptions(this);
491 mAuthenticatorHelper.onAccountsUpdated(this, null);
492
493 DevicePolicyManager dpm =
494 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
495 mHeaderAdapter= new HeaderAdapter(this, getHeaders(), mAuthenticatorHelper, dpm);
496
497 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
498 Context.MODE_PRIVATE);
499
500 getMetaData();
501
502 super.onCreate(savedInstanceState);
503
504 setContentView(R.layout.settings_main);
505
506 getFragmentManager().addOnBackStackChangedListener(this);
507
508 mActionBar = getActionBar();
509 if (mActionBar != null) {
510 mActionBar.setDisplayHomeAsUpEnabled(true);
511 mActionBar.setHomeButtonEnabled(true);
512
513 mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800514
515 mDrawer = (ListView) findViewById(R.id.headers_drawer);
516 mDrawer.setAdapter(mHeaderAdapter);
517 mDrawer.setOnItemClickListener(new DrawerItemClickListener());
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000518 mDrawer.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800519
520 mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
521 R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
522 }
523
524 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
525 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
526
527 if (savedInstanceState != null) {
528 // We are restarting from a previous saved state; used that to
529 // initialize, instead of starting fresh.
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800530 ArrayList<Header> headers =
531 savedInstanceState.getParcelableArrayList(SAVE_KEY_HEADERS_TAG);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800532 if (headers != null) {
533 mHeaders.addAll(headers);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800534 int curHeader = savedInstanceState.getInt(SAVE_KEY_CURRENT_HEADER_TAG,
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800535 (int) HEADER_ID_UNDEFINED);
536 if (curHeader >= 0 && curHeader < mHeaders.size()) {
537 setSelectedHeader(mHeaders.get(curHeader));
538 }
539 }
540
541 } else {
542 if (initialFragment != null) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000543 // If we are just showing a fragment, we want to run in
544 // new fragment mode, but don't need to compute and show
545 // the headers.
546 switchToHeader(initialFragment, initialArguments, true);
547
548 final int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
549 if (initialTitle != 0) {
550 setTitle(getText(initialTitle));
551 }
552 } else {
553 // We need to try to build the headers.
554 onBuildHeaders(mHeaders);
555
556 // If there are headers, then at this point we need to show
557 // them and, depending on the screen, we may also show in-line
558 // the currently selected preference fragment.
559 if (mHeaders.size() > 0) {
560 if (initialFragment == null) {
561 Header h = onGetInitialHeader();
562 switchToHeader(h, false);
563 } else {
564 switchToHeader(initialFragment, initialArguments, false);
565 }
566 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800567 }
568 }
569
570 // see if we should show Back/Next buttons
571 Intent intent = getIntent();
572 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
573
574 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
575 if (buttonBar != null) {
576 buttonBar.setVisibility(View.VISIBLE);
577
578 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
579 backButton.setOnClickListener(new OnClickListener() {
580 public void onClick(View v) {
581 setResult(RESULT_CANCELED);
582 finish();
583 }
584 });
585 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
586 skipButton.setOnClickListener(new OnClickListener() {
587 public void onClick(View v) {
588 setResult(RESULT_OK);
589 finish();
590 }
591 });
592 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
593 mNextButton.setOnClickListener(new OnClickListener() {
594 public void onClick(View v) {
595 setResult(RESULT_OK);
596 finish();
597 }
598 });
599
600 // set our various button parameters
601 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
602 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
603 if (TextUtils.isEmpty(buttonText)) {
604 mNextButton.setVisibility(View.GONE);
605 }
606 else {
607 mNextButton.setText(buttonText);
608 }
609 }
610 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
611 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
612 if (TextUtils.isEmpty(buttonText)) {
613 backButton.setVisibility(View.GONE);
614 }
615 else {
616 backButton.setText(buttonText);
617 }
618 }
619 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
620 skipButton.setVisibility(View.VISIBLE);
621 }
622 }
623 }
624
625 if (!onIsHidingHeaders()) {
626 highlightHeader(mTopLevelHeaderId);
627 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800628 }
629
630 @Override
631 public void onBackStackChanged() {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000632 final int count = getFragmentManager().getBackStackEntryCount() + 1;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800633 TitlePair pair = null;
634 int last;
635 while (mTitleStack.size() > count) {
636 last = mTitleStack.size() - 1;
637 pair = mTitleStack.remove(last);
638 }
639 // Check if we go back
640 if (pair != null) {
641 int size = mTitleStack.size();
642 if (size > 0) {
643 last = mTitleStack.size() - 1;
644 pair = mTitleStack.get(last);
645 if (pair != null) {
646 final CharSequence title;
647 if (pair.first > 0) {
648 title = getText(pair.first);
649 } else {
650 title = pair.second;
651 }
652 setTitle(title);
653 }
654 }
655 }
656 }
657
658 /**
659 * Returns the Header list
660 */
661 private List<Header> getHeaders() {
662 return mHeaders;
663 }
664
665 @Override
666 protected void onSaveInstanceState(Bundle outState) {
667 super.onSaveInstanceState(outState);
668
669 if (mHeaders.size() > 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800670 outState.putParcelableArrayList(SAVE_KEY_HEADERS_TAG, mHeaders);
671 if (mCurrentHeader != null) {
672 int index = mHeaders.indexOf(mCurrentHeader);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800673 if (index >= 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800674 outState.putInt(SAVE_KEY_CURRENT_HEADER_TAG, index);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800675 }
676 }
677 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800678 }
679
680 @Override
681 public void onResume() {
682 super.onResume();
683
684 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
685 @Override
686 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
687 invalidateHeaders();
688 }
689 };
690 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
691 mDevelopmentPreferencesListener);
692
693 mHeaderAdapter.resume();
694 invalidateHeaders();
695
696 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800697
698 mDrawerLayout.setDrawerListener(new DrawerListener());
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800699 }
700
701 @Override
702 public void onPause() {
703 super.onPause();
704
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800705 mDrawerLayout.setDrawerListener(null);
706
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800707 unregisterReceiver(mBatteryInfoReceiver);
708
709 mHeaderAdapter.pause();
710
711 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
712 mDevelopmentPreferencesListener);
713
714 mDevelopmentPreferencesListener = null;
715 }
716
717 @Override
718 public void onDestroy() {
719 super.onDestroy();
720 if (mListeningToAccountUpdates) {
721 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
722 }
723 }
724
725 /**
726 * @hide
727 */
728 protected boolean isValidFragment(String fragmentName) {
729 // Almost all fragments are wrapped in this,
730 // except for a few that have their own activities.
731 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
732 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
733 }
734 return false;
735 }
736
737 /**
738 * When in two-pane mode, switch to the fragment pane to show the given
739 * preference fragment.
740 *
741 * @param header The new header to display.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000742 * @param validate true means that the fragment's Header needs to be validated
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800743 */
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000744 private void switchToHeader(Header header, boolean validate) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800745 if (mCurrentHeader == header) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000746 // This is the header we are currently displaying. Just make sure
747 // to pop the stack up to its root state.
748 getFragmentManager().popBackStack(BACK_STACK_PREFS,
749 FragmentManager.POP_BACK_STACK_INCLUSIVE);
750 } else {
751 mTitleStack.clear();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800752 if (header.fragment == null) {
753 throw new IllegalStateException("can't switch to header that has no fragment");
754 }
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000755 switchToHeaderInner(header.fragment, header.fragmentArguments, validate);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800756 setSelectedHeader(header);
757 final CharSequence title;
758 if (header.fragment.equals("com.android.settings.dashboard.DashboardSummary")) {
759 title = getResources().getString(R.string.settings_label);
760 } else {
761 title = header.getTitle(getResources());
762 }
763 final TitlePair pair = new TitlePair(0, title);
764 mTitleStack.add(pair);
765 setTitle(title);
766 }
767 }
768
769 private void setSelectedHeader(Header header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800770 mCurrentHeader = header;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800771 int index = mHeaders.indexOf(header);
772 if (mDrawer != null) {
773 if (index >= 0) {
774 mDrawer.setItemChecked(index, true);
775 } else {
776 mDrawer.clearChoices();
777 }
778 }
779 }
780
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000781 public Header onGetInitialHeader() {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800782 String fragmentClass = getStartingFragmentClass(super.getIntent());
783 if (fragmentClass != null) {
784 Header header = new Header();
785 header.fragment = fragmentClass;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000786 header.title = getTitle();
787 header.fragmentArguments = getIntent().getExtras();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800788 return header;
789 }
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000790
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800791 return mFirstHeader;
792 }
793
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000794 /**
795 * When in two-pane mode, switch the fragment pane to show the given
796 * preference fragment.
797 *
798 * @param fragmentName The name of the fragment to display.
799 * @param args Optional arguments to supply to the fragment.
800 * @param validate true means that the fragment's Header needs to be validated
801 */
802 private void switchToHeader(String fragmentName, Bundle args, boolean validate) {
803 setSelectedHeader(null);
804 switchToHeaderInner(fragmentName, args, validate);
805 }
806
807 private void switchToHeaderInner(String fragmentName, Bundle args, boolean validate) {
808 getFragmentManager().popBackStack(BACK_STACK_PREFS,
809 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800810 if (validate && !isValidFragment(fragmentName)) {
811 throw new IllegalArgumentException("Invalid fragment for this activity: "
812 + fragmentName);
813 }
814 Fragment f = Fragment.instantiate(this, fragmentName, args);
815 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800816 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800817 transaction.replace(R.id.prefs, f);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800818 transaction.commitAllowingStateLoss();
819 }
820
821 @Override
822 public void onNewIntent(Intent intent) {
823 super.onNewIntent(intent);
824
825 // If it is not launched from history, then reset to top-level
826 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
827 if (mDrawer != null) {
828 mDrawer.setSelectionFromTop(0, 0);
829 }
830 }
831 }
832
833 /**
834 * Called to determine whether the header list should be hidden.
835 * The default implementation returns the
836 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
837 * This is set to false, for example, when the activity is being re-launched
838 * to show a particular preference activity.
839 */
840 public boolean onIsHidingHeaders() {
841 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
842 }
843
844 private void highlightHeader(int id) {
845 if (id != 0) {
846 Integer index = mHeaderIndexMap.get(id);
847 if (index != null && mDrawer != null) {
848 mDrawer.setItemChecked(index, true);
849 if (mDrawer.getVisibility() == View.VISIBLE) {
850 mDrawer.smoothScrollToPosition(index);
851 }
852 }
853 }
854 }
855
856 @Override
857 public Intent getIntent() {
858 Intent superIntent = super.getIntent();
859 String startingFragment = getStartingFragmentClass(superIntent);
860 // This is called from super.onCreate, isMultiPane() is not yet reliable
861 // Do not use onIsHidingHeaders either, which relies itself on this method
862 if (startingFragment != null) {
863 Intent modIntent = new Intent(superIntent);
864 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
865 Bundle args = superIntent.getExtras();
866 if (args != null) {
867 args = new Bundle(args);
868 } else {
869 args = new Bundle();
870 }
871 args.putParcelable("intent", superIntent);
872 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
873 return modIntent;
874 }
875 return superIntent;
876 }
877
878 /**
879 * Checks if the component name in the intent is different from the Settings class and
880 * returns the class name to load as a fragment.
881 */
882 private String getStartingFragmentClass(Intent intent) {
883 if (mFragmentClass != null) return mFragmentClass;
884
885 String intentClass = intent.getComponent().getClassName();
886 if (intentClass.equals(getClass().getName())) return null;
887
888 if ("com.android.settings.ManageApplications".equals(intentClass)
889 || "com.android.settings.RunningServices".equals(intentClass)
890 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
891 // Old names of manage apps.
892 intentClass = com.android.settings.applications.ManageApplications.class.getName();
893 }
894
895 return intentClass;
896 }
897
898 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000899 * Start a new fragment containing a preference panel. If the preferences
900 * are being displayed in multi-pane mode, the given fragment class will
901 * be instantiated and placed in the appropriate pane. If running in
902 * single-pane mode, a new activity will be launched in which to show the
903 * fragment.
904 *
905 * @param fragmentClass Full name of the class implementing the fragment.
906 * @param args Any desired arguments to supply to the fragment.
907 * @param titleRes Optional resource identifier of the title of this
908 * fragment.
909 * @param titleText Optional text of the title of this fragment.
910 * @param resultTo Optional fragment that result data should be sent to.
911 * If non-null, resultTo.onActivityResult() will be called when this
912 * preference panel is done. The launched panel must use
913 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
914 * @param resultRequestCode If resultTo is non-null, this is the caller's
915 * request code to be received with the resut.
916 */
917 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
918 CharSequence titleText, Fragment resultTo,
919 int resultRequestCode) {
920 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
921 }
922
923 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800924 * Called by a preference panel fragment to finish itself.
925 *
926 * @param caller The fragment that is asking to be finished.
927 * @param resultCode Optional result code to send back to the original
928 * launching fragment.
929 * @param resultData Optional result data to send back to the original
930 * launching fragment.
931 */
932 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
933 setResult(resultCode, resultData);
934 }
935
936 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000937 * Start a new fragment.
938 *
939 * @param fragment The fragment to start
940 * @param push If true, the current fragment will be pushed onto the back stack. If false,
941 * the current fragment will be replaced.
942 */
943 public void startPreferenceFragment(Fragment fragment, boolean push) {
944 FragmentTransaction transaction = getFragmentManager().beginTransaction();
945 transaction.replace(R.id.prefs, fragment);
946 if (push) {
947 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
948 transaction.addToBackStack(BACK_STACK_PREFS);
949 } else {
950 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
951 }
952 transaction.commitAllowingStateLoss();
953 }
954
955 /**
956 * Start a new fragment.
957 *
958 * @param fragmentName The name of the fragment to display.
959 * @param args Optional arguments to supply to the fragment.
960 * @param resultTo Option fragment that should receive the result of
961 * the activity launch.
962 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
963 * report the result.
964 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
965 * valid one then it will be used to get the title. Otherwise the titleText
966 * argument will be used as the title.
967 * @param titleText string to display for the title of.
968 */
969 private void startWithFragment(String fragmentName, Bundle args, Fragment resultTo,
970 int resultRequestCode, int titleRes, CharSequence titleText) {
971 Fragment f = Fragment.instantiate(this, fragmentName, args);
972 if (resultTo != null) {
973 f.setTargetFragment(resultTo, resultRequestCode);
974 }
975 FragmentTransaction transaction = getFragmentManager().beginTransaction();
976 transaction.replace(R.id.prefs, f);
977 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
978 transaction.addToBackStack(BACK_STACK_PREFS);
979 transaction.commitAllowingStateLoss();
980
981 final TitlePair pair;
982 final CharSequence cs;
983 if (titleRes != 0) {
984 pair = new TitlePair(titleRes, null);
985 cs = getText(titleRes);
986 } else {
987 pair = new TitlePair(0, titleText);
988 cs = titleText;
989 }
990 setTitle(cs);
991 mTitleStack.add(pair);
992 }
993
994 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800995 * Called when the activity needs its list of headers build. By
996 * implementing this and adding at least one item to the list, you
997 * will cause the activity to run in its modern fragment mode. Note
998 * that this function may not always be called; for example, if the
999 * activity has been asked to display a particular fragment without
1000 * the header list, there is no need to build the headers.
1001 *
1002 * <p>Typical implementations will use {@link #loadHeadersFromResource}
1003 * to fill in the list from a resource.
1004 *
1005 * @param headers The list in which to place the headers.
1006 */
1007 private void onBuildHeaders(List<Header> headers) {
1008 loadHeadersFromResource(R.xml.settings_headers, headers);
1009 updateHeaderList(headers);
1010 }
1011
1012 /**
1013 * Parse the given XML file as a header description, adding each
1014 * parsed Header into the target list.
1015 *
1016 * @param resid The XML resource to load and parse.
1017 * @param target The list in which the parsed headers should be placed.
1018 */
1019 private void loadHeadersFromResource(int resid, List<Header> target) {
1020 XmlResourceParser parser = null;
1021 try {
1022 parser = getResources().getXml(resid);
1023 AttributeSet attrs = Xml.asAttributeSet(parser);
1024
1025 int type;
1026 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1027 && type != XmlPullParser.START_TAG) {
1028 // Parse next until start tag is found
1029 }
1030
1031 String nodeName = parser.getName();
1032 if (!"preference-headers".equals(nodeName)) {
1033 throw new RuntimeException(
1034 "XML document must start with <preference-headers> tag; found"
1035 + nodeName + " at " + parser.getPositionDescription());
1036 }
1037
1038 Bundle curBundle = null;
1039
1040 final int outerDepth = parser.getDepth();
1041 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1042 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1043 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1044 continue;
1045 }
1046
1047 nodeName = parser.getName();
1048 if ("header".equals(nodeName)) {
1049 Header header = new Header();
1050
1051 TypedArray sa = obtainStyledAttributes(
1052 attrs, com.android.internal.R.styleable.PreferenceHeader);
1053 header.id = sa.getResourceId(
1054 com.android.internal.R.styleable.PreferenceHeader_id,
1055 (int)HEADER_ID_UNDEFINED);
1056 TypedValue tv = sa.peekValue(
1057 com.android.internal.R.styleable.PreferenceHeader_title);
1058 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1059 if (tv.resourceId != 0) {
1060 header.titleRes = tv.resourceId;
1061 } else {
1062 header.title = tv.string;
1063 }
1064 }
1065 tv = sa.peekValue(
1066 com.android.internal.R.styleable.PreferenceHeader_summary);
1067 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1068 if (tv.resourceId != 0) {
1069 header.summaryRes = tv.resourceId;
1070 } else {
1071 header.summary = tv.string;
1072 }
1073 }
1074 header.iconRes = sa.getResourceId(
1075 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
1076 header.fragment = sa.getString(
1077 com.android.internal.R.styleable.PreferenceHeader_fragment);
1078 sa.recycle();
1079
1080 if (curBundle == null) {
1081 curBundle = new Bundle();
1082 }
1083
1084 final int innerDepth = parser.getDepth();
1085 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1086 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
1087 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1088 continue;
1089 }
1090
1091 String innerNodeName = parser.getName();
1092 if (innerNodeName.equals("extra")) {
1093 getResources().parseBundleExtra("extra", attrs, curBundle);
1094 XmlUtils.skipCurrentTag(parser);
1095
1096 } else if (innerNodeName.equals("intent")) {
1097 header.intent = Intent.parseIntent(getResources(), parser, attrs);
1098
1099 } else {
1100 XmlUtils.skipCurrentTag(parser);
1101 }
1102 }
1103
1104 if (curBundle.size() > 0) {
1105 header.fragmentArguments = curBundle;
1106 curBundle = null;
1107 }
1108
1109 target.add(header);
1110 } else {
1111 XmlUtils.skipCurrentTag(parser);
1112 }
1113 }
1114
1115 } catch (XmlPullParserException e) {
1116 throw new RuntimeException("Error parsing headers", e);
1117 } catch (IOException e) {
1118 throw new RuntimeException("Error parsing headers", e);
1119 } finally {
1120 if (parser != null) parser.close();
1121 }
1122 }
1123
1124 private void updateHeaderList(List<Header> target) {
1125 final boolean showDev = mDevelopmentPreferences.getBoolean(
1126 DevelopmentSettings.PREF_SHOW,
1127 android.os.Build.TYPE.equals("eng"));
1128 int i = 0;
1129
1130 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1131 mHeaderIndexMap.clear();
1132 while (i < target.size()) {
1133 Header header = target.get(i);
1134 // Ids are integers, so downcasting
1135 int id = (int) header.id;
1136 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1137 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1138 } else if (id == R.id.wifi_settings) {
1139 // Remove WiFi Settings if WiFi service is not available.
1140 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1141 target.remove(i);
1142 }
1143 } else if (id == R.id.bluetooth_settings) {
1144 // Remove Bluetooth Settings if Bluetooth service is not available.
1145 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1146 target.remove(i);
1147 }
1148 } else if (id == R.id.data_usage_settings) {
1149 // Remove data usage when kernel module not enabled
1150 final INetworkManagementService netManager = INetworkManagementService.Stub
1151 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1152 try {
1153 if (!netManager.isBandwidthControlEnabled()) {
1154 target.remove(i);
1155 }
1156 } catch (RemoteException e) {
1157 // ignored
1158 }
1159 } else if (id == R.id.battery_settings) {
1160 // Remove battery settings when battery is not available. (e.g. TV)
1161
1162 if (!mBatteryPresent) {
1163 target.remove(i);
1164 }
1165 } else if (id == R.id.account_settings) {
1166 int headerIndex = i + 1;
1167 i = insertAccountsHeaders(target, headerIndex);
1168 } else if (id == R.id.home_settings) {
1169 if (!updateHomeSettingHeaders(header)) {
1170 target.remove(i);
1171 }
1172 } else if (id == R.id.user_settings) {
1173 if (!UserHandle.MU_ENABLED
1174 || !UserManager.supportsMultipleUsers()
1175 || Utils.isMonkeyRunning()) {
1176 target.remove(i);
1177 }
1178 } else if (id == R.id.nfc_payment_settings) {
1179 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1180 target.remove(i);
1181 } else {
1182 // Only show if NFC is on and we have the HCE feature
1183 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1184 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1185 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1186 target.remove(i);
1187 }
1188 }
1189 } else if (id == R.id.development_settings) {
1190 if (!showDev) {
1191 target.remove(i);
1192 }
1193 } else if (id == R.id.account_add) {
1194 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1195 target.remove(i);
1196 }
1197 }
1198
1199 if (i < target.size() && target.get(i) == header
1200 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1201 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1202 target.remove(i);
1203 }
1204
1205 // Increment if the current one wasn't removed by the Utils code.
1206 if (i < target.size() && target.get(i) == header) {
1207 // Hold on to the first header, when we need to reset to the top-level
1208 if (mFirstHeader == null &&
1209 HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
1210 mFirstHeader = header;
1211 }
1212 mHeaderIndexMap.put(id, i);
1213 i++;
1214 }
1215 }
1216 }
1217
1218 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1219 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1220 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1221 for (String accountType : accountTypes) {
1222 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1223 if (label == null) {
1224 continue;
1225 }
1226
1227 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1228 boolean skipToAccount = accounts.length == 1
1229 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1230 Header accHeader = new Header();
1231 accHeader.title = label;
1232 if (accHeader.extras == null) {
1233 accHeader.extras = new Bundle();
1234 }
1235 if (skipToAccount) {
1236 accHeader.fragment = AccountSyncSettings.class.getName();
1237 accHeader.fragmentArguments = new Bundle();
1238 // Need this for the icon
1239 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1240 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1241 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1242 accounts[0]);
1243 } else {
1244 accHeader.fragment = ManageAccountsSettings.class.getName();
1245 accHeader.fragmentArguments = new Bundle();
1246 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1247 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1248 accountType);
1249 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1250 label.toString());
1251 }
1252 accountHeaders.add(accHeader);
1253 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1254 }
1255
1256 // Sort by label
1257 Collections.sort(accountHeaders, new Comparator<Header>() {
1258 @Override
1259 public int compare(Header h1, Header h2) {
1260 return h1.title.toString().compareTo(h2.title.toString());
1261 }
1262 });
1263
1264 for (Header header : accountHeaders) {
1265 target.add(headerIndex++, header);
1266 }
1267 if (!mListeningToAccountUpdates) {
1268 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1269 mListeningToAccountUpdates = true;
1270 }
1271 return headerIndex;
1272 }
1273
1274 private boolean updateHomeSettingHeaders(Header header) {
1275 // Once we decide to show Home settings, keep showing it forever
1276 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1277 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1278 return true;
1279 }
1280
1281 try {
1282 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1283 getPackageManager().getHomeActivities(homeApps);
1284 if (homeApps.size() < 2) {
1285 // When there's only one available home app, omit this settings
1286 // category entirely at the top level UI. If the user just
1287 // uninstalled the penultimate home app candidiate, we also
1288 // now tell them about why they aren't seeing 'Home' in the list.
1289 if (sShowNoHomeNotice) {
1290 sShowNoHomeNotice = false;
1291 NoHomeDialogFragment.show(this);
1292 }
1293 return false;
1294 } else {
1295 // Okay, we're allowing the Home settings category. Tell it, when
1296 // invoked via this front door, that we'll need to be told about the
1297 // case when the user uninstalls all but one home app.
1298 if (header.fragmentArguments == null) {
1299 header.fragmentArguments = new Bundle();
1300 }
1301 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1302 }
1303 } catch (Exception e) {
1304 // Can't look up the home activity; bail on configuring the icon
1305 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1306 }
1307
1308 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1309 return true;
1310 }
1311
1312 private void getMetaData() {
1313 try {
1314 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1315 PackageManager.GET_META_DATA);
1316 if (ai == null || ai.metaData == null) return;
1317 mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
1318 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1319 } catch (NameNotFoundException nnfe) {
1320 // No recovery
1321 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1322 }
1323 }
1324
1325 // give subclasses access to the Next button
1326 public boolean hasNextButton() {
1327 return mNextButton != null;
1328 }
1329
1330 public Button getNextButton() {
1331 return mNextButton;
1332 }
1333
1334 public static class NoHomeDialogFragment extends DialogFragment {
1335 public static void show(Activity parent) {
1336 final NoHomeDialogFragment dialog = new NoHomeDialogFragment();
1337 dialog.show(parent.getFragmentManager(), null);
1338 }
1339
1340 @Override
1341 public Dialog onCreateDialog(Bundle savedInstanceState) {
1342 return new AlertDialog.Builder(getActivity())
1343 .setMessage(R.string.only_one_home_message)
1344 .setPositiveButton(android.R.string.ok, null)
1345 .create();
1346 }
1347 }
1348
1349 private static class HeaderAdapter extends ArrayAdapter<Header> {
1350 static final int HEADER_TYPE_CATEGORY = 0;
1351 static final int HEADER_TYPE_NORMAL = 1;
1352 static final int HEADER_TYPE_SWITCH = 2;
1353 static final int HEADER_TYPE_BUTTON = 3;
1354 private static final int HEADER_TYPE_COUNT = HEADER_TYPE_BUTTON + 1;
1355
1356 private final WifiEnabler mWifiEnabler;
1357 private final BluetoothEnabler mBluetoothEnabler;
1358 private AuthenticatorHelper mAuthHelper;
1359 private DevicePolicyManager mDevicePolicyManager;
1360
1361 private static class HeaderViewHolder {
1362 ImageView mIcon;
1363 TextView mTitle;
1364 TextView mSummary;
1365 Switch mSwitch;
1366 ImageButton mButton;
1367 View mDivider;
1368 }
1369
1370 private LayoutInflater mInflater;
1371
1372 static int getHeaderType(Header header) {
1373 if (header.fragment == null && header.intent == null) {
1374 return HEADER_TYPE_CATEGORY;
1375 } else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings) {
1376 return HEADER_TYPE_SWITCH;
1377 } else if (header.id == R.id.security_settings) {
1378 return HEADER_TYPE_BUTTON;
1379 } else {
1380 return HEADER_TYPE_NORMAL;
1381 }
1382 }
1383
1384 @Override
1385 public int getItemViewType(int position) {
1386 Header header = getItem(position);
1387 return getHeaderType(header);
1388 }
1389
1390 @Override
1391 public boolean areAllItemsEnabled() {
1392 return false; // because of categories
1393 }
1394
1395 @Override
1396 public boolean isEnabled(int position) {
1397 return getItemViewType(position) != HEADER_TYPE_CATEGORY;
1398 }
1399
1400 @Override
1401 public int getViewTypeCount() {
1402 return HEADER_TYPE_COUNT;
1403 }
1404
1405 @Override
1406 public boolean hasStableIds() {
1407 return true;
1408 }
1409
1410 public HeaderAdapter(Context context, List<Header> objects,
1411 AuthenticatorHelper authenticatorHelper, DevicePolicyManager dpm) {
1412 super(context, 0, objects);
1413
1414 mAuthHelper = authenticatorHelper;
1415 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1416
1417 // Temp Switches provided as placeholder until the adapter replaces these with actual
1418 // Switches inflated from their layouts. Must be done before adapter is set in super
1419 mWifiEnabler = new WifiEnabler(context, new Switch(context));
1420 mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
1421 mDevicePolicyManager = dpm;
1422 }
1423
1424 @Override
1425 public View getView(int position, View convertView, ViewGroup parent) {
1426 HeaderViewHolder holder;
1427 Header header = getItem(position);
1428 int headerType = getHeaderType(header);
1429 View view = null;
1430
1431 if (convertView == null) {
1432 holder = new HeaderViewHolder();
1433 switch (headerType) {
1434 case HEADER_TYPE_CATEGORY:
1435 view = new TextView(getContext(), null,
1436 android.R.attr.listSeparatorTextViewStyle);
1437 holder.mTitle = (TextView) view;
1438 break;
1439
1440 case HEADER_TYPE_SWITCH:
1441 view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
1442 false);
1443 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1444 holder.mTitle = (TextView)
1445 view.findViewById(com.android.internal.R.id.title);
1446 holder.mSummary = (TextView)
1447 view.findViewById(com.android.internal.R.id.summary);
1448 holder.mSwitch = (Switch) view.findViewById(R.id.switchWidget);
1449 break;
1450
1451 case HEADER_TYPE_BUTTON:
1452 view = mInflater.inflate(R.layout.preference_header_button_item, parent,
1453 false);
1454 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1455 holder.mTitle = (TextView)
1456 view.findViewById(com.android.internal.R.id.title);
1457 holder.mSummary = (TextView)
1458 view.findViewById(com.android.internal.R.id.summary);
1459 holder.mButton = (ImageButton) view.findViewById(R.id.buttonWidget);
1460 holder.mDivider = view.findViewById(R.id.divider);
1461 break;
1462
1463 case HEADER_TYPE_NORMAL:
1464 view = mInflater.inflate(
1465 R.layout.preference_header_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 break;
1473 }
1474 view.setTag(holder);
1475 } else {
1476 view = convertView;
1477 holder = (HeaderViewHolder) view.getTag();
1478 }
1479
1480 // All view fields must be updated every time, because the view may be recycled
1481 switch (headerType) {
1482 case HEADER_TYPE_CATEGORY:
1483 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1484 break;
1485
1486 case HEADER_TYPE_SWITCH:
1487 // Would need a different treatment if the main menu had more switches
1488 if (header.id == R.id.wifi_settings) {
1489 mWifiEnabler.setSwitch(holder.mSwitch);
1490 } else {
1491 mBluetoothEnabler.setSwitch(holder.mSwitch);
1492 }
1493 updateCommonHeaderView(header, holder);
1494 break;
1495
1496 case HEADER_TYPE_BUTTON:
1497 if (header.id == R.id.security_settings) {
1498 boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled();
1499 if (hasCert) {
1500 holder.mButton.setVisibility(View.VISIBLE);
1501 holder.mDivider.setVisibility(View.VISIBLE);
1502 boolean isManaged = mDevicePolicyManager.getDeviceOwner() != null;
1503 if (isManaged) {
1504 holder.mButton.setImageResource(R.drawable.ic_settings_about);
1505 } else {
1506 holder.mButton.setImageResource(
1507 android.R.drawable.stat_notify_error);
1508 }
1509 holder.mButton.setOnClickListener(new OnClickListener() {
1510 @Override
1511 public void onClick(View v) {
1512 Intent intent = new Intent(
1513 android.provider.Settings.ACTION_MONITORING_CERT_INFO);
1514 getContext().startActivity(intent);
1515 }
1516 });
1517 } else {
1518 holder.mButton.setVisibility(View.GONE);
1519 holder.mDivider.setVisibility(View.GONE);
1520 }
1521 }
1522 updateCommonHeaderView(header, holder);
1523 break;
1524
1525 case HEADER_TYPE_NORMAL:
1526 updateCommonHeaderView(header, holder);
1527 break;
1528 }
1529
1530 return view;
1531 }
1532
1533 private void updateCommonHeaderView(Header header, HeaderViewHolder holder) {
1534 if (header.extras != null
1535 && header.extras.containsKey(ManageAccountsSettings.KEY_ACCOUNT_TYPE)) {
1536 String accType = header.extras.getString(
1537 ManageAccountsSettings.KEY_ACCOUNT_TYPE);
1538 Drawable icon = mAuthHelper.getDrawableForType(getContext(), accType);
1539 setHeaderIcon(holder, icon);
1540 } else {
1541 holder.mIcon.setImageResource(header.iconRes);
1542 }
1543 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1544 CharSequence summary = header.getSummary(getContext().getResources());
1545 if (!TextUtils.isEmpty(summary)) {
1546 holder.mSummary.setVisibility(View.VISIBLE);
1547 holder.mSummary.setText(summary);
1548 } else {
1549 holder.mSummary.setVisibility(View.GONE);
1550 }
1551 }
1552
1553 private void setHeaderIcon(HeaderViewHolder holder, Drawable icon) {
1554 ViewGroup.LayoutParams lp = holder.mIcon.getLayoutParams();
1555 lp.width = getContext().getResources().getDimensionPixelSize(
1556 R.dimen.header_icon_width);
1557 lp.height = lp.width;
1558 holder.mIcon.setLayoutParams(lp);
1559 holder.mIcon.setImageDrawable(icon);
1560 }
1561
1562 public void resume() {
1563 mWifiEnabler.resume();
1564 mBluetoothEnabler.resume();
1565 }
1566
1567 public void pause() {
1568 mWifiEnabler.pause();
1569 mBluetoothEnabler.pause();
1570 }
1571 }
1572
1573 private void onListItemClick(ListView l, View v, int position, long id) {
1574 if (!isResumed()) {
1575 return;
1576 }
1577 Object item = mHeaderAdapter.getItem(position);
1578 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001579 mSelectedHeader = (Header) item;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001580 }
1581 }
1582
1583 /**
1584 * Called when the user selects an item in the header list. The default
1585 * implementation will call either
Fabrice Di Megliodc77b732014-02-04 12:41:30 -08001586 * {@link #startWithFragment(String, android.os.Bundle, android.app.Fragment, int, int, CharSequence)}
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001587 * or {@link #switchToHeader(com.android.settings.SettingsActivity.Header, boolean)}
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001588 * as appropriate.
1589 *
1590 * @param header The header that was selected.
1591 */
1592 private void onHeaderClick(Header header) {
1593 if (header == null) return;
1594 if (header.fragment != null) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001595 switchToHeader(header, false);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001596 } else if (header.intent != null) {
1597 startActivity(header.intent);
1598 }
1599 }
1600
1601 @Override
1602 public boolean shouldUpRecreateTask(Intent targetIntent) {
1603 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1604 }
1605
1606 @Override
1607 public void onAccountsUpdated(Account[] accounts) {
1608 // TODO: watch for package upgrades to invalidate cache; see 7206643
1609 mAuthenticatorHelper.updateAuthDescriptions(this);
1610 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1611 invalidateHeaders();
1612 }
1613
1614 public static void requestHomeNotice() {
1615 sShowNoHomeNotice = true;
1616 }
1617
1618 /**
1619 * Default value for {@link Header#id Header.id} indicating that no
1620 * identifier value is set. All other values (including those below -1)
1621 * are valid.
1622 */
1623 private static final long HEADER_ID_UNDEFINED = -1;
1624
1625 /**
1626 * Description of a single Header item that the user can select.
1627 */
1628 static final class Header implements Parcelable {
1629 /**
1630 * Identifier for this header, to correlate with a new list when
1631 * it is updated. The default value is
1632 * {@link SettingsActivity#HEADER_ID_UNDEFINED}, meaning no id.
1633 * @attr ref android.R.styleable#PreferenceHeader_id
1634 */
1635 public long id = HEADER_ID_UNDEFINED;
1636
1637 /**
1638 * Resource ID of title of the header that is shown to the user.
1639 * @attr ref android.R.styleable#PreferenceHeader_title
1640 */
1641 public int titleRes;
1642
1643 /**
1644 * Title of the header that is shown to the user.
1645 * @attr ref android.R.styleable#PreferenceHeader_title
1646 */
1647 public CharSequence title;
1648
1649 /**
1650 * Resource ID of optional summary describing what this header controls.
1651 * @attr ref android.R.styleable#PreferenceHeader_summary
1652 */
1653 public int summaryRes;
1654
1655 /**
1656 * Optional summary describing what this header controls.
1657 * @attr ref android.R.styleable#PreferenceHeader_summary
1658 */
1659 public CharSequence summary;
1660
1661 /**
1662 * Optional icon resource to show for this header.
1663 * @attr ref android.R.styleable#PreferenceHeader_icon
1664 */
1665 public int iconRes;
1666
1667 /**
1668 * Full class name of the fragment to display when this header is
1669 * selected.
1670 * @attr ref android.R.styleable#PreferenceHeader_fragment
1671 */
1672 public String fragment;
1673
1674 /**
1675 * Optional arguments to supply to the fragment when it is
1676 * instantiated.
1677 */
1678 public Bundle fragmentArguments;
1679
1680 /**
1681 * Intent to launch when the preference is selected.
1682 */
1683 public Intent intent;
1684
1685 /**
1686 * Optional additional data for use by subclasses of the activity
1687 */
1688 public Bundle extras;
1689
1690 public Header() {
1691 // Empty
1692 }
1693
1694 /**
1695 * Return the currently set title. If {@link #titleRes} is set,
1696 * this resource is loaded from <var>res</var> and returned. Otherwise
1697 * {@link #title} is returned.
1698 */
1699 public CharSequence getTitle(Resources res) {
1700 if (titleRes != 0) {
1701 return res.getText(titleRes);
1702 }
1703 return title;
1704 }
1705
1706 /**
1707 * Return the currently set summary. If {@link #summaryRes} is set,
1708 * this resource is loaded from <var>res</var> and returned. Otherwise
1709 * {@link #summary} is returned.
1710 */
1711 public CharSequence getSummary(Resources res) {
1712 if (summaryRes != 0) {
1713 return res.getText(summaryRes);
1714 }
1715 return summary;
1716 }
1717
1718 @Override
1719 public int describeContents() {
1720 return 0;
1721 }
1722
1723 @Override
1724 public void writeToParcel(Parcel dest, int flags) {
1725 dest.writeLong(id);
1726 dest.writeInt(titleRes);
1727 TextUtils.writeToParcel(title, dest, flags);
1728 dest.writeInt(summaryRes);
1729 TextUtils.writeToParcel(summary, dest, flags);
1730 dest.writeInt(iconRes);
1731 dest.writeString(fragment);
1732 dest.writeBundle(fragmentArguments);
1733 if (intent != null) {
1734 dest.writeInt(1);
1735 intent.writeToParcel(dest, flags);
1736 } else {
1737 dest.writeInt(0);
1738 }
1739 dest.writeBundle(extras);
1740 }
1741
1742 public void readFromParcel(Parcel in) {
1743 id = in.readLong();
1744 titleRes = in.readInt();
1745 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1746 summaryRes = in.readInt();
1747 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1748 iconRes = in.readInt();
1749 fragment = in.readString();
1750 fragmentArguments = in.readBundle();
1751 if (in.readInt() != 0) {
1752 intent = Intent.CREATOR.createFromParcel(in);
1753 }
1754 extras = in.readBundle();
1755 }
1756
1757 Header(Parcel in) {
1758 readFromParcel(in);
1759 }
1760
1761 public static final Creator<Header> CREATOR = new Creator<Header>() {
1762 public Header createFromParcel(Parcel source) {
1763 return new Header(source);
1764 }
1765 public Header[] newArray(int size) {
1766 return new Header[size];
1767 }
1768 };
1769 }
1770}