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