blob: 206865facfcab8856ec4caa0159f652ad0954cc2 [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 Megliod25314d2014-03-21 19:24:43 -070027import android.app.SearchManager;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080028import android.app.admin.DevicePolicyManager;
29import android.content.BroadcastReceiver;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070030import android.content.ComponentName;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080031import android.content.Context;
32import android.content.Intent;
33import android.content.IntentFilter;
34import android.content.SharedPreferences;
35import android.content.pm.ActivityInfo;
36import android.content.pm.PackageManager;
37import android.content.pm.PackageManager.NameNotFoundException;
38import android.content.pm.ResolveInfo;
39import android.content.res.Configuration;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080040import android.content.res.TypedArray;
41import android.content.res.XmlResourceParser;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080042import android.nfc.NfcAdapter;
43import android.os.Bundle;
44import android.os.Handler;
45import android.os.INetworkManagementService;
46import android.os.Message;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080047import android.os.RemoteException;
48import android.os.ServiceManager;
49import android.os.UserHandle;
50import android.os.UserManager;
51import android.preference.Preference;
52import android.preference.PreferenceFragment;
53import android.preference.PreferenceManager;
54import android.preference.PreferenceScreen;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080055import android.text.TextUtils;
56import android.util.AttributeSet;
57import android.util.Log;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080058import android.util.TypedValue;
59import android.util.Xml;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070060import android.view.Menu;
61import android.view.MenuInflater;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080062import android.view.MenuItem;
63import android.view.View;
64import android.view.View.OnClickListener;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080065import android.widget.Button;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080066import android.widget.ListView;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080067
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070068import android.widget.SearchView;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080069import com.android.internal.util.ArrayUtils;
70import com.android.internal.util.XmlUtils;
71import com.android.settings.accessibility.AccessibilitySettings;
72import com.android.settings.accessibility.CaptionPropertiesFragment;
73import com.android.settings.accounts.AccountSyncSettings;
74import com.android.settings.accounts.AuthenticatorHelper;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080075import com.android.settings.accounts.ManageAccountsSettings;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070076import com.android.settings.applications.InstalledAppDetails;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080077import com.android.settings.applications.ManageApplications;
78import com.android.settings.applications.ProcessStatsUi;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080079import com.android.settings.bluetooth.BluetoothSettings;
80import com.android.settings.dashboard.DashboardSummary;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070081import com.android.settings.dashboard.Header;
82import com.android.settings.dashboard.HeaderAdapter;
83import com.android.settings.dashboard.NoHomeDialogFragment;
84import com.android.settings.dashboard.SearchResultsSummary;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080085import com.android.settings.deviceinfo.Memory;
86import com.android.settings.deviceinfo.UsbSettings;
87import com.android.settings.fuelgauge.PowerUsageSummary;
Fabrice Di Megliofa7dc242014-03-12 19:24:43 -070088import com.android.settings.search.Index;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080089import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
90import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
91import com.android.settings.inputmethod.SpellCheckersSettings;
92import com.android.settings.inputmethod.UserDictionaryList;
93import com.android.settings.location.LocationSettings;
94import com.android.settings.nfc.AndroidBeam;
95import com.android.settings.nfc.PaymentSettings;
96import com.android.settings.print.PrintJobSettingsFragment;
97import com.android.settings.print.PrintSettingsFragment;
98import com.android.settings.tts.TextToSpeechSettings;
99import com.android.settings.users.UserSettings;
100import com.android.settings.vpn2.VpnSettings;
101import com.android.settings.wfd.WifiDisplaySettings;
102import com.android.settings.wifi.AdvancedWifiSettings;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800103import com.android.settings.wifi.WifiSettings;
104import com.android.settings.wifi.p2p.WifiP2pSettings;
105import org.xmlpull.v1.XmlPullParser;
106import org.xmlpull.v1.XmlPullParserException;
107
108import java.io.IOException;
109import java.util.ArrayList;
110import java.util.Collections;
111import java.util.Comparator;
112import java.util.HashMap;
113import java.util.List;
114
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700115import static com.android.settings.dashboard.Header.HEADER_ID_UNDEFINED;
116
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800117public class SettingsActivity extends Activity
118 implements PreferenceManager.OnPreferenceTreeClickListener,
119 PreferenceFragment.OnPreferenceStartFragmentCallback,
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700120 ButtonBarHandler, OnAccountsUpdateListener, FragmentManager.OnBackStackChangedListener,
121 SearchView.OnQueryTextListener, SearchView.OnCloseListener,
122 MenuItem.OnActionExpandListener {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800123
124 private static final String LOG_TAG = "Settings";
125
126 // Constants for state save/restore
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700127 private static final String SAVE_KEY_HEADERS = ":settings:headers";
128 private static final String SAVE_KEY_CURRENT_HEADER = ":settings:cur_header";
129 private static final String SAVE_KEY_SEARCH_MENU_EXPANDED = ":settings:search_menu_expanded";
130 private static final String SAVE_KEY_SEARCH_QUERY = ":settings:search_query";
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800131
132 /**
133 * When starting this activity, the invoking Intent can contain this extra
134 * string to specify which fragment should be initially displayed.
135 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
136 * will call isValidFragment() to confirm that the fragment class name is valid for this
137 * activity.
138 */
139 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
140
141 /**
142 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
143 * this extra can also be specified to supply a Bundle of arguments to pass
144 * to that fragment when it is instantiated during the initial creation
145 * of the activity.
146 */
147 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
148
149 /**
150 * When starting this activity, the invoking Intent can contain this extra
151 * boolean that the header list should not be displayed. This is most often
152 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
153 * the activity to display a specific fragment that the user has navigated
154 * to.
155 */
156 public static final String EXTRA_NO_HEADERS = ":settings:no_headers";
157
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800158 public static final String BACK_STACK_PREFS = ":settings:prefs";
159
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800160 // extras that allow any preference activity to be launched as part of a wizard
161
162 // show Back and Next buttons? takes boolean parameter
163 // Back will then return RESULT_CANCELED and Next RESULT_OK
164 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
165
166 // add a Skip button?
167 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
168
169 // specify custom text for the Back or Next buttons, or cause a button to not appear
170 // at all by setting it to null
171 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
172 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
173
174 /**
175 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
176 * this extra can also be specify to supply the title to be shown for
177 * that fragment.
178 */
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700179 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800180
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800181 private static final String META_DATA_KEY_HEADER_ID =
182 "com.android.settings.TOP_LEVEL_HEADER_ID";
183
184 private static final String META_DATA_KEY_FRAGMENT_CLASS =
185 "com.android.settings.FRAGMENT_CLASS";
186
187 private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
188
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800189 private static boolean sShowNoHomeNotice = false;
190
191 private String mFragmentClass;
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800192 private Header mSelectedHeader;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800193 private Header mCurrentHeader;
194
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800195 private CharSequence mInitialTitle;
196
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800197 // Show only these settings for restricted users
198 private int[] SETTINGS_FOR_RESTRICTED = {
199 R.id.wireless_section,
200 R.id.wifi_settings,
201 R.id.bluetooth_settings,
202 R.id.data_usage_settings,
203 R.id.wireless_settings,
204 R.id.device_section,
205 R.id.sound_settings,
206 R.id.display_settings,
207 R.id.storage_settings,
208 R.id.application_settings,
209 R.id.battery_settings,
210 R.id.personal_section,
211 R.id.location_settings,
212 R.id.security_settings,
213 R.id.language_settings,
214 R.id.user_settings,
215 R.id.account_settings,
216 R.id.account_add,
217 R.id.system_section,
218 R.id.date_time_settings,
219 R.id.about_settings,
220 R.id.accessibility_settings,
221 R.id.print_settings,
222 R.id.nfc_payment_settings,
Fabrice Di Meglio2858b792014-02-18 19:35:08 -0800223 R.id.home_settings,
224 R.id.dashboard
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800225 };
226
227 private static final String[] ENTRY_FRAGMENTS = {
228 WirelessSettings.class.getName(),
229 WifiSettings.class.getName(),
230 AdvancedWifiSettings.class.getName(),
231 BluetoothSettings.class.getName(),
232 TetherSettings.class.getName(),
233 WifiP2pSettings.class.getName(),
234 VpnSettings.class.getName(),
235 DateTimeSettings.class.getName(),
236 LocalePicker.class.getName(),
237 InputMethodAndLanguageSettings.class.getName(),
238 SpellCheckersSettings.class.getName(),
239 UserDictionaryList.class.getName(),
240 UserDictionarySettings.class.getName(),
241 SoundSettings.class.getName(),
242 DisplaySettings.class.getName(),
243 DeviceInfoSettings.class.getName(),
244 ManageApplications.class.getName(),
245 ProcessStatsUi.class.getName(),
246 NotificationStation.class.getName(),
247 LocationSettings.class.getName(),
248 SecuritySettings.class.getName(),
249 PrivacySettings.class.getName(),
250 DeviceAdminSettings.class.getName(),
251 AccessibilitySettings.class.getName(),
252 CaptionPropertiesFragment.class.getName(),
253 com.android.settings.accessibility.ToggleInversionPreferenceFragment.class.getName(),
254 com.android.settings.accessibility.ToggleContrastPreferenceFragment.class.getName(),
255 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
256 TextToSpeechSettings.class.getName(),
257 Memory.class.getName(),
258 DevelopmentSettings.class.getName(),
259 UsbSettings.class.getName(),
260 AndroidBeam.class.getName(),
261 WifiDisplaySettings.class.getName(),
262 PowerUsageSummary.class.getName(),
263 AccountSyncSettings.class.getName(),
264 CryptKeeperSettings.class.getName(),
265 DataUsageSummary.class.getName(),
266 DreamSettings.class.getName(),
267 UserSettings.class.getName(),
268 NotificationAccessSettings.class.getName(),
269 ManageAccountsSettings.class.getName(),
270 PrintSettingsFragment.class.getName(),
271 PrintJobSettingsFragment.class.getName(),
272 TrustedCredentialsSettings.class.getName(),
273 PaymentSettings.class.getName(),
274 KeyboardLayoutPickerFragment.class.getName(),
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700275 ZenModeSettings.class.getName(),
276 NotificationSettings.class.getName(),
277 ChooseLockPassword.ChooseLockPasswordFragment.class.getName(),
278 ChooseLockPattern.ChooseLockPatternFragment.class.getName(),
279 InstalledAppDetails.class.getName()
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800280 };
281
282 private SharedPreferences mDevelopmentPreferences;
283 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
284
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800285 private AuthenticatorHelper mAuthenticatorHelper;
286 private boolean mListeningToAccountUpdates;
287
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800288 private boolean mBatteryPresent = true;
289 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
290
291 @Override
292 public void onReceive(Context context, Intent intent) {
293 String action = intent.getAction();
294 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
295 boolean batteryPresent = Utils.isBatteryPresent(intent);
296
297 if (mBatteryPresent != batteryPresent) {
298 mBatteryPresent = batteryPresent;
299 invalidateHeaders();
300 }
301 }
302 }
303 };
304
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700305 private Button mNextButton;
306 private ActionBar mActionBar;
307
308 private SearchView mSearchView;
309 private MenuItem mSearchMenuItem;
310 private boolean mSearchMenuItemExpanded = false;
311 private boolean mIsShowingSearchResults = false;
312 private SearchResultsSummary mSearchResultsFragment;
313 private String mSearchQuery;
314
315 // Headers
316 protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800317 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800318 private HeaderAdapter mHeaderAdapter;
319
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800320 private static final int MSG_BUILD_HEADERS = 1;
321 private Handler mHandler = new Handler() {
322 @Override
323 public void handleMessage(Message msg) {
324 switch (msg.what) {
325 case MSG_BUILD_HEADERS: {
326 mHeaders.clear();
327 onBuildHeaders(mHeaders);
328 mHeaderAdapter.notifyDataSetChanged();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800329 } break;
330 }
331 }
332 };
333
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700334 private boolean mNeedToRevertToInitialFragment = false;
335
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800336 @Override
337 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
338 // Override the fragment title for Wallpaper settings
339 int titleRes = pref.getTitleRes();
340 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
341 titleRes = R.string.wallpaper_settings_fragment_title;
342 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
343 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
344 if (UserManager.get(this).isLinkedUser()) {
345 titleRes = R.string.profile_info_settings_title;
346 } else {
347 titleRes = R.string.user_info_settings_title;
348 }
349 }
350 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
351 null, 0);
352 return true;
353 }
354
355 @Override
356 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
357 return false;
358 }
359
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800360 private void invalidateHeaders() {
361 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
362 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
363 }
364 }
365
366 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800367 public void onConfigurationChanged(Configuration newConfig) {
368 super.onConfigurationChanged(newConfig);
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800369 Index.getInstance(this).update();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800370 }
371
372 @Override
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700373 protected void onStart() {
374 super.onStart();
375
376 if (mNeedToRevertToInitialFragment) {
377 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800378 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800379 }
380
381 @Override
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700382 public boolean onCreateOptionsMenu(Menu menu) {
383 MenuInflater inflater = getMenuInflater();
384 inflater.inflate(R.menu.options_menu, menu);
385
386 // Cache the search query (can be overriden by the OnQueryTextListener)
387 final String query = mSearchQuery;
388
Fabrice Di Meglio95937822014-03-31 19:46:42 -0700389 mSearchMenuItem = menu.findItem(R.id.search);
390 mSearchView = (SearchView) mSearchMenuItem.getActionView();
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700391
Fabrice Di Meglio95937822014-03-31 19:46:42 -0700392 mSearchMenuItem.setOnActionExpandListener(this);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700393 mSearchView.setOnQueryTextListener(this);
394 mSearchView.setOnCloseListener(this);
395
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700396 if (mSearchMenuItemExpanded) {
397 mSearchMenuItem.expandActionView();
398 }
399 mSearchView.setQuery(query, true /* submit */);
400
401 return true;
402 }
403
404 @Override
405 protected void onCreate(Bundle savedState) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800406 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
407 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
408 }
409
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800410 Index.getInstance(this).update();
411
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800412 mAuthenticatorHelper = new AuthenticatorHelper();
413 mAuthenticatorHelper.updateAuthDescriptions(this);
414 mAuthenticatorHelper.onAccountsUpdated(this, null);
415
416 DevicePolicyManager dpm =
417 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
Fabrice Di Megliodca28062014-02-21 17:42:56 -0800418
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700419 mHeaderAdapter = new HeaderAdapter(this, mHeaders, mAuthenticatorHelper, dpm);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800420
421 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
422 Context.MODE_PRIVATE);
423
424 getMetaData();
425
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700426 super.onCreate(savedState);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800427
428 setContentView(R.layout.settings_main);
429
430 getFragmentManager().addOnBackStackChangedListener(this);
431
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700432 boolean displayHomeAsUpEnabled = true;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800433
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700434 String initialFragmentName = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
435 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800436
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700437 if (savedState != null) {
438 mSearchMenuItemExpanded = savedState.getBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED);
439 mSearchQuery = savedState.getString(SAVE_KEY_SEARCH_QUERY);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800440
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700441 // We are restarting from a previous saved state; used that to
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800442 // initialize, instead of starting fresh.
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800443 mInitialTitle = getTitle();
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800444
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700445 ArrayList<Header> headers = savedState.getParcelableArrayList(SAVE_KEY_HEADERS);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800446 if (headers != null) {
447 mHeaders.addAll(headers);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700448 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800449 }
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())) {
458 displayHomeAsUpEnabled = false;
459 }
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
467 displayHomeAsUpEnabled = 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);
478 mActionBar.setDisplayHomeAsUpEnabled(displayHomeAsUpEnabled);
479
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 Meglio65027202014-02-11 15:19:46 -0800574 if (mCurrentHeader != null) {
575 int index = mHeaders.indexOf(mCurrentHeader);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800576 if (index >= 0) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700577 outState.putInt(SAVE_KEY_CURRENT_HEADER, index);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800578 }
579 }
580 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700581 outState.putBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED, mSearchMenuItem.isActionViewExpanded());
582 outState.putString(SAVE_KEY_SEARCH_QUERY, mSearchView.getQuery().toString());
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800583 }
584
585 @Override
586 public void onResume() {
587 super.onResume();
588
589 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
590 @Override
591 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
592 invalidateHeaders();
593 }
594 };
595 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
596 mDevelopmentPreferencesListener);
597
Matthew Xiea504c4d2014-02-14 16:32:32 -0800598 mHeaderAdapter.resume(this);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800599 invalidateHeaders();
600
601 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
602 }
603
604 @Override
605 public void onPause() {
606 super.onPause();
607
608 unregisterReceiver(mBatteryInfoReceiver);
609
610 mHeaderAdapter.pause();
611
612 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
613 mDevelopmentPreferencesListener);
614
615 mDevelopmentPreferencesListener = null;
616 }
617
618 @Override
619 public void onDestroy() {
620 super.onDestroy();
621 if (mListeningToAccountUpdates) {
622 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
623 }
624 }
625
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800626 protected boolean isValidFragment(String fragmentName) {
627 // Almost all fragments are wrapped in this,
628 // except for a few that have their own activities.
629 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
630 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
631 }
632 return false;
633 }
634
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800635 /**
636 * When in two-pane mode, switch to the fragment pane to show the given
637 * preference fragment.
638 *
639 * @param header The new header to display.
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800640 */
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700641 private void onHeaderClick(Header header) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800642 if (header == null) {
643 return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800644 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700645 if (header.fragment != null) {
646 startWithFragment(header.fragment, header.fragmentArguments, null, 0,
647 header.getTitle(getResources()));
648 } else if (header.intent != null) {
649 startActivity(header.intent);
650 } else {
651 throw new IllegalStateException(
652 "Can't switch to header that has no Fragment nor Intent");
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800653 }
654 }
655
656 /**
657 * Called to determine whether the header list should be hidden.
658 * The default implementation returns the
659 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
660 * This is set to false, for example, when the activity is being re-launched
661 * to show a particular preference activity.
662 */
663 public boolean onIsHidingHeaders() {
664 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
665 }
666
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800667 @Override
668 public Intent getIntent() {
669 Intent superIntent = super.getIntent();
670 String startingFragment = getStartingFragmentClass(superIntent);
671 // This is called from super.onCreate, isMultiPane() is not yet reliable
672 // Do not use onIsHidingHeaders either, which relies itself on this method
673 if (startingFragment != null) {
674 Intent modIntent = new Intent(superIntent);
675 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
676 Bundle args = superIntent.getExtras();
677 if (args != null) {
678 args = new Bundle(args);
679 } else {
680 args = new Bundle();
681 }
682 args.putParcelable("intent", superIntent);
683 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
684 return modIntent;
685 }
686 return superIntent;
687 }
688
689 /**
690 * Checks if the component name in the intent is different from the Settings class and
691 * returns the class name to load as a fragment.
692 */
693 private String getStartingFragmentClass(Intent intent) {
694 if (mFragmentClass != null) return mFragmentClass;
695
696 String intentClass = intent.getComponent().getClassName();
697 if (intentClass.equals(getClass().getName())) return null;
698
699 if ("com.android.settings.ManageApplications".equals(intentClass)
700 || "com.android.settings.RunningServices".equals(intentClass)
701 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
702 // Old names of manage apps.
703 intentClass = com.android.settings.applications.ManageApplications.class.getName();
704 }
705
706 return intentClass;
707 }
708
709 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000710 * Start a new fragment containing a preference panel. If the preferences
711 * are being displayed in multi-pane mode, the given fragment class will
712 * be instantiated and placed in the appropriate pane. If running in
713 * single-pane mode, a new activity will be launched in which to show the
714 * fragment.
715 *
716 * @param fragmentClass Full name of the class implementing the fragment.
717 * @param args Any desired arguments to supply to the fragment.
718 * @param titleRes Optional resource identifier of the title of this
719 * fragment.
720 * @param titleText Optional text of the title of this fragment.
721 * @param resultTo Optional fragment that result data should be sent to.
722 * If non-null, resultTo.onActivityResult() will be called when this
723 * preference panel is done. The launched panel must use
724 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
725 * @param resultRequestCode If resultTo is non-null, this is the caller's
726 * request code to be received with the resut.
727 */
728 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700729 CharSequence titleText, Fragment resultTo, int resultRequestCode) {
730 switchToFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000731 }
732
733 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800734 * Called by a preference panel fragment to finish itself.
735 *
736 * @param caller The fragment that is asking to be finished.
737 * @param resultCode Optional result code to send back to the original
738 * launching fragment.
739 * @param resultData Optional result data to send back to the original
740 * launching fragment.
741 */
742 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
743 setResult(resultCode, resultData);
744 }
745
746 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000747 * Start a new fragment.
748 *
749 * @param fragment The fragment to start
750 * @param push If true, the current fragment will be pushed onto the back stack. If false,
751 * the current fragment will be replaced.
752 */
753 public void startPreferenceFragment(Fragment fragment, boolean push) {
754 FragmentTransaction transaction = getFragmentManager().beginTransaction();
755 transaction.replace(R.id.prefs, fragment);
756 if (push) {
757 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
758 transaction.addToBackStack(BACK_STACK_PREFS);
759 } else {
760 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
761 }
762 transaction.commitAllowingStateLoss();
763 }
764
765 /**
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700766 * Start a new fragment. Used by #startPreferencePanel.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000767 *
768 * @param fragmentName The name of the fragment to display.
769 * @param args Optional arguments to supply to the fragment.
770 * @param resultTo Option fragment that should receive the result of
771 * the activity launch.
772 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
773 * report the result.
774 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
775 * valid one then it will be used to get the title. Otherwise the titleText
776 * argument will be used as the title.
777 * @param titleText string to display for the title of.
778 */
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700779 private void switchToFragment(String fragmentName, Bundle args, Fragment resultTo,
780 int resultRequestCode, int titleRes, CharSequence titleText) {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800781 final CharSequence cs;
782 if (titleRes != 0) {
783 cs = getText(titleRes);
784 } else {
785 cs = titleText;
786 }
787
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000788 Fragment f = Fragment.instantiate(this, fragmentName, args);
789 if (resultTo != null) {
790 f.setTargetFragment(resultTo, resultRequestCode);
791 }
792 FragmentTransaction transaction = getFragmentManager().beginTransaction();
793 transaction.replace(R.id.prefs, f);
794 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
795 transaction.addToBackStack(BACK_STACK_PREFS);
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800796 transaction.setBreadCrumbTitle(cs);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000797 transaction.commitAllowingStateLoss();
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000798 }
799
800 /**
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700801 * Switch to a specific Fragment with taking care of validation, Title and BackStack
802 */
803 private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
804 boolean addToBackStack, CharSequence title, boolean withTransition) {
805 if (validate && !isValidFragment(fragmentName)) {
806 throw new IllegalArgumentException("Invalid fragment for this activity: "
807 + fragmentName);
808 }
809 Fragment f = Fragment.instantiate(this, fragmentName, args);
810 FragmentTransaction transaction = getFragmentManager().beginTransaction();
811 transaction.replace(R.id.prefs, f);
812 if (withTransition) {
813 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
814 }
815 if (addToBackStack) {
816 transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
817 }
818 if (title != null) {
819 transaction.setBreadCrumbTitle(title);
820 }
821 transaction.commitAllowingStateLoss();
822 return f;
823 }
824
825 /**
826 * Start a new instance of this activity, showing only the given fragment.
827 * When launched in this mode, the given preference fragment will be instantiated and fill the
828 * entire activity.
829 *
830 * @param fragmentName The name of the fragment to display.
831 * @param args Optional arguments to supply to the fragment.
832 * @param resultTo Option fragment that should receive the result of
833 * the activity launch.
834 * @param resultRequestCode If resultTo is non-null, this is the request
835 * code in which to report the result.
836 * @param title String to display for the title of this set of preferences.
837 */
838 public void startWithFragment(String fragmentName, Bundle args,
839 Fragment resultTo, int resultRequestCode, CharSequence title) {
840 Intent intent = onBuildStartFragmentIntent(fragmentName, args, title);
841 if (resultTo == null) {
842 startActivity(intent);
843 } else {
844 resultTo.startActivityForResult(intent, resultRequestCode);
845 }
846 }
847
848 /**
849 * Build an Intent to launch a new activity showing the selected fragment.
850 * The implementation constructs an Intent that re-launches the current activity with the
851 * appropriate arguments to display the fragment.
852 *
853 * @param fragmentName The name of the fragment to display.
854 * @param args Optional arguments to supply to the fragment.
855 * @param title Optional title to show for this item.
856 * @return Returns an Intent that can be launched to display the given
857 * fragment.
858 */
859 public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args, CharSequence title) {
860 Intent intent = new Intent(Intent.ACTION_MAIN);
861 intent.setClass(this, SubSettings.class);
862 intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
863 intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
864 intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, title);
865 intent.putExtra(EXTRA_NO_HEADERS, true);
866 return intent;
867 }
868
869 /**
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800870 * Called when the activity needs its list of headers build.
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800871 *
872 * @param headers The list in which to place the headers.
873 */
874 private void onBuildHeaders(List<Header> headers) {
875 loadHeadersFromResource(R.xml.settings_headers, headers);
876 updateHeaderList(headers);
877 }
878
879 /**
880 * Parse the given XML file as a header description, adding each
881 * parsed Header into the target list.
882 *
883 * @param resid The XML resource to load and parse.
884 * @param target The list in which the parsed headers should be placed.
885 */
886 private void loadHeadersFromResource(int resid, List<Header> target) {
887 XmlResourceParser parser = null;
888 try {
889 parser = getResources().getXml(resid);
890 AttributeSet attrs = Xml.asAttributeSet(parser);
891
892 int type;
893 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
894 && type != XmlPullParser.START_TAG) {
895 // Parse next until start tag is found
896 }
897
898 String nodeName = parser.getName();
899 if (!"preference-headers".equals(nodeName)) {
900 throw new RuntimeException(
901 "XML document must start with <preference-headers> tag; found"
902 + nodeName + " at " + parser.getPositionDescription());
903 }
904
905 Bundle curBundle = null;
906
907 final int outerDepth = parser.getDepth();
908 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
909 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
910 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
911 continue;
912 }
913
914 nodeName = parser.getName();
915 if ("header".equals(nodeName)) {
916 Header header = new Header();
917
918 TypedArray sa = obtainStyledAttributes(
919 attrs, com.android.internal.R.styleable.PreferenceHeader);
920 header.id = sa.getResourceId(
921 com.android.internal.R.styleable.PreferenceHeader_id,
922 (int)HEADER_ID_UNDEFINED);
923 TypedValue tv = sa.peekValue(
924 com.android.internal.R.styleable.PreferenceHeader_title);
925 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
926 if (tv.resourceId != 0) {
927 header.titleRes = tv.resourceId;
928 } else {
929 header.title = tv.string;
930 }
931 }
932 tv = sa.peekValue(
933 com.android.internal.R.styleable.PreferenceHeader_summary);
934 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
935 if (tv.resourceId != 0) {
936 header.summaryRes = tv.resourceId;
937 } else {
938 header.summary = tv.string;
939 }
940 }
941 header.iconRes = sa.getResourceId(
942 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
943 header.fragment = sa.getString(
944 com.android.internal.R.styleable.PreferenceHeader_fragment);
945 sa.recycle();
946
947 if (curBundle == null) {
948 curBundle = new Bundle();
949 }
950
951 final int innerDepth = parser.getDepth();
952 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
953 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
954 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
955 continue;
956 }
957
958 String innerNodeName = parser.getName();
959 if (innerNodeName.equals("extra")) {
960 getResources().parseBundleExtra("extra", attrs, curBundle);
961 XmlUtils.skipCurrentTag(parser);
962
963 } else if (innerNodeName.equals("intent")) {
964 header.intent = Intent.parseIntent(getResources(), parser, attrs);
965
966 } else {
967 XmlUtils.skipCurrentTag(parser);
968 }
969 }
970
971 if (curBundle.size() > 0) {
972 header.fragmentArguments = curBundle;
973 curBundle = null;
974 }
975
976 target.add(header);
977 } else {
978 XmlUtils.skipCurrentTag(parser);
979 }
980 }
981
982 } catch (XmlPullParserException e) {
983 throw new RuntimeException("Error parsing headers", e);
984 } catch (IOException e) {
985 throw new RuntimeException("Error parsing headers", e);
986 } finally {
987 if (parser != null) parser.close();
988 }
989 }
990
991 private void updateHeaderList(List<Header> target) {
992 final boolean showDev = mDevelopmentPreferences.getBoolean(
993 DevelopmentSettings.PREF_SHOW,
994 android.os.Build.TYPE.equals("eng"));
995 int i = 0;
996
997 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
998 mHeaderIndexMap.clear();
999 while (i < target.size()) {
1000 Header header = target.get(i);
1001 // Ids are integers, so downcasting
1002 int id = (int) header.id;
1003 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1004 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1005 } else if (id == R.id.wifi_settings) {
1006 // Remove WiFi Settings if WiFi service is not available.
1007 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1008 target.remove(i);
1009 }
1010 } else if (id == R.id.bluetooth_settings) {
1011 // Remove Bluetooth Settings if Bluetooth service is not available.
1012 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1013 target.remove(i);
1014 }
1015 } else if (id == R.id.data_usage_settings) {
1016 // Remove data usage when kernel module not enabled
1017 final INetworkManagementService netManager = INetworkManagementService.Stub
1018 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1019 try {
1020 if (!netManager.isBandwidthControlEnabled()) {
1021 target.remove(i);
1022 }
1023 } catch (RemoteException e) {
1024 // ignored
1025 }
1026 } else if (id == R.id.battery_settings) {
1027 // Remove battery settings when battery is not available. (e.g. TV)
1028
1029 if (!mBatteryPresent) {
1030 target.remove(i);
1031 }
1032 } else if (id == R.id.account_settings) {
1033 int headerIndex = i + 1;
1034 i = insertAccountsHeaders(target, headerIndex);
1035 } else if (id == R.id.home_settings) {
1036 if (!updateHomeSettingHeaders(header)) {
1037 target.remove(i);
1038 }
1039 } else if (id == R.id.user_settings) {
1040 if (!UserHandle.MU_ENABLED
1041 || !UserManager.supportsMultipleUsers()
1042 || Utils.isMonkeyRunning()) {
1043 target.remove(i);
1044 }
1045 } else if (id == R.id.nfc_payment_settings) {
1046 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1047 target.remove(i);
1048 } else {
1049 // Only show if NFC is on and we have the HCE feature
1050 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1051 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1052 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1053 target.remove(i);
1054 }
1055 }
1056 } else if (id == R.id.development_settings) {
1057 if (!showDev) {
1058 target.remove(i);
1059 }
1060 } else if (id == R.id.account_add) {
1061 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1062 target.remove(i);
1063 }
1064 }
1065
1066 if (i < target.size() && target.get(i) == header
1067 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1068 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1069 target.remove(i);
1070 }
1071
1072 // Increment if the current one wasn't removed by the Utils code.
1073 if (i < target.size() && target.get(i) == header) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001074 mHeaderIndexMap.put(id, i);
1075 i++;
1076 }
1077 }
1078 }
1079
1080 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1081 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1082 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1083 for (String accountType : accountTypes) {
1084 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1085 if (label == null) {
1086 continue;
1087 }
1088
1089 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1090 boolean skipToAccount = accounts.length == 1
1091 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1092 Header accHeader = new Header();
1093 accHeader.title = label;
1094 if (accHeader.extras == null) {
1095 accHeader.extras = new Bundle();
1096 }
1097 if (skipToAccount) {
1098 accHeader.fragment = AccountSyncSettings.class.getName();
1099 accHeader.fragmentArguments = new Bundle();
1100 // Need this for the icon
1101 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1102 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1103 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1104 accounts[0]);
1105 } else {
1106 accHeader.fragment = ManageAccountsSettings.class.getName();
1107 accHeader.fragmentArguments = new Bundle();
1108 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1109 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1110 accountType);
1111 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1112 label.toString());
1113 }
1114 accountHeaders.add(accHeader);
1115 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1116 }
1117
1118 // Sort by label
1119 Collections.sort(accountHeaders, new Comparator<Header>() {
1120 @Override
1121 public int compare(Header h1, Header h2) {
1122 return h1.title.toString().compareTo(h2.title.toString());
1123 }
1124 });
1125
1126 for (Header header : accountHeaders) {
1127 target.add(headerIndex++, header);
1128 }
1129 if (!mListeningToAccountUpdates) {
1130 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1131 mListeningToAccountUpdates = true;
1132 }
1133 return headerIndex;
1134 }
1135
1136 private boolean updateHomeSettingHeaders(Header header) {
1137 // Once we decide to show Home settings, keep showing it forever
1138 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1139 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1140 return true;
1141 }
1142
1143 try {
1144 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1145 getPackageManager().getHomeActivities(homeApps);
1146 if (homeApps.size() < 2) {
1147 // When there's only one available home app, omit this settings
1148 // category entirely at the top level UI. If the user just
1149 // uninstalled the penultimate home app candidiate, we also
1150 // now tell them about why they aren't seeing 'Home' in the list.
1151 if (sShowNoHomeNotice) {
1152 sShowNoHomeNotice = false;
1153 NoHomeDialogFragment.show(this);
1154 }
1155 return false;
1156 } else {
1157 // Okay, we're allowing the Home settings category. Tell it, when
1158 // invoked via this front door, that we'll need to be told about the
1159 // case when the user uninstalls all but one home app.
1160 if (header.fragmentArguments == null) {
1161 header.fragmentArguments = new Bundle();
1162 }
1163 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1164 }
1165 } catch (Exception e) {
1166 // Can't look up the home activity; bail on configuring the icon
1167 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1168 }
1169
1170 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1171 return true;
1172 }
1173
1174 private void getMetaData() {
1175 try {
1176 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1177 PackageManager.GET_META_DATA);
1178 if (ai == null || ai.metaData == null) return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001179 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1180 } catch (NameNotFoundException nnfe) {
1181 // No recovery
1182 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1183 }
1184 }
1185
1186 // give subclasses access to the Next button
1187 public boolean hasNextButton() {
1188 return mNextButton != null;
1189 }
1190
1191 public Button getNextButton() {
1192 return mNextButton;
1193 }
1194
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001195 public HeaderAdapter getHeaderAdapter() {
1196 return mHeaderAdapter;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001197 }
1198
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001199 public void onListItemClick(ListView l, View v, int position, long id) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001200 if (!isResumed()) {
1201 return;
1202 }
1203 Object item = mHeaderAdapter.getItem(position);
1204 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001205 mSelectedHeader = (Header) item;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001206 onHeaderClick(mSelectedHeader);
1207 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001208 }
1209 }
1210
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001211 @Override
1212 public boolean shouldUpRecreateTask(Intent targetIntent) {
1213 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1214 }
1215
1216 @Override
1217 public void onAccountsUpdated(Account[] accounts) {
1218 // TODO: watch for package upgrades to invalidate cache; see 7206643
1219 mAuthenticatorHelper.updateAuthDescriptions(this);
1220 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1221 invalidateHeaders();
1222 }
1223
1224 public static void requestHomeNotice() {
1225 sShowNoHomeNotice = true;
1226 }
1227
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001228 @Override
1229 public boolean onQueryTextSubmit(String query) {
1230 switchToSearchResultsFragmentIfNeeded();
1231 mSearchQuery = query;
1232 return mSearchResultsFragment.onQueryTextSubmit(query);
1233 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001234
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001235 @Override
1236 public boolean onQueryTextChange(String newText) {
1237 mSearchQuery = newText;
1238 return false;
1239 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001240
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001241 @Override
1242 public boolean onClose() {
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 onMenuItemActionExpand(MenuItem item) {
1248 if (item.getItemId() == mSearchMenuItem.getItemId()) {
1249 if (mSearchResultsFragment == null) {
1250 switchToSearchResultsFragmentIfNeeded();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001251 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001252 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001253 return true;
1254 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001255
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001256 @Override
1257 public boolean onMenuItemActionCollapse(MenuItem item) {
1258 if (item.getItemId() == mSearchMenuItem.getItemId()) {
1259 if (mIsShowingSearchResults) {
1260 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001261 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001262 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001263 return true;
1264 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001265
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001266 private void switchToSearchResultsFragmentIfNeeded() {
1267 if (!mIsShowingSearchResults) {
1268 Fragment current = getFragmentManager().findFragmentById(R.id.prefs);
1269 if (current != null && current instanceof SearchResultsSummary) {
1270 mSearchResultsFragment = (SearchResultsSummary) current;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001271 } else {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001272 String title = getString(R.string.search_results_title);
1273 mSearchResultsFragment = (SearchResultsSummary) switchToFragment(
1274 SearchResultsSummary.class.getName(), null, false, true, title, true);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001275 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001276 mIsShowingSearchResults = true;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001277 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001278 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001279
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001280 public void needToRevertToInitialFragment() {
1281 mNeedToRevertToInitialFragment = true;
1282 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001283
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001284 private void revertToInitialFragment() {
1285 mNeedToRevertToInitialFragment = false;
1286 getFragmentManager().popBackStack(SettingsActivity.BACK_STACK_PREFS,
1287 FragmentManager.POP_BACK_STACK_INCLUSIVE);
1288 mSearchResultsFragment = null;
1289 mIsShowingSearchResults = false;
1290 mSearchMenuItem.collapseActionView();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001291 }
1292}