blob: 378a8e4cd07d7544fa479cf3be9f0c72536acb78 [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;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080027import android.app.admin.DevicePolicyManager;
28import android.content.BroadcastReceiver;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070029import android.content.ComponentName;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080030import android.content.Context;
31import android.content.Intent;
32import android.content.IntentFilter;
33import android.content.SharedPreferences;
34import android.content.pm.ActivityInfo;
35import android.content.pm.PackageManager;
36import android.content.pm.PackageManager.NameNotFoundException;
37import android.content.pm.ResolveInfo;
38import android.content.res.Configuration;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080039import android.content.res.TypedArray;
40import android.content.res.XmlResourceParser;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080041import android.nfc.NfcAdapter;
42import android.os.Bundle;
43import android.os.Handler;
44import android.os.INetworkManagementService;
45import android.os.Message;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080046import android.os.RemoteException;
47import android.os.ServiceManager;
48import android.os.UserHandle;
49import android.os.UserManager;
50import android.preference.Preference;
51import android.preference.PreferenceFragment;
52import android.preference.PreferenceManager;
53import android.preference.PreferenceScreen;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080054import android.text.TextUtils;
55import android.util.AttributeSet;
56import android.util.Log;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080057import android.util.TypedValue;
58import android.util.Xml;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070059import android.view.Menu;
60import android.view.MenuInflater;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080061import android.view.MenuItem;
62import android.view.View;
63import android.view.View.OnClickListener;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080064import android.widget.Button;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080065import android.widget.ListView;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080066
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070067import android.widget.SearchView;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080068import com.android.internal.util.ArrayUtils;
69import com.android.internal.util.XmlUtils;
70import com.android.settings.accessibility.AccessibilitySettings;
71import com.android.settings.accessibility.CaptionPropertiesFragment;
72import com.android.settings.accounts.AccountSyncSettings;
73import com.android.settings.accounts.AuthenticatorHelper;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080074import com.android.settings.accounts.ManageAccountsSettings;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070075import com.android.settings.applications.InstalledAppDetails;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080076import com.android.settings.applications.ManageApplications;
77import com.android.settings.applications.ProcessStatsUi;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080078import com.android.settings.bluetooth.BluetoothSettings;
79import com.android.settings.dashboard.DashboardSummary;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070080import com.android.settings.dashboard.Header;
81import com.android.settings.dashboard.HeaderAdapter;
82import com.android.settings.dashboard.NoHomeDialogFragment;
83import com.android.settings.dashboard.SearchResultsSummary;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080084import com.android.settings.deviceinfo.Memory;
85import com.android.settings.deviceinfo.UsbSettings;
86import com.android.settings.fuelgauge.PowerUsageSummary;
Fabrice Di Megliofa7dc242014-03-12 19:24:43 -070087import com.android.settings.search.Index;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080088import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
89import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
90import com.android.settings.inputmethod.SpellCheckersSettings;
91import com.android.settings.inputmethod.UserDictionaryList;
92import com.android.settings.location.LocationSettings;
93import com.android.settings.nfc.AndroidBeam;
94import com.android.settings.nfc.PaymentSettings;
95import com.android.settings.print.PrintJobSettingsFragment;
96import com.android.settings.print.PrintSettingsFragment;
97import com.android.settings.tts.TextToSpeechSettings;
98import com.android.settings.users.UserSettings;
99import com.android.settings.vpn2.VpnSettings;
100import com.android.settings.wfd.WifiDisplaySettings;
101import com.android.settings.wifi.AdvancedWifiSettings;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800102import com.android.settings.wifi.WifiSettings;
103import com.android.settings.wifi.p2p.WifiP2pSettings;
104import org.xmlpull.v1.XmlPullParser;
105import org.xmlpull.v1.XmlPullParserException;
106
107import java.io.IOException;
108import java.util.ArrayList;
109import java.util.Collections;
110import java.util.Comparator;
111import java.util.HashMap;
112import java.util.List;
113
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700114import static com.android.settings.dashboard.Header.HEADER_ID_UNDEFINED;
115
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800116public class SettingsActivity extends Activity
117 implements PreferenceManager.OnPreferenceTreeClickListener,
118 PreferenceFragment.OnPreferenceStartFragmentCallback,
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700119 ButtonBarHandler, OnAccountsUpdateListener, FragmentManager.OnBackStackChangedListener,
120 SearchView.OnQueryTextListener, SearchView.OnCloseListener,
121 MenuItem.OnActionExpandListener {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800122
123 private static final String LOG_TAG = "Settings";
124
125 // Constants for state save/restore
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700126 private static final String SAVE_KEY_HEADERS = ":settings:headers";
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700127 private static final String SAVE_KEY_SEARCH_MENU_EXPANDED = ":settings:search_menu_expanded";
128 private static final String SAVE_KEY_SEARCH_QUERY = ":settings:search_query";
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700129 private static final String SAVE_KEY_SHOW_HOME_AS_UP = ":settings:show_home_as_up";
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800130
131 /**
132 * When starting this activity, the invoking Intent can contain this extra
133 * string to specify which fragment should be initially displayed.
134 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
135 * will call isValidFragment() to confirm that the fragment class name is valid for this
136 * activity.
137 */
138 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
139
140 /**
141 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
142 * this extra can also be specified to supply a Bundle of arguments to pass
143 * to that fragment when it is instantiated during the initial creation
144 * of the activity.
145 */
146 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
147
148 /**
149 * When starting this activity, the invoking Intent can contain this extra
150 * boolean that the header list should not be displayed. This is most often
151 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
152 * the activity to display a specific fragment that the user has navigated
153 * to.
154 */
155 public static final String EXTRA_NO_HEADERS = ":settings:no_headers";
156
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800157 public static final String BACK_STACK_PREFS = ":settings:prefs";
158
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800159 // extras that allow any preference activity to be launched as part of a wizard
160
161 // show Back and Next buttons? takes boolean parameter
162 // Back will then return RESULT_CANCELED and Next RESULT_OK
163 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
164
165 // add a Skip button?
166 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
167
168 // specify custom text for the Back or Next buttons, or cause a button to not appear
169 // at all by setting it to null
170 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
171 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
172
173 /**
174 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
175 * this extra can also be specify to supply the title to be shown for
176 * that fragment.
177 */
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700178 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800179
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800180 private static final String META_DATA_KEY_FRAGMENT_CLASS =
181 "com.android.settings.FRAGMENT_CLASS";
182
183 private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
184
Fabrice Di Megliod6985df2014-04-03 16:43:26 -0700185 private static final String EMPTY_QUERY = "";
186
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800187 private static boolean sShowNoHomeNotice = false;
188
189 private String mFragmentClass;
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800190 private Header mSelectedHeader;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800191
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800192 private CharSequence mInitialTitle;
193
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800194 // Show only these settings for restricted users
195 private int[] SETTINGS_FOR_RESTRICTED = {
196 R.id.wireless_section,
197 R.id.wifi_settings,
198 R.id.bluetooth_settings,
199 R.id.data_usage_settings,
200 R.id.wireless_settings,
201 R.id.device_section,
202 R.id.sound_settings,
203 R.id.display_settings,
204 R.id.storage_settings,
205 R.id.application_settings,
206 R.id.battery_settings,
207 R.id.personal_section,
208 R.id.location_settings,
209 R.id.security_settings,
210 R.id.language_settings,
211 R.id.user_settings,
212 R.id.account_settings,
213 R.id.account_add,
214 R.id.system_section,
215 R.id.date_time_settings,
216 R.id.about_settings,
217 R.id.accessibility_settings,
218 R.id.print_settings,
219 R.id.nfc_payment_settings,
Fabrice Di Meglio2858b792014-02-18 19:35:08 -0800220 R.id.home_settings,
221 R.id.dashboard
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800222 };
223
224 private static final String[] ENTRY_FRAGMENTS = {
225 WirelessSettings.class.getName(),
226 WifiSettings.class.getName(),
227 AdvancedWifiSettings.class.getName(),
228 BluetoothSettings.class.getName(),
229 TetherSettings.class.getName(),
230 WifiP2pSettings.class.getName(),
231 VpnSettings.class.getName(),
232 DateTimeSettings.class.getName(),
233 LocalePicker.class.getName(),
234 InputMethodAndLanguageSettings.class.getName(),
235 SpellCheckersSettings.class.getName(),
236 UserDictionaryList.class.getName(),
237 UserDictionarySettings.class.getName(),
238 SoundSettings.class.getName(),
239 DisplaySettings.class.getName(),
240 DeviceInfoSettings.class.getName(),
241 ManageApplications.class.getName(),
242 ProcessStatsUi.class.getName(),
243 NotificationStation.class.getName(),
244 LocationSettings.class.getName(),
245 SecuritySettings.class.getName(),
246 PrivacySettings.class.getName(),
247 DeviceAdminSettings.class.getName(),
248 AccessibilitySettings.class.getName(),
249 CaptionPropertiesFragment.class.getName(),
250 com.android.settings.accessibility.ToggleInversionPreferenceFragment.class.getName(),
251 com.android.settings.accessibility.ToggleContrastPreferenceFragment.class.getName(),
252 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
253 TextToSpeechSettings.class.getName(),
254 Memory.class.getName(),
255 DevelopmentSettings.class.getName(),
256 UsbSettings.class.getName(),
257 AndroidBeam.class.getName(),
258 WifiDisplaySettings.class.getName(),
259 PowerUsageSummary.class.getName(),
260 AccountSyncSettings.class.getName(),
261 CryptKeeperSettings.class.getName(),
262 DataUsageSummary.class.getName(),
263 DreamSettings.class.getName(),
264 UserSettings.class.getName(),
265 NotificationAccessSettings.class.getName(),
266 ManageAccountsSettings.class.getName(),
267 PrintSettingsFragment.class.getName(),
268 PrintJobSettingsFragment.class.getName(),
269 TrustedCredentialsSettings.class.getName(),
270 PaymentSettings.class.getName(),
271 KeyboardLayoutPickerFragment.class.getName(),
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700272 ZenModeSettings.class.getName(),
273 NotificationSettings.class.getName(),
274 ChooseLockPassword.ChooseLockPasswordFragment.class.getName(),
275 ChooseLockPattern.ChooseLockPatternFragment.class.getName(),
276 InstalledAppDetails.class.getName()
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800277 };
278
279 private SharedPreferences mDevelopmentPreferences;
280 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
281
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800282 private AuthenticatorHelper mAuthenticatorHelper;
283 private boolean mListeningToAccountUpdates;
284
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800285 private boolean mBatteryPresent = true;
286 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
287
288 @Override
289 public void onReceive(Context context, Intent intent) {
290 String action = intent.getAction();
291 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
292 boolean batteryPresent = Utils.isBatteryPresent(intent);
293
294 if (mBatteryPresent != batteryPresent) {
295 mBatteryPresent = batteryPresent;
296 invalidateHeaders();
297 }
298 }
299 }
300 };
301
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700302 private Button mNextButton;
303 private ActionBar mActionBar;
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700304 private boolean mDisplayHomeAsUpEnabled;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700305
306 private SearchView mSearchView;
307 private MenuItem mSearchMenuItem;
308 private boolean mSearchMenuItemExpanded = false;
309 private boolean mIsShowingSearchResults = false;
310 private SearchResultsSummary mSearchResultsFragment;
311 private String mSearchQuery;
312
313 // Headers
314 protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800315 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800316 private HeaderAdapter mHeaderAdapter;
317
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800318 private static final int MSG_BUILD_HEADERS = 1;
319 private Handler mHandler = new Handler() {
320 @Override
321 public void handleMessage(Message msg) {
322 switch (msg.what) {
323 case MSG_BUILD_HEADERS: {
324 mHeaders.clear();
325 onBuildHeaders(mHeaders);
326 mHeaderAdapter.notifyDataSetChanged();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800327 } break;
328 }
329 }
330 };
331
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700332 private boolean mNeedToRevertToInitialFragment = false;
333
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800334 @Override
335 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
336 // Override the fragment title for Wallpaper settings
337 int titleRes = pref.getTitleRes();
338 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
339 titleRes = R.string.wallpaper_settings_fragment_title;
340 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
341 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
342 if (UserManager.get(this).isLinkedUser()) {
343 titleRes = R.string.profile_info_settings_title;
344 } else {
345 titleRes = R.string.user_info_settings_title;
346 }
347 }
348 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
349 null, 0);
350 return true;
351 }
352
353 @Override
354 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
355 return false;
356 }
357
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800358 private void invalidateHeaders() {
359 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
360 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
361 }
362 }
363
364 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800365 public void onConfigurationChanged(Configuration newConfig) {
366 super.onConfigurationChanged(newConfig);
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800367 Index.getInstance(this).update();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800368 }
369
370 @Override
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700371 protected void onStart() {
372 super.onStart();
373
374 if (mNeedToRevertToInitialFragment) {
375 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800376 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800377 }
378
379 @Override
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700380 public boolean onCreateOptionsMenu(Menu menu) {
381 MenuInflater inflater = getMenuInflater();
382 inflater.inflate(R.menu.options_menu, menu);
383
384 // Cache the search query (can be overriden by the OnQueryTextListener)
385 final String query = mSearchQuery;
386
Fabrice Di Meglio95937822014-03-31 19:46:42 -0700387 mSearchMenuItem = menu.findItem(R.id.search);
388 mSearchView = (SearchView) mSearchMenuItem.getActionView();
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700389
Fabrice Di Meglio95937822014-03-31 19:46:42 -0700390 mSearchMenuItem.setOnActionExpandListener(this);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700391 mSearchView.setOnQueryTextListener(this);
392 mSearchView.setOnCloseListener(this);
393
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700394 if (mSearchMenuItemExpanded) {
395 mSearchMenuItem.expandActionView();
396 }
397 mSearchView.setQuery(query, true /* submit */);
398
399 return true;
400 }
401
402 @Override
403 protected void onCreate(Bundle savedState) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800404 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
405 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
406 }
407
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800408 Index.getInstance(this).update();
409
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800410 mAuthenticatorHelper = new AuthenticatorHelper();
411 mAuthenticatorHelper.updateAuthDescriptions(this);
412 mAuthenticatorHelper.onAccountsUpdated(this, null);
413
414 DevicePolicyManager dpm =
415 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
Fabrice Di Megliodca28062014-02-21 17:42:56 -0800416
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700417 mHeaderAdapter = new HeaderAdapter(this, mHeaders, mAuthenticatorHelper, dpm);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800418
419 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
420 Context.MODE_PRIVATE);
421
422 getMetaData();
423
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700424 super.onCreate(savedState);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800425
426 setContentView(R.layout.settings_main);
427
428 getFragmentManager().addOnBackStackChangedListener(this);
429
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700430 mDisplayHomeAsUpEnabled = true;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800431
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700432 String initialFragmentName = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
433 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800434
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700435 if (savedState != null) {
436 mSearchMenuItemExpanded = savedState.getBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED);
437 mSearchQuery = savedState.getString(SAVE_KEY_SEARCH_QUERY);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800438
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700439 // We are restarting from a previous saved state; used that to
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800440 // initialize, instead of starting fresh.
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800441 mInitialTitle = getTitle();
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800442
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700443 ArrayList<Header> headers = savedState.getParcelableArrayList(SAVE_KEY_HEADERS);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800444 if (headers != null) {
445 mHeaders.addAll(headers);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700446 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800447 }
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700448
449 mDisplayHomeAsUpEnabled = savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800450 } else {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800451 // We need to build the Headers in all cases
452 onBuildHeaders(mHeaders);
453
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700454 if (initialFragmentName != null) {
455 final ComponentName cn = getIntent().getComponent();
456 // No UP is we are launched thru a Settings shortcut
457 if (!cn.getClassName().equals(SubSettings.class.getName())) {
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700458 mDisplayHomeAsUpEnabled = false;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700459 }
460 final String initialTitle = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
461 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800462 setTitle(mInitialTitle);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700463 switchToFragment( initialFragmentName, initialArguments, true, false,
464 mInitialTitle, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000465 } else {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700466 // No UP if we are displaying the Headers
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700467 mDisplayHomeAsUpEnabled = false;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000468 if (mHeaders.size() > 0) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700469 mInitialTitle = getText(R.string.dashboard_title);
470 switchToFragment(DashboardSummary.class.getName(), null, false, false,
471 mInitialTitle, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000472 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800473 }
474 }
475
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700476 mActionBar = getActionBar();
477 mActionBar.setHomeButtonEnabled(true);
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700478 mActionBar.setDisplayHomeAsUpEnabled(mDisplayHomeAsUpEnabled);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700479
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800480 // see if we should show Back/Next buttons
481 Intent intent = getIntent();
482 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
483
484 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
485 if (buttonBar != null) {
486 buttonBar.setVisibility(View.VISIBLE);
487
488 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
489 backButton.setOnClickListener(new OnClickListener() {
490 public void onClick(View v) {
491 setResult(RESULT_CANCELED);
492 finish();
493 }
494 });
495 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
496 skipButton.setOnClickListener(new OnClickListener() {
497 public void onClick(View v) {
498 setResult(RESULT_OK);
499 finish();
500 }
501 });
502 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
503 mNextButton.setOnClickListener(new OnClickListener() {
504 public void onClick(View v) {
505 setResult(RESULT_OK);
506 finish();
507 }
508 });
509
510 // set our various button parameters
511 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
512 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
513 if (TextUtils.isEmpty(buttonText)) {
514 mNextButton.setVisibility(View.GONE);
515 }
516 else {
517 mNextButton.setText(buttonText);
518 }
519 }
520 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
521 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
522 if (TextUtils.isEmpty(buttonText)) {
523 backButton.setVisibility(View.GONE);
524 }
525 else {
526 backButton.setText(buttonText);
527 }
528 }
529 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
530 skipButton.setVisibility(View.VISIBLE);
531 }
532 }
533 }
Fabrice Di Meglioc95be4f2014-03-07 12:57:38 -0800534 }
535
536 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800537 public void onBackStackChanged() {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700538 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800539 }
540
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700541 private int setTitleFromBackStack() {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800542 final int count = getFragmentManager().getBackStackEntryCount();
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700543
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800544 if (count == 0) {
545 setTitle(mInitialTitle);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700546 return 0;
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800547 }
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700548
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800549 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1);
550 setTitleFromBackStackEntry(bse);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700551
552 return count;
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800553 }
554
555 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
556 final CharSequence title;
557 final int titleRes = bse.getBreadCrumbTitleRes();
558 if (titleRes > 0) {
559 title = getText(titleRes);
560 } else {
561 title = bse.getBreadCrumbTitle();
562 }
563 if (title != null) {
564 setTitle(title);
565 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800566 }
567
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800568 @Override
569 protected void onSaveInstanceState(Bundle outState) {
570 super.onSaveInstanceState(outState);
571
572 if (mHeaders.size() > 0) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700573 outState.putParcelableArrayList(SAVE_KEY_HEADERS, mHeaders);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800574 }
Fabrice Di Megliod6985df2014-04-03 16:43:26 -0700575
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700576 outState.putBoolean(SAVE_KEY_SHOW_HOME_AS_UP, mDisplayHomeAsUpEnabled);
577
Fabrice Di Megliod6985df2014-04-03 16:43:26 -0700578 // The option menus are created if the ActionBar is visible and they are also created
579 // asynchronously. If you launch Settings with an Intent action like
580 // android.intent.action.POWER_USAGE_SUMMARY and at the same time your device is locked
581 // thru a LockScreen, onCreateOptionsMenu() is not yet called and references to the search
582 // menu item and search view are null.
583 boolean isExpanded = (mSearchMenuItem != null) && mSearchMenuItem.isActionViewExpanded();
584 outState.putBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED, isExpanded);
585
586 String query = (mSearchView != null) ? mSearchView.getQuery().toString() : EMPTY_QUERY;
587 outState.putString(SAVE_KEY_SEARCH_QUERY, query);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800588 }
589
590 @Override
591 public void onResume() {
592 super.onResume();
593
594 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
595 @Override
596 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
597 invalidateHeaders();
598 }
599 };
600 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
601 mDevelopmentPreferencesListener);
602
Matthew Xiea504c4d2014-02-14 16:32:32 -0800603 mHeaderAdapter.resume(this);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800604 invalidateHeaders();
605
606 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
607 }
608
609 @Override
610 public void onPause() {
611 super.onPause();
612
613 unregisterReceiver(mBatteryInfoReceiver);
614
615 mHeaderAdapter.pause();
616
617 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
618 mDevelopmentPreferencesListener);
619
620 mDevelopmentPreferencesListener = null;
621 }
622
623 @Override
624 public void onDestroy() {
625 super.onDestroy();
626 if (mListeningToAccountUpdates) {
627 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
628 }
629 }
630
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800631 protected boolean isValidFragment(String fragmentName) {
632 // Almost all fragments are wrapped in this,
633 // except for a few that have their own activities.
634 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
635 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
636 }
637 return false;
638 }
639
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800640 /**
641 * When in two-pane mode, switch to the fragment pane to show the given
642 * preference fragment.
643 *
644 * @param header The new header to display.
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800645 */
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700646 private void onHeaderClick(Header header) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800647 if (header == null) {
648 return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800649 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700650 if (header.fragment != null) {
651 startWithFragment(header.fragment, header.fragmentArguments, null, 0,
652 header.getTitle(getResources()));
653 } else if (header.intent != null) {
654 startActivity(header.intent);
655 } else {
656 throw new IllegalStateException(
657 "Can't switch to header that has no Fragment nor Intent");
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800658 }
659 }
660
661 /**
662 * Called to determine whether the header list should be hidden.
663 * The default implementation returns the
664 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
665 * This is set to false, for example, when the activity is being re-launched
666 * to show a particular preference activity.
667 */
668 public boolean onIsHidingHeaders() {
669 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
670 }
671
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800672 @Override
673 public Intent getIntent() {
674 Intent superIntent = super.getIntent();
675 String startingFragment = getStartingFragmentClass(superIntent);
676 // This is called from super.onCreate, isMultiPane() is not yet reliable
677 // Do not use onIsHidingHeaders either, which relies itself on this method
678 if (startingFragment != null) {
679 Intent modIntent = new Intent(superIntent);
680 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
681 Bundle args = superIntent.getExtras();
682 if (args != null) {
683 args = new Bundle(args);
684 } else {
685 args = new Bundle();
686 }
687 args.putParcelable("intent", superIntent);
688 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
689 return modIntent;
690 }
691 return superIntent;
692 }
693
694 /**
695 * Checks if the component name in the intent is different from the Settings class and
696 * returns the class name to load as a fragment.
697 */
698 private String getStartingFragmentClass(Intent intent) {
699 if (mFragmentClass != null) return mFragmentClass;
700
701 String intentClass = intent.getComponent().getClassName();
702 if (intentClass.equals(getClass().getName())) return null;
703
704 if ("com.android.settings.ManageApplications".equals(intentClass)
705 || "com.android.settings.RunningServices".equals(intentClass)
706 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
707 // Old names of manage apps.
708 intentClass = com.android.settings.applications.ManageApplications.class.getName();
709 }
710
711 return intentClass;
712 }
713
714 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000715 * Start a new fragment containing a preference panel. If the preferences
716 * are being displayed in multi-pane mode, the given fragment class will
717 * be instantiated and placed in the appropriate pane. If running in
718 * single-pane mode, a new activity will be launched in which to show the
719 * fragment.
720 *
721 * @param fragmentClass Full name of the class implementing the fragment.
722 * @param args Any desired arguments to supply to the fragment.
723 * @param titleRes Optional resource identifier of the title of this
724 * fragment.
725 * @param titleText Optional text of the title of this fragment.
726 * @param resultTo Optional fragment that result data should be sent to.
727 * If non-null, resultTo.onActivityResult() will be called when this
728 * preference panel is done. The launched panel must use
729 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
730 * @param resultRequestCode If resultTo is non-null, this is the caller's
731 * request code to be received with the resut.
732 */
733 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700734 CharSequence titleText, Fragment resultTo, int resultRequestCode) {
735 switchToFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000736 }
737
738 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800739 * Called by a preference panel fragment to finish itself.
740 *
741 * @param caller The fragment that is asking to be finished.
742 * @param resultCode Optional result code to send back to the original
743 * launching fragment.
744 * @param resultData Optional result data to send back to the original
745 * launching fragment.
746 */
747 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
748 setResult(resultCode, resultData);
749 }
750
751 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000752 * Start a new fragment.
753 *
754 * @param fragment The fragment to start
755 * @param push If true, the current fragment will be pushed onto the back stack. If false,
756 * the current fragment will be replaced.
757 */
758 public void startPreferenceFragment(Fragment fragment, boolean push) {
759 FragmentTransaction transaction = getFragmentManager().beginTransaction();
760 transaction.replace(R.id.prefs, fragment);
761 if (push) {
762 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
763 transaction.addToBackStack(BACK_STACK_PREFS);
764 } else {
765 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
766 }
767 transaction.commitAllowingStateLoss();
768 }
769
770 /**
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700771 * Start a new fragment. Used by #startPreferencePanel.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000772 *
773 * @param fragmentName The name of the fragment to display.
774 * @param args Optional arguments to supply to the fragment.
775 * @param resultTo Option fragment that should receive the result of
776 * the activity launch.
777 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
778 * report the result.
779 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
780 * valid one then it will be used to get the title. Otherwise the titleText
781 * argument will be used as the title.
782 * @param titleText string to display for the title of.
783 */
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700784 private void switchToFragment(String fragmentName, Bundle args, Fragment resultTo,
785 int resultRequestCode, int titleRes, CharSequence titleText) {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800786 final CharSequence cs;
787 if (titleRes != 0) {
788 cs = getText(titleRes);
789 } else {
790 cs = titleText;
791 }
792
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000793 Fragment f = Fragment.instantiate(this, fragmentName, args);
794 if (resultTo != null) {
795 f.setTargetFragment(resultTo, resultRequestCode);
796 }
797 FragmentTransaction transaction = getFragmentManager().beginTransaction();
798 transaction.replace(R.id.prefs, f);
799 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
800 transaction.addToBackStack(BACK_STACK_PREFS);
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800801 transaction.setBreadCrumbTitle(cs);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000802 transaction.commitAllowingStateLoss();
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000803 }
804
805 /**
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700806 * Switch to a specific Fragment with taking care of validation, Title and BackStack
807 */
808 private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
809 boolean addToBackStack, CharSequence title, boolean withTransition) {
810 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();
816 transaction.replace(R.id.prefs, f);
817 if (withTransition) {
818 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
819 }
820 if (addToBackStack) {
821 transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
822 }
823 if (title != null) {
824 transaction.setBreadCrumbTitle(title);
825 }
826 transaction.commitAllowingStateLoss();
827 return f;
828 }
829
830 /**
831 * Start a new instance of this activity, showing only the given fragment.
832 * When launched in this mode, the given preference fragment will be instantiated and fill the
833 * entire activity.
834 *
835 * @param fragmentName The name of the fragment to display.
836 * @param args Optional arguments to supply to the fragment.
837 * @param resultTo Option fragment that should receive the result of
838 * the activity launch.
839 * @param resultRequestCode If resultTo is non-null, this is the request
840 * code in which to report the result.
841 * @param title String to display for the title of this set of preferences.
842 */
843 public void startWithFragment(String fragmentName, Bundle args,
844 Fragment resultTo, int resultRequestCode, CharSequence title) {
845 Intent intent = onBuildStartFragmentIntent(fragmentName, args, title);
846 if (resultTo == null) {
847 startActivity(intent);
848 } else {
849 resultTo.startActivityForResult(intent, resultRequestCode);
850 }
851 }
852
853 /**
854 * Build an Intent to launch a new activity showing the selected fragment.
855 * The implementation constructs an Intent that re-launches the current activity with the
856 * appropriate arguments to display the fragment.
857 *
858 * @param fragmentName The name of the fragment to display.
859 * @param args Optional arguments to supply to the fragment.
860 * @param title Optional title to show for this item.
861 * @return Returns an Intent that can be launched to display the given
862 * fragment.
863 */
864 public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args, CharSequence title) {
865 Intent intent = new Intent(Intent.ACTION_MAIN);
866 intent.setClass(this, SubSettings.class);
867 intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
868 intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
869 intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, title);
870 intent.putExtra(EXTRA_NO_HEADERS, true);
871 return intent;
872 }
873
874 /**
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800875 * Called when the activity needs its list of headers build.
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800876 *
877 * @param headers The list in which to place the headers.
878 */
879 private void onBuildHeaders(List<Header> headers) {
880 loadHeadersFromResource(R.xml.settings_headers, headers);
881 updateHeaderList(headers);
882 }
883
884 /**
885 * Parse the given XML file as a header description, adding each
886 * parsed Header into the target list.
887 *
888 * @param resid The XML resource to load and parse.
889 * @param target The list in which the parsed headers should be placed.
890 */
891 private void loadHeadersFromResource(int resid, List<Header> target) {
892 XmlResourceParser parser = null;
893 try {
894 parser = getResources().getXml(resid);
895 AttributeSet attrs = Xml.asAttributeSet(parser);
896
897 int type;
898 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
899 && type != XmlPullParser.START_TAG) {
900 // Parse next until start tag is found
901 }
902
903 String nodeName = parser.getName();
904 if (!"preference-headers".equals(nodeName)) {
905 throw new RuntimeException(
906 "XML document must start with <preference-headers> tag; found"
907 + nodeName + " at " + parser.getPositionDescription());
908 }
909
910 Bundle curBundle = null;
911
912 final int outerDepth = parser.getDepth();
913 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
914 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
915 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
916 continue;
917 }
918
919 nodeName = parser.getName();
920 if ("header".equals(nodeName)) {
921 Header header = new Header();
922
923 TypedArray sa = obtainStyledAttributes(
924 attrs, com.android.internal.R.styleable.PreferenceHeader);
925 header.id = sa.getResourceId(
926 com.android.internal.R.styleable.PreferenceHeader_id,
927 (int)HEADER_ID_UNDEFINED);
928 TypedValue tv = sa.peekValue(
929 com.android.internal.R.styleable.PreferenceHeader_title);
930 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
931 if (tv.resourceId != 0) {
932 header.titleRes = tv.resourceId;
933 } else {
934 header.title = tv.string;
935 }
936 }
937 tv = sa.peekValue(
938 com.android.internal.R.styleable.PreferenceHeader_summary);
939 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
940 if (tv.resourceId != 0) {
941 header.summaryRes = tv.resourceId;
942 } else {
943 header.summary = tv.string;
944 }
945 }
946 header.iconRes = sa.getResourceId(
947 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
948 header.fragment = sa.getString(
949 com.android.internal.R.styleable.PreferenceHeader_fragment);
950 sa.recycle();
951
952 if (curBundle == null) {
953 curBundle = new Bundle();
954 }
955
956 final int innerDepth = parser.getDepth();
957 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
958 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
959 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
960 continue;
961 }
962
963 String innerNodeName = parser.getName();
964 if (innerNodeName.equals("extra")) {
965 getResources().parseBundleExtra("extra", attrs, curBundle);
966 XmlUtils.skipCurrentTag(parser);
967
968 } else if (innerNodeName.equals("intent")) {
969 header.intent = Intent.parseIntent(getResources(), parser, attrs);
970
971 } else {
972 XmlUtils.skipCurrentTag(parser);
973 }
974 }
975
976 if (curBundle.size() > 0) {
977 header.fragmentArguments = curBundle;
978 curBundle = null;
979 }
980
981 target.add(header);
982 } else {
983 XmlUtils.skipCurrentTag(parser);
984 }
985 }
986
987 } catch (XmlPullParserException e) {
988 throw new RuntimeException("Error parsing headers", e);
989 } catch (IOException e) {
990 throw new RuntimeException("Error parsing headers", e);
991 } finally {
992 if (parser != null) parser.close();
993 }
994 }
995
996 private void updateHeaderList(List<Header> target) {
997 final boolean showDev = mDevelopmentPreferences.getBoolean(
998 DevelopmentSettings.PREF_SHOW,
999 android.os.Build.TYPE.equals("eng"));
1000 int i = 0;
1001
1002 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1003 mHeaderIndexMap.clear();
1004 while (i < target.size()) {
1005 Header header = target.get(i);
1006 // Ids are integers, so downcasting
1007 int id = (int) header.id;
1008 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1009 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1010 } else if (id == R.id.wifi_settings) {
1011 // Remove WiFi Settings if WiFi service is not available.
1012 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1013 target.remove(i);
1014 }
1015 } else if (id == R.id.bluetooth_settings) {
1016 // Remove Bluetooth Settings if Bluetooth service is not available.
1017 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1018 target.remove(i);
1019 }
1020 } else if (id == R.id.data_usage_settings) {
1021 // Remove data usage when kernel module not enabled
1022 final INetworkManagementService netManager = INetworkManagementService.Stub
1023 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1024 try {
1025 if (!netManager.isBandwidthControlEnabled()) {
1026 target.remove(i);
1027 }
1028 } catch (RemoteException e) {
1029 // ignored
1030 }
1031 } else if (id == R.id.battery_settings) {
1032 // Remove battery settings when battery is not available. (e.g. TV)
1033
1034 if (!mBatteryPresent) {
1035 target.remove(i);
1036 }
1037 } else if (id == R.id.account_settings) {
1038 int headerIndex = i + 1;
1039 i = insertAccountsHeaders(target, headerIndex);
1040 } else if (id == R.id.home_settings) {
1041 if (!updateHomeSettingHeaders(header)) {
1042 target.remove(i);
1043 }
1044 } else if (id == R.id.user_settings) {
1045 if (!UserHandle.MU_ENABLED
1046 || !UserManager.supportsMultipleUsers()
1047 || Utils.isMonkeyRunning()) {
1048 target.remove(i);
1049 }
1050 } else if (id == R.id.nfc_payment_settings) {
1051 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1052 target.remove(i);
1053 } else {
1054 // Only show if NFC is on and we have the HCE feature
1055 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1056 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1057 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1058 target.remove(i);
1059 }
1060 }
1061 } else if (id == R.id.development_settings) {
1062 if (!showDev) {
1063 target.remove(i);
1064 }
1065 } else if (id == R.id.account_add) {
1066 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1067 target.remove(i);
1068 }
1069 }
1070
1071 if (i < target.size() && target.get(i) == header
1072 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1073 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1074 target.remove(i);
1075 }
1076
1077 // Increment if the current one wasn't removed by the Utils code.
1078 if (i < target.size() && target.get(i) == header) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001079 mHeaderIndexMap.put(id, i);
1080 i++;
1081 }
1082 }
1083 }
1084
1085 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1086 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1087 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1088 for (String accountType : accountTypes) {
1089 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1090 if (label == null) {
1091 continue;
1092 }
1093
1094 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1095 boolean skipToAccount = accounts.length == 1
1096 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1097 Header accHeader = new Header();
1098 accHeader.title = label;
1099 if (accHeader.extras == null) {
1100 accHeader.extras = new Bundle();
1101 }
1102 if (skipToAccount) {
1103 accHeader.fragment = AccountSyncSettings.class.getName();
1104 accHeader.fragmentArguments = new Bundle();
1105 // Need this for the icon
1106 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1107 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1108 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1109 accounts[0]);
1110 } else {
1111 accHeader.fragment = ManageAccountsSettings.class.getName();
1112 accHeader.fragmentArguments = new Bundle();
1113 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1114 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1115 accountType);
1116 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1117 label.toString());
1118 }
1119 accountHeaders.add(accHeader);
1120 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1121 }
1122
1123 // Sort by label
1124 Collections.sort(accountHeaders, new Comparator<Header>() {
1125 @Override
1126 public int compare(Header h1, Header h2) {
1127 return h1.title.toString().compareTo(h2.title.toString());
1128 }
1129 });
1130
1131 for (Header header : accountHeaders) {
1132 target.add(headerIndex++, header);
1133 }
1134 if (!mListeningToAccountUpdates) {
1135 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1136 mListeningToAccountUpdates = true;
1137 }
1138 return headerIndex;
1139 }
1140
1141 private boolean updateHomeSettingHeaders(Header header) {
1142 // Once we decide to show Home settings, keep showing it forever
1143 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1144 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1145 return true;
1146 }
1147
1148 try {
1149 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1150 getPackageManager().getHomeActivities(homeApps);
1151 if (homeApps.size() < 2) {
1152 // When there's only one available home app, omit this settings
1153 // category entirely at the top level UI. If the user just
1154 // uninstalled the penultimate home app candidiate, we also
1155 // now tell them about why they aren't seeing 'Home' in the list.
1156 if (sShowNoHomeNotice) {
1157 sShowNoHomeNotice = false;
1158 NoHomeDialogFragment.show(this);
1159 }
1160 return false;
1161 } else {
1162 // Okay, we're allowing the Home settings category. Tell it, when
1163 // invoked via this front door, that we'll need to be told about the
1164 // case when the user uninstalls all but one home app.
1165 if (header.fragmentArguments == null) {
1166 header.fragmentArguments = new Bundle();
1167 }
1168 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1169 }
1170 } catch (Exception e) {
1171 // Can't look up the home activity; bail on configuring the icon
1172 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1173 }
1174
1175 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1176 return true;
1177 }
1178
1179 private void getMetaData() {
1180 try {
1181 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1182 PackageManager.GET_META_DATA);
1183 if (ai == null || ai.metaData == null) return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001184 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1185 } catch (NameNotFoundException nnfe) {
1186 // No recovery
1187 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1188 }
1189 }
1190
1191 // give subclasses access to the Next button
1192 public boolean hasNextButton() {
1193 return mNextButton != null;
1194 }
1195
1196 public Button getNextButton() {
1197 return mNextButton;
1198 }
1199
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001200 public HeaderAdapter getHeaderAdapter() {
1201 return mHeaderAdapter;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001202 }
1203
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001204 public void onListItemClick(ListView l, View v, int position, long id) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001205 if (!isResumed()) {
1206 return;
1207 }
1208 Object item = mHeaderAdapter.getItem(position);
1209 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001210 mSelectedHeader = (Header) item;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001211 onHeaderClick(mSelectedHeader);
1212 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001213 }
1214 }
1215
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001216 @Override
1217 public boolean shouldUpRecreateTask(Intent targetIntent) {
1218 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1219 }
1220
1221 @Override
1222 public void onAccountsUpdated(Account[] accounts) {
1223 // TODO: watch for package upgrades to invalidate cache; see 7206643
1224 mAuthenticatorHelper.updateAuthDescriptions(this);
1225 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1226 invalidateHeaders();
1227 }
1228
1229 public static void requestHomeNotice() {
1230 sShowNoHomeNotice = true;
1231 }
1232
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001233 @Override
1234 public boolean onQueryTextSubmit(String query) {
1235 switchToSearchResultsFragmentIfNeeded();
1236 mSearchQuery = query;
1237 return mSearchResultsFragment.onQueryTextSubmit(query);
1238 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001239
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001240 @Override
1241 public boolean onQueryTextChange(String newText) {
1242 mSearchQuery = newText;
1243 return false;
1244 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001245
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001246 @Override
1247 public boolean onClose() {
1248 return false;
1249 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001250
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001251 @Override
1252 public boolean onMenuItemActionExpand(MenuItem item) {
1253 if (item.getItemId() == mSearchMenuItem.getItemId()) {
1254 if (mSearchResultsFragment == null) {
1255 switchToSearchResultsFragmentIfNeeded();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001256 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001257 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001258 return true;
1259 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001260
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001261 @Override
1262 public boolean onMenuItemActionCollapse(MenuItem item) {
1263 if (item.getItemId() == mSearchMenuItem.getItemId()) {
1264 if (mIsShowingSearchResults) {
1265 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001266 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001267 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001268 return true;
1269 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001270
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001271 private void switchToSearchResultsFragmentIfNeeded() {
1272 if (!mIsShowingSearchResults) {
1273 Fragment current = getFragmentManager().findFragmentById(R.id.prefs);
1274 if (current != null && current instanceof SearchResultsSummary) {
1275 mSearchResultsFragment = (SearchResultsSummary) current;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001276 } else {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001277 String title = getString(R.string.search_results_title);
1278 mSearchResultsFragment = (SearchResultsSummary) switchToFragment(
1279 SearchResultsSummary.class.getName(), null, false, true, title, true);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001280 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001281 mIsShowingSearchResults = true;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001282 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001283 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001284
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001285 public void needToRevertToInitialFragment() {
1286 mNeedToRevertToInitialFragment = true;
1287 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001288
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001289 private void revertToInitialFragment() {
1290 mNeedToRevertToInitialFragment = false;
1291 getFragmentManager().popBackStack(SettingsActivity.BACK_STACK_PREFS,
1292 FragmentManager.POP_BACK_STACK_INCLUSIVE);
1293 mSearchResultsFragment = null;
1294 mIsShowingSearchResults = false;
1295 mSearchMenuItem.collapseActionView();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001296 }
1297}