blob: 5aa273787fbc873b14ec4d438b85cd304d31b35b [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";
127 private static final String SAVE_KEY_CURRENT_HEADER = ":settings:cur_header";
128 private static final String SAVE_KEY_SEARCH_MENU_EXPANDED = ":settings:search_menu_expanded";
129 private static final String SAVE_KEY_SEARCH_QUERY = ":settings:search_query";
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_HEADER_ID =
181 "com.android.settings.TOP_LEVEL_HEADER_ID";
182
183 private static final String META_DATA_KEY_FRAGMENT_CLASS =
184 "com.android.settings.FRAGMENT_CLASS";
185
186 private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
187
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800188 private static boolean sShowNoHomeNotice = false;
189
190 private String mFragmentClass;
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800191 private Header mSelectedHeader;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800192 private Header mCurrentHeader;
193
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800194 private CharSequence mInitialTitle;
195
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800196 // Show only these settings for restricted users
197 private int[] SETTINGS_FOR_RESTRICTED = {
198 R.id.wireless_section,
199 R.id.wifi_settings,
200 R.id.bluetooth_settings,
201 R.id.data_usage_settings,
202 R.id.wireless_settings,
203 R.id.device_section,
204 R.id.sound_settings,
205 R.id.display_settings,
206 R.id.storage_settings,
207 R.id.application_settings,
208 R.id.battery_settings,
209 R.id.personal_section,
210 R.id.location_settings,
211 R.id.security_settings,
212 R.id.language_settings,
213 R.id.user_settings,
214 R.id.account_settings,
215 R.id.account_add,
216 R.id.system_section,
217 R.id.date_time_settings,
218 R.id.about_settings,
219 R.id.accessibility_settings,
220 R.id.print_settings,
221 R.id.nfc_payment_settings,
Fabrice Di Meglio2858b792014-02-18 19:35:08 -0800222 R.id.home_settings,
223 R.id.dashboard
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800224 };
225
226 private static final String[] ENTRY_FRAGMENTS = {
227 WirelessSettings.class.getName(),
228 WifiSettings.class.getName(),
229 AdvancedWifiSettings.class.getName(),
230 BluetoothSettings.class.getName(),
231 TetherSettings.class.getName(),
232 WifiP2pSettings.class.getName(),
233 VpnSettings.class.getName(),
234 DateTimeSettings.class.getName(),
235 LocalePicker.class.getName(),
236 InputMethodAndLanguageSettings.class.getName(),
237 SpellCheckersSettings.class.getName(),
238 UserDictionaryList.class.getName(),
239 UserDictionarySettings.class.getName(),
240 SoundSettings.class.getName(),
241 DisplaySettings.class.getName(),
242 DeviceInfoSettings.class.getName(),
243 ManageApplications.class.getName(),
244 ProcessStatsUi.class.getName(),
245 NotificationStation.class.getName(),
246 LocationSettings.class.getName(),
247 SecuritySettings.class.getName(),
248 PrivacySettings.class.getName(),
249 DeviceAdminSettings.class.getName(),
250 AccessibilitySettings.class.getName(),
251 CaptionPropertiesFragment.class.getName(),
252 com.android.settings.accessibility.ToggleInversionPreferenceFragment.class.getName(),
253 com.android.settings.accessibility.ToggleContrastPreferenceFragment.class.getName(),
254 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
255 TextToSpeechSettings.class.getName(),
256 Memory.class.getName(),
257 DevelopmentSettings.class.getName(),
258 UsbSettings.class.getName(),
259 AndroidBeam.class.getName(),
260 WifiDisplaySettings.class.getName(),
261 PowerUsageSummary.class.getName(),
262 AccountSyncSettings.class.getName(),
263 CryptKeeperSettings.class.getName(),
264 DataUsageSummary.class.getName(),
265 DreamSettings.class.getName(),
266 UserSettings.class.getName(),
267 NotificationAccessSettings.class.getName(),
268 ManageAccountsSettings.class.getName(),
269 PrintSettingsFragment.class.getName(),
270 PrintJobSettingsFragment.class.getName(),
271 TrustedCredentialsSettings.class.getName(),
272 PaymentSettings.class.getName(),
273 KeyboardLayoutPickerFragment.class.getName(),
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700274 ZenModeSettings.class.getName(),
275 NotificationSettings.class.getName(),
276 ChooseLockPassword.ChooseLockPasswordFragment.class.getName(),
277 ChooseLockPattern.ChooseLockPatternFragment.class.getName(),
278 InstalledAppDetails.class.getName()
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800279 };
280
281 private SharedPreferences mDevelopmentPreferences;
282 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
283
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800284 private AuthenticatorHelper mAuthenticatorHelper;
285 private boolean mListeningToAccountUpdates;
286
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800287 private boolean mBatteryPresent = true;
288 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
289
290 @Override
291 public void onReceive(Context context, Intent intent) {
292 String action = intent.getAction();
293 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
294 boolean batteryPresent = Utils.isBatteryPresent(intent);
295
296 if (mBatteryPresent != batteryPresent) {
297 mBatteryPresent = batteryPresent;
298 invalidateHeaders();
299 }
300 }
301 }
302 };
303
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700304 private Button mNextButton;
305 private ActionBar mActionBar;
306
307 private SearchView mSearchView;
308 private MenuItem mSearchMenuItem;
309 private boolean mSearchMenuItemExpanded = false;
310 private boolean mIsShowingSearchResults = false;
311 private SearchResultsSummary mSearchResultsFragment;
312 private String mSearchQuery;
313
314 // Headers
315 protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800316 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800317 private HeaderAdapter mHeaderAdapter;
318
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800319 private static final int MSG_BUILD_HEADERS = 1;
320 private Handler mHandler = new Handler() {
321 @Override
322 public void handleMessage(Message msg) {
323 switch (msg.what) {
324 case MSG_BUILD_HEADERS: {
325 mHeaders.clear();
326 onBuildHeaders(mHeaders);
327 mHeaderAdapter.notifyDataSetChanged();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800328 } break;
329 }
330 }
331 };
332
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700333 private boolean mNeedToRevertToInitialFragment = false;
334
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800335 @Override
336 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
337 // Override the fragment title for Wallpaper settings
338 int titleRes = pref.getTitleRes();
339 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
340 titleRes = R.string.wallpaper_settings_fragment_title;
341 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
342 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
343 if (UserManager.get(this).isLinkedUser()) {
344 titleRes = R.string.profile_info_settings_title;
345 } else {
346 titleRes = R.string.user_info_settings_title;
347 }
348 }
349 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
350 null, 0);
351 return true;
352 }
353
354 @Override
355 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
356 return false;
357 }
358
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800359 private void invalidateHeaders() {
360 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
361 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
362 }
363 }
364
365 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800366 public void onConfigurationChanged(Configuration newConfig) {
367 super.onConfigurationChanged(newConfig);
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800368 Index.getInstance(this).update();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800369 }
370
371 @Override
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700372 protected void onStart() {
373 super.onStart();
374
375 if (mNeedToRevertToInitialFragment) {
376 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800377 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800378 }
379
380 @Override
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700381 public boolean onCreateOptionsMenu(Menu menu) {
382 MenuInflater inflater = getMenuInflater();
383 inflater.inflate(R.menu.options_menu, menu);
384
385 // Cache the search query (can be overriden by the OnQueryTextListener)
386 final String query = mSearchQuery;
387
Fabrice Di Meglio95937822014-03-31 19:46:42 -0700388 mSearchMenuItem = menu.findItem(R.id.search);
389 mSearchView = (SearchView) mSearchMenuItem.getActionView();
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700390
Fabrice Di Meglio95937822014-03-31 19:46:42 -0700391 mSearchMenuItem.setOnActionExpandListener(this);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700392 mSearchView.setOnQueryTextListener(this);
393 mSearchView.setOnCloseListener(this);
394
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700395 if (mSearchMenuItemExpanded) {
396 mSearchMenuItem.expandActionView();
397 }
398 mSearchView.setQuery(query, true /* submit */);
399
400 return true;
401 }
402
403 @Override
404 protected void onCreate(Bundle savedState) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800405 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
406 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
407 }
408
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800409 Index.getInstance(this).update();
410
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800411 mAuthenticatorHelper = new AuthenticatorHelper();
412 mAuthenticatorHelper.updateAuthDescriptions(this);
413 mAuthenticatorHelper.onAccountsUpdated(this, null);
414
415 DevicePolicyManager dpm =
416 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
Fabrice Di Megliodca28062014-02-21 17:42:56 -0800417
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700418 mHeaderAdapter = new HeaderAdapter(this, mHeaders, mAuthenticatorHelper, dpm);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800419
420 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
421 Context.MODE_PRIVATE);
422
423 getMetaData();
424
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700425 super.onCreate(savedState);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800426
427 setContentView(R.layout.settings_main);
428
429 getFragmentManager().addOnBackStackChangedListener(this);
430
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700431 boolean displayHomeAsUpEnabled = true;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800432
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700433 String initialFragmentName = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
434 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800435
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700436 if (savedState != null) {
437 mSearchMenuItemExpanded = savedState.getBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED);
438 mSearchQuery = savedState.getString(SAVE_KEY_SEARCH_QUERY);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800439
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700440 // We are restarting from a previous saved state; used that to
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800441 // initialize, instead of starting fresh.
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800442 mInitialTitle = getTitle();
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800443
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700444 ArrayList<Header> headers = savedState.getParcelableArrayList(SAVE_KEY_HEADERS);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800445 if (headers != null) {
446 mHeaders.addAll(headers);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700447 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800448 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800449 } else {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800450 // We need to build the Headers in all cases
451 onBuildHeaders(mHeaders);
452
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700453 if (initialFragmentName != null) {
454 final ComponentName cn = getIntent().getComponent();
455 // No UP is we are launched thru a Settings shortcut
456 if (!cn.getClassName().equals(SubSettings.class.getName())) {
457 displayHomeAsUpEnabled = false;
458 }
459 final String initialTitle = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
460 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800461 setTitle(mInitialTitle);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700462 switchToFragment( initialFragmentName, initialArguments, true, false,
463 mInitialTitle, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000464 } else {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700465 // No UP if we are displaying the Headers
466 displayHomeAsUpEnabled = false;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000467 if (mHeaders.size() > 0) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700468 mInitialTitle = getText(R.string.dashboard_title);
469 switchToFragment(DashboardSummary.class.getName(), null, false, false,
470 mInitialTitle, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000471 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800472 }
473 }
474
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700475 mActionBar = getActionBar();
476 mActionBar.setHomeButtonEnabled(true);
477 mActionBar.setDisplayHomeAsUpEnabled(displayHomeAsUpEnabled);
478
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800479 // see if we should show Back/Next buttons
480 Intent intent = getIntent();
481 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
482
483 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
484 if (buttonBar != null) {
485 buttonBar.setVisibility(View.VISIBLE);
486
487 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
488 backButton.setOnClickListener(new OnClickListener() {
489 public void onClick(View v) {
490 setResult(RESULT_CANCELED);
491 finish();
492 }
493 });
494 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
495 skipButton.setOnClickListener(new OnClickListener() {
496 public void onClick(View v) {
497 setResult(RESULT_OK);
498 finish();
499 }
500 });
501 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
502 mNextButton.setOnClickListener(new OnClickListener() {
503 public void onClick(View v) {
504 setResult(RESULT_OK);
505 finish();
506 }
507 });
508
509 // set our various button parameters
510 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
511 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
512 if (TextUtils.isEmpty(buttonText)) {
513 mNextButton.setVisibility(View.GONE);
514 }
515 else {
516 mNextButton.setText(buttonText);
517 }
518 }
519 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
520 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
521 if (TextUtils.isEmpty(buttonText)) {
522 backButton.setVisibility(View.GONE);
523 }
524 else {
525 backButton.setText(buttonText);
526 }
527 }
528 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
529 skipButton.setVisibility(View.VISIBLE);
530 }
531 }
532 }
Fabrice Di Meglioc95be4f2014-03-07 12:57:38 -0800533 }
534
535 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800536 public void onBackStackChanged() {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700537 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800538 }
539
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700540 private int setTitleFromBackStack() {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800541 final int count = getFragmentManager().getBackStackEntryCount();
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700542
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800543 if (count == 0) {
544 setTitle(mInitialTitle);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700545 return 0;
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800546 }
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700547
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800548 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1);
549 setTitleFromBackStackEntry(bse);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700550
551 return count;
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800552 }
553
554 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
555 final CharSequence title;
556 final int titleRes = bse.getBreadCrumbTitleRes();
557 if (titleRes > 0) {
558 title = getText(titleRes);
559 } else {
560 title = bse.getBreadCrumbTitle();
561 }
562 if (title != null) {
563 setTitle(title);
564 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800565 }
566
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800567 @Override
568 protected void onSaveInstanceState(Bundle outState) {
569 super.onSaveInstanceState(outState);
570
571 if (mHeaders.size() > 0) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700572 outState.putParcelableArrayList(SAVE_KEY_HEADERS, mHeaders);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800573 if (mCurrentHeader != null) {
574 int index = mHeaders.indexOf(mCurrentHeader);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800575 if (index >= 0) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700576 outState.putInt(SAVE_KEY_CURRENT_HEADER, index);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800577 }
578 }
579 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700580 outState.putBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED, mSearchMenuItem.isActionViewExpanded());
581 outState.putString(SAVE_KEY_SEARCH_QUERY, mSearchView.getQuery().toString());
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800582 }
583
584 @Override
585 public void onResume() {
586 super.onResume();
587
588 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
589 @Override
590 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
591 invalidateHeaders();
592 }
593 };
594 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
595 mDevelopmentPreferencesListener);
596
Matthew Xiea504c4d2014-02-14 16:32:32 -0800597 mHeaderAdapter.resume(this);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800598 invalidateHeaders();
599
600 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
601 }
602
603 @Override
604 public void onPause() {
605 super.onPause();
606
607 unregisterReceiver(mBatteryInfoReceiver);
608
609 mHeaderAdapter.pause();
610
611 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
612 mDevelopmentPreferencesListener);
613
614 mDevelopmentPreferencesListener = null;
615 }
616
617 @Override
618 public void onDestroy() {
619 super.onDestroy();
620 if (mListeningToAccountUpdates) {
621 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
622 }
623 }
624
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800625 protected boolean isValidFragment(String fragmentName) {
626 // Almost all fragments are wrapped in this,
627 // except for a few that have their own activities.
628 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
629 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
630 }
631 return false;
632 }
633
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800634 /**
635 * When in two-pane mode, switch to the fragment pane to show the given
636 * preference fragment.
637 *
638 * @param header The new header to display.
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800639 */
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700640 private void onHeaderClick(Header header) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800641 if (header == null) {
642 return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800643 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700644 if (header.fragment != null) {
645 startWithFragment(header.fragment, header.fragmentArguments, null, 0,
646 header.getTitle(getResources()));
647 } else if (header.intent != null) {
648 startActivity(header.intent);
649 } else {
650 throw new IllegalStateException(
651 "Can't switch to header that has no Fragment nor Intent");
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800652 }
653 }
654
655 /**
656 * Called to determine whether the header list should be hidden.
657 * The default implementation returns the
658 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
659 * This is set to false, for example, when the activity is being re-launched
660 * to show a particular preference activity.
661 */
662 public boolean onIsHidingHeaders() {
663 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
664 }
665
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800666 @Override
667 public Intent getIntent() {
668 Intent superIntent = super.getIntent();
669 String startingFragment = getStartingFragmentClass(superIntent);
670 // This is called from super.onCreate, isMultiPane() is not yet reliable
671 // Do not use onIsHidingHeaders either, which relies itself on this method
672 if (startingFragment != null) {
673 Intent modIntent = new Intent(superIntent);
674 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
675 Bundle args = superIntent.getExtras();
676 if (args != null) {
677 args = new Bundle(args);
678 } else {
679 args = new Bundle();
680 }
681 args.putParcelable("intent", superIntent);
682 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
683 return modIntent;
684 }
685 return superIntent;
686 }
687
688 /**
689 * Checks if the component name in the intent is different from the Settings class and
690 * returns the class name to load as a fragment.
691 */
692 private String getStartingFragmentClass(Intent intent) {
693 if (mFragmentClass != null) return mFragmentClass;
694
695 String intentClass = intent.getComponent().getClassName();
696 if (intentClass.equals(getClass().getName())) return null;
697
698 if ("com.android.settings.ManageApplications".equals(intentClass)
699 || "com.android.settings.RunningServices".equals(intentClass)
700 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
701 // Old names of manage apps.
702 intentClass = com.android.settings.applications.ManageApplications.class.getName();
703 }
704
705 return intentClass;
706 }
707
708 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000709 * Start a new fragment containing a preference panel. If the preferences
710 * are being displayed in multi-pane mode, the given fragment class will
711 * be instantiated and placed in the appropriate pane. If running in
712 * single-pane mode, a new activity will be launched in which to show the
713 * fragment.
714 *
715 * @param fragmentClass Full name of the class implementing the fragment.
716 * @param args Any desired arguments to supply to the fragment.
717 * @param titleRes Optional resource identifier of the title of this
718 * fragment.
719 * @param titleText Optional text of the title of this fragment.
720 * @param resultTo Optional fragment that result data should be sent to.
721 * If non-null, resultTo.onActivityResult() will be called when this
722 * preference panel is done. The launched panel must use
723 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
724 * @param resultRequestCode If resultTo is non-null, this is the caller's
725 * request code to be received with the resut.
726 */
727 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700728 CharSequence titleText, Fragment resultTo, int resultRequestCode) {
729 switchToFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000730 }
731
732 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800733 * Called by a preference panel fragment to finish itself.
734 *
735 * @param caller The fragment that is asking to be finished.
736 * @param resultCode Optional result code to send back to the original
737 * launching fragment.
738 * @param resultData Optional result data to send back to the original
739 * launching fragment.
740 */
741 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
742 setResult(resultCode, resultData);
743 }
744
745 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000746 * Start a new fragment.
747 *
748 * @param fragment The fragment to start
749 * @param push If true, the current fragment will be pushed onto the back stack. If false,
750 * the current fragment will be replaced.
751 */
752 public void startPreferenceFragment(Fragment fragment, boolean push) {
753 FragmentTransaction transaction = getFragmentManager().beginTransaction();
754 transaction.replace(R.id.prefs, fragment);
755 if (push) {
756 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
757 transaction.addToBackStack(BACK_STACK_PREFS);
758 } else {
759 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
760 }
761 transaction.commitAllowingStateLoss();
762 }
763
764 /**
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700765 * Start a new fragment. Used by #startPreferencePanel.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000766 *
767 * @param fragmentName The name of the fragment to display.
768 * @param args Optional arguments to supply to the fragment.
769 * @param resultTo Option fragment that should receive the result of
770 * the activity launch.
771 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
772 * report the result.
773 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
774 * valid one then it will be used to get the title. Otherwise the titleText
775 * argument will be used as the title.
776 * @param titleText string to display for the title of.
777 */
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700778 private void switchToFragment(String fragmentName, Bundle args, Fragment resultTo,
779 int resultRequestCode, int titleRes, CharSequence titleText) {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800780 final CharSequence cs;
781 if (titleRes != 0) {
782 cs = getText(titleRes);
783 } else {
784 cs = titleText;
785 }
786
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000787 Fragment f = Fragment.instantiate(this, fragmentName, args);
788 if (resultTo != null) {
789 f.setTargetFragment(resultTo, resultRequestCode);
790 }
791 FragmentTransaction transaction = getFragmentManager().beginTransaction();
792 transaction.replace(R.id.prefs, f);
793 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
794 transaction.addToBackStack(BACK_STACK_PREFS);
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800795 transaction.setBreadCrumbTitle(cs);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000796 transaction.commitAllowingStateLoss();
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000797 }
798
799 /**
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700800 * Switch to a specific Fragment with taking care of validation, Title and BackStack
801 */
802 private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
803 boolean addToBackStack, CharSequence title, boolean withTransition) {
804 if (validate && !isValidFragment(fragmentName)) {
805 throw new IllegalArgumentException("Invalid fragment for this activity: "
806 + fragmentName);
807 }
808 Fragment f = Fragment.instantiate(this, fragmentName, args);
809 FragmentTransaction transaction = getFragmentManager().beginTransaction();
810 transaction.replace(R.id.prefs, f);
811 if (withTransition) {
812 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
813 }
814 if (addToBackStack) {
815 transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
816 }
817 if (title != null) {
818 transaction.setBreadCrumbTitle(title);
819 }
820 transaction.commitAllowingStateLoss();
821 return f;
822 }
823
824 /**
825 * Start a new instance of this activity, showing only the given fragment.
826 * When launched in this mode, the given preference fragment will be instantiated and fill the
827 * entire activity.
828 *
829 * @param fragmentName The name of the fragment to display.
830 * @param args Optional arguments to supply to the fragment.
831 * @param resultTo Option fragment that should receive the result of
832 * the activity launch.
833 * @param resultRequestCode If resultTo is non-null, this is the request
834 * code in which to report the result.
835 * @param title String to display for the title of this set of preferences.
836 */
837 public void startWithFragment(String fragmentName, Bundle args,
838 Fragment resultTo, int resultRequestCode, CharSequence title) {
839 Intent intent = onBuildStartFragmentIntent(fragmentName, args, title);
840 if (resultTo == null) {
841 startActivity(intent);
842 } else {
843 resultTo.startActivityForResult(intent, resultRequestCode);
844 }
845 }
846
847 /**
848 * Build an Intent to launch a new activity showing the selected fragment.
849 * The implementation constructs an Intent that re-launches the current activity with the
850 * appropriate arguments to display the fragment.
851 *
852 * @param fragmentName The name of the fragment to display.
853 * @param args Optional arguments to supply to the fragment.
854 * @param title Optional title to show for this item.
855 * @return Returns an Intent that can be launched to display the given
856 * fragment.
857 */
858 public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args, CharSequence title) {
859 Intent intent = new Intent(Intent.ACTION_MAIN);
860 intent.setClass(this, SubSettings.class);
861 intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
862 intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
863 intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, title);
864 intent.putExtra(EXTRA_NO_HEADERS, true);
865 return intent;
866 }
867
868 /**
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800869 * Called when the activity needs its list of headers build.
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800870 *
871 * @param headers The list in which to place the headers.
872 */
873 private void onBuildHeaders(List<Header> headers) {
874 loadHeadersFromResource(R.xml.settings_headers, headers);
875 updateHeaderList(headers);
876 }
877
878 /**
879 * Parse the given XML file as a header description, adding each
880 * parsed Header into the target list.
881 *
882 * @param resid The XML resource to load and parse.
883 * @param target The list in which the parsed headers should be placed.
884 */
885 private void loadHeadersFromResource(int resid, List<Header> target) {
886 XmlResourceParser parser = null;
887 try {
888 parser = getResources().getXml(resid);
889 AttributeSet attrs = Xml.asAttributeSet(parser);
890
891 int type;
892 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
893 && type != XmlPullParser.START_TAG) {
894 // Parse next until start tag is found
895 }
896
897 String nodeName = parser.getName();
898 if (!"preference-headers".equals(nodeName)) {
899 throw new RuntimeException(
900 "XML document must start with <preference-headers> tag; found"
901 + nodeName + " at " + parser.getPositionDescription());
902 }
903
904 Bundle curBundle = null;
905
906 final int outerDepth = parser.getDepth();
907 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
908 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
909 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
910 continue;
911 }
912
913 nodeName = parser.getName();
914 if ("header".equals(nodeName)) {
915 Header header = new Header();
916
917 TypedArray sa = obtainStyledAttributes(
918 attrs, com.android.internal.R.styleable.PreferenceHeader);
919 header.id = sa.getResourceId(
920 com.android.internal.R.styleable.PreferenceHeader_id,
921 (int)HEADER_ID_UNDEFINED);
922 TypedValue tv = sa.peekValue(
923 com.android.internal.R.styleable.PreferenceHeader_title);
924 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
925 if (tv.resourceId != 0) {
926 header.titleRes = tv.resourceId;
927 } else {
928 header.title = tv.string;
929 }
930 }
931 tv = sa.peekValue(
932 com.android.internal.R.styleable.PreferenceHeader_summary);
933 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
934 if (tv.resourceId != 0) {
935 header.summaryRes = tv.resourceId;
936 } else {
937 header.summary = tv.string;
938 }
939 }
940 header.iconRes = sa.getResourceId(
941 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
942 header.fragment = sa.getString(
943 com.android.internal.R.styleable.PreferenceHeader_fragment);
944 sa.recycle();
945
946 if (curBundle == null) {
947 curBundle = new Bundle();
948 }
949
950 final int innerDepth = parser.getDepth();
951 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
952 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
953 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
954 continue;
955 }
956
957 String innerNodeName = parser.getName();
958 if (innerNodeName.equals("extra")) {
959 getResources().parseBundleExtra("extra", attrs, curBundle);
960 XmlUtils.skipCurrentTag(parser);
961
962 } else if (innerNodeName.equals("intent")) {
963 header.intent = Intent.parseIntent(getResources(), parser, attrs);
964
965 } else {
966 XmlUtils.skipCurrentTag(parser);
967 }
968 }
969
970 if (curBundle.size() > 0) {
971 header.fragmentArguments = curBundle;
972 curBundle = null;
973 }
974
975 target.add(header);
976 } else {
977 XmlUtils.skipCurrentTag(parser);
978 }
979 }
980
981 } catch (XmlPullParserException e) {
982 throw new RuntimeException("Error parsing headers", e);
983 } catch (IOException e) {
984 throw new RuntimeException("Error parsing headers", e);
985 } finally {
986 if (parser != null) parser.close();
987 }
988 }
989
990 private void updateHeaderList(List<Header> target) {
991 final boolean showDev = mDevelopmentPreferences.getBoolean(
992 DevelopmentSettings.PREF_SHOW,
993 android.os.Build.TYPE.equals("eng"));
994 int i = 0;
995
996 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
997 mHeaderIndexMap.clear();
998 while (i < target.size()) {
999 Header header = target.get(i);
1000 // Ids are integers, so downcasting
1001 int id = (int) header.id;
1002 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1003 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1004 } else if (id == R.id.wifi_settings) {
1005 // Remove WiFi Settings if WiFi service is not available.
1006 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1007 target.remove(i);
1008 }
1009 } else if (id == R.id.bluetooth_settings) {
1010 // Remove Bluetooth Settings if Bluetooth service is not available.
1011 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1012 target.remove(i);
1013 }
1014 } else if (id == R.id.data_usage_settings) {
1015 // Remove data usage when kernel module not enabled
1016 final INetworkManagementService netManager = INetworkManagementService.Stub
1017 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1018 try {
1019 if (!netManager.isBandwidthControlEnabled()) {
1020 target.remove(i);
1021 }
1022 } catch (RemoteException e) {
1023 // ignored
1024 }
1025 } else if (id == R.id.battery_settings) {
1026 // Remove battery settings when battery is not available. (e.g. TV)
1027
1028 if (!mBatteryPresent) {
1029 target.remove(i);
1030 }
1031 } else if (id == R.id.account_settings) {
1032 int headerIndex = i + 1;
1033 i = insertAccountsHeaders(target, headerIndex);
1034 } else if (id == R.id.home_settings) {
1035 if (!updateHomeSettingHeaders(header)) {
1036 target.remove(i);
1037 }
1038 } else if (id == R.id.user_settings) {
1039 if (!UserHandle.MU_ENABLED
1040 || !UserManager.supportsMultipleUsers()
1041 || Utils.isMonkeyRunning()) {
1042 target.remove(i);
1043 }
1044 } else if (id == R.id.nfc_payment_settings) {
1045 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1046 target.remove(i);
1047 } else {
1048 // Only show if NFC is on and we have the HCE feature
1049 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1050 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1051 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1052 target.remove(i);
1053 }
1054 }
1055 } else if (id == R.id.development_settings) {
1056 if (!showDev) {
1057 target.remove(i);
1058 }
1059 } else if (id == R.id.account_add) {
1060 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1061 target.remove(i);
1062 }
1063 }
1064
1065 if (i < target.size() && target.get(i) == header
1066 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1067 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1068 target.remove(i);
1069 }
1070
1071 // Increment if the current one wasn't removed by the Utils code.
1072 if (i < target.size() && target.get(i) == header) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001073 mHeaderIndexMap.put(id, i);
1074 i++;
1075 }
1076 }
1077 }
1078
1079 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1080 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1081 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1082 for (String accountType : accountTypes) {
1083 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1084 if (label == null) {
1085 continue;
1086 }
1087
1088 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1089 boolean skipToAccount = accounts.length == 1
1090 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1091 Header accHeader = new Header();
1092 accHeader.title = label;
1093 if (accHeader.extras == null) {
1094 accHeader.extras = new Bundle();
1095 }
1096 if (skipToAccount) {
1097 accHeader.fragment = AccountSyncSettings.class.getName();
1098 accHeader.fragmentArguments = new Bundle();
1099 // Need this for the icon
1100 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1101 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1102 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1103 accounts[0]);
1104 } else {
1105 accHeader.fragment = ManageAccountsSettings.class.getName();
1106 accHeader.fragmentArguments = new Bundle();
1107 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1108 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1109 accountType);
1110 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1111 label.toString());
1112 }
1113 accountHeaders.add(accHeader);
1114 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1115 }
1116
1117 // Sort by label
1118 Collections.sort(accountHeaders, new Comparator<Header>() {
1119 @Override
1120 public int compare(Header h1, Header h2) {
1121 return h1.title.toString().compareTo(h2.title.toString());
1122 }
1123 });
1124
1125 for (Header header : accountHeaders) {
1126 target.add(headerIndex++, header);
1127 }
1128 if (!mListeningToAccountUpdates) {
1129 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1130 mListeningToAccountUpdates = true;
1131 }
1132 return headerIndex;
1133 }
1134
1135 private boolean updateHomeSettingHeaders(Header header) {
1136 // Once we decide to show Home settings, keep showing it forever
1137 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1138 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1139 return true;
1140 }
1141
1142 try {
1143 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1144 getPackageManager().getHomeActivities(homeApps);
1145 if (homeApps.size() < 2) {
1146 // When there's only one available home app, omit this settings
1147 // category entirely at the top level UI. If the user just
1148 // uninstalled the penultimate home app candidiate, we also
1149 // now tell them about why they aren't seeing 'Home' in the list.
1150 if (sShowNoHomeNotice) {
1151 sShowNoHomeNotice = false;
1152 NoHomeDialogFragment.show(this);
1153 }
1154 return false;
1155 } else {
1156 // Okay, we're allowing the Home settings category. Tell it, when
1157 // invoked via this front door, that we'll need to be told about the
1158 // case when the user uninstalls all but one home app.
1159 if (header.fragmentArguments == null) {
1160 header.fragmentArguments = new Bundle();
1161 }
1162 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1163 }
1164 } catch (Exception e) {
1165 // Can't look up the home activity; bail on configuring the icon
1166 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1167 }
1168
1169 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1170 return true;
1171 }
1172
1173 private void getMetaData() {
1174 try {
1175 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1176 PackageManager.GET_META_DATA);
1177 if (ai == null || ai.metaData == null) return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001178 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1179 } catch (NameNotFoundException nnfe) {
1180 // No recovery
1181 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1182 }
1183 }
1184
1185 // give subclasses access to the Next button
1186 public boolean hasNextButton() {
1187 return mNextButton != null;
1188 }
1189
1190 public Button getNextButton() {
1191 return mNextButton;
1192 }
1193
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001194 public HeaderAdapter getHeaderAdapter() {
1195 return mHeaderAdapter;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001196 }
1197
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001198 public void onListItemClick(ListView l, View v, int position, long id) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001199 if (!isResumed()) {
1200 return;
1201 }
1202 Object item = mHeaderAdapter.getItem(position);
1203 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001204 mSelectedHeader = (Header) item;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001205 onHeaderClick(mSelectedHeader);
1206 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001207 }
1208 }
1209
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001210 @Override
1211 public boolean shouldUpRecreateTask(Intent targetIntent) {
1212 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1213 }
1214
1215 @Override
1216 public void onAccountsUpdated(Account[] accounts) {
1217 // TODO: watch for package upgrades to invalidate cache; see 7206643
1218 mAuthenticatorHelper.updateAuthDescriptions(this);
1219 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1220 invalidateHeaders();
1221 }
1222
1223 public static void requestHomeNotice() {
1224 sShowNoHomeNotice = true;
1225 }
1226
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001227 @Override
1228 public boolean onQueryTextSubmit(String query) {
1229 switchToSearchResultsFragmentIfNeeded();
1230 mSearchQuery = query;
1231 return mSearchResultsFragment.onQueryTextSubmit(query);
1232 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001233
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001234 @Override
1235 public boolean onQueryTextChange(String newText) {
1236 mSearchQuery = newText;
1237 return false;
1238 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001239
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001240 @Override
1241 public boolean onClose() {
1242 return false;
1243 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001244
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001245 @Override
1246 public boolean onMenuItemActionExpand(MenuItem item) {
1247 if (item.getItemId() == mSearchMenuItem.getItemId()) {
1248 if (mSearchResultsFragment == null) {
1249 switchToSearchResultsFragmentIfNeeded();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001250 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001251 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001252 return true;
1253 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001254
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001255 @Override
1256 public boolean onMenuItemActionCollapse(MenuItem item) {
1257 if (item.getItemId() == mSearchMenuItem.getItemId()) {
1258 if (mIsShowingSearchResults) {
1259 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001260 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001261 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001262 return true;
1263 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001264
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001265 private void switchToSearchResultsFragmentIfNeeded() {
1266 if (!mIsShowingSearchResults) {
1267 Fragment current = getFragmentManager().findFragmentById(R.id.prefs);
1268 if (current != null && current instanceof SearchResultsSummary) {
1269 mSearchResultsFragment = (SearchResultsSummary) current;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001270 } else {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001271 String title = getString(R.string.search_results_title);
1272 mSearchResultsFragment = (SearchResultsSummary) switchToFragment(
1273 SearchResultsSummary.class.getName(), null, false, true, title, true);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001274 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001275 mIsShowingSearchResults = true;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001276 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001277 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001278
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001279 public void needToRevertToInitialFragment() {
1280 mNeedToRevertToInitialFragment = true;
1281 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001282
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001283 private void revertToInitialFragment() {
1284 mNeedToRevertToInitialFragment = false;
1285 getFragmentManager().popBackStack(SettingsActivity.BACK_STACK_PREFS,
1286 FragmentManager.POP_BACK_STACK_INCLUSIVE);
1287 mSearchResultsFragment = null;
1288 mIsShowingSearchResults = false;
1289 mSearchMenuItem.collapseActionView();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001290 }
1291}