blob: 62b37af72ba3511e8f99f9c6568b92e78e9e124c [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 Megliod6985df2014-04-03 16:43:26 -0700188 private static final String EMPTY_QUERY = "";
189
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800190 private static boolean sShowNoHomeNotice = false;
191
192 private String mFragmentClass;
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800193 private Header mSelectedHeader;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800194 private Header mCurrentHeader;
195
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800196 private CharSequence mInitialTitle;
197
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800198 // Show only these settings for restricted users
199 private int[] SETTINGS_FOR_RESTRICTED = {
200 R.id.wireless_section,
201 R.id.wifi_settings,
202 R.id.bluetooth_settings,
203 R.id.data_usage_settings,
204 R.id.wireless_settings,
205 R.id.device_section,
206 R.id.sound_settings,
207 R.id.display_settings,
208 R.id.storage_settings,
209 R.id.application_settings,
210 R.id.battery_settings,
211 R.id.personal_section,
212 R.id.location_settings,
213 R.id.security_settings,
214 R.id.language_settings,
215 R.id.user_settings,
216 R.id.account_settings,
217 R.id.account_add,
218 R.id.system_section,
219 R.id.date_time_settings,
220 R.id.about_settings,
221 R.id.accessibility_settings,
222 R.id.print_settings,
223 R.id.nfc_payment_settings,
Fabrice Di Meglio2858b792014-02-18 19:35:08 -0800224 R.id.home_settings,
225 R.id.dashboard
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800226 };
227
228 private static final String[] ENTRY_FRAGMENTS = {
229 WirelessSettings.class.getName(),
230 WifiSettings.class.getName(),
231 AdvancedWifiSettings.class.getName(),
232 BluetoothSettings.class.getName(),
233 TetherSettings.class.getName(),
234 WifiP2pSettings.class.getName(),
235 VpnSettings.class.getName(),
236 DateTimeSettings.class.getName(),
237 LocalePicker.class.getName(),
238 InputMethodAndLanguageSettings.class.getName(),
239 SpellCheckersSettings.class.getName(),
240 UserDictionaryList.class.getName(),
241 UserDictionarySettings.class.getName(),
242 SoundSettings.class.getName(),
243 DisplaySettings.class.getName(),
244 DeviceInfoSettings.class.getName(),
245 ManageApplications.class.getName(),
246 ProcessStatsUi.class.getName(),
247 NotificationStation.class.getName(),
248 LocationSettings.class.getName(),
249 SecuritySettings.class.getName(),
250 PrivacySettings.class.getName(),
251 DeviceAdminSettings.class.getName(),
252 AccessibilitySettings.class.getName(),
253 CaptionPropertiesFragment.class.getName(),
254 com.android.settings.accessibility.ToggleInversionPreferenceFragment.class.getName(),
255 com.android.settings.accessibility.ToggleContrastPreferenceFragment.class.getName(),
256 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
257 TextToSpeechSettings.class.getName(),
258 Memory.class.getName(),
259 DevelopmentSettings.class.getName(),
260 UsbSettings.class.getName(),
261 AndroidBeam.class.getName(),
262 WifiDisplaySettings.class.getName(),
263 PowerUsageSummary.class.getName(),
264 AccountSyncSettings.class.getName(),
265 CryptKeeperSettings.class.getName(),
266 DataUsageSummary.class.getName(),
267 DreamSettings.class.getName(),
268 UserSettings.class.getName(),
269 NotificationAccessSettings.class.getName(),
270 ManageAccountsSettings.class.getName(),
271 PrintSettingsFragment.class.getName(),
272 PrintJobSettingsFragment.class.getName(),
273 TrustedCredentialsSettings.class.getName(),
274 PaymentSettings.class.getName(),
275 KeyboardLayoutPickerFragment.class.getName(),
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700276 ZenModeSettings.class.getName(),
277 NotificationSettings.class.getName(),
278 ChooseLockPassword.ChooseLockPasswordFragment.class.getName(),
279 ChooseLockPattern.ChooseLockPatternFragment.class.getName(),
280 InstalledAppDetails.class.getName()
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800281 };
282
283 private SharedPreferences mDevelopmentPreferences;
284 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
285
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800286 private AuthenticatorHelper mAuthenticatorHelper;
287 private boolean mListeningToAccountUpdates;
288
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800289 private boolean mBatteryPresent = true;
290 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
291
292 @Override
293 public void onReceive(Context context, Intent intent) {
294 String action = intent.getAction();
295 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
296 boolean batteryPresent = Utils.isBatteryPresent(intent);
297
298 if (mBatteryPresent != batteryPresent) {
299 mBatteryPresent = batteryPresent;
300 invalidateHeaders();
301 }
302 }
303 }
304 };
305
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700306 private Button mNextButton;
307 private ActionBar mActionBar;
308
309 private SearchView mSearchView;
310 private MenuItem mSearchMenuItem;
311 private boolean mSearchMenuItemExpanded = false;
312 private boolean mIsShowingSearchResults = false;
313 private SearchResultsSummary mSearchResultsFragment;
314 private String mSearchQuery;
315
316 // Headers
317 protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800318 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800319 private HeaderAdapter mHeaderAdapter;
320
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800321 private static final int MSG_BUILD_HEADERS = 1;
322 private Handler mHandler = new Handler() {
323 @Override
324 public void handleMessage(Message msg) {
325 switch (msg.what) {
326 case MSG_BUILD_HEADERS: {
327 mHeaders.clear();
328 onBuildHeaders(mHeaders);
329 mHeaderAdapter.notifyDataSetChanged();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800330 } break;
331 }
332 }
333 };
334
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700335 private boolean mNeedToRevertToInitialFragment = false;
336
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800337 @Override
338 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
339 // Override the fragment title for Wallpaper settings
340 int titleRes = pref.getTitleRes();
341 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
342 titleRes = R.string.wallpaper_settings_fragment_title;
343 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
344 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
345 if (UserManager.get(this).isLinkedUser()) {
346 titleRes = R.string.profile_info_settings_title;
347 } else {
348 titleRes = R.string.user_info_settings_title;
349 }
350 }
351 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
352 null, 0);
353 return true;
354 }
355
356 @Override
357 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
358 return false;
359 }
360
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800361 private void invalidateHeaders() {
362 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
363 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
364 }
365 }
366
367 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800368 public void onConfigurationChanged(Configuration newConfig) {
369 super.onConfigurationChanged(newConfig);
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800370 Index.getInstance(this).update();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800371 }
372
373 @Override
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700374 protected void onStart() {
375 super.onStart();
376
377 if (mNeedToRevertToInitialFragment) {
378 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800379 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800380 }
381
382 @Override
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700383 public boolean onCreateOptionsMenu(Menu menu) {
384 MenuInflater inflater = getMenuInflater();
385 inflater.inflate(R.menu.options_menu, menu);
386
387 // Cache the search query (can be overriden by the OnQueryTextListener)
388 final String query = mSearchQuery;
389
Fabrice Di Meglio95937822014-03-31 19:46:42 -0700390 mSearchMenuItem = menu.findItem(R.id.search);
391 mSearchView = (SearchView) mSearchMenuItem.getActionView();
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700392
Fabrice Di Meglio95937822014-03-31 19:46:42 -0700393 mSearchMenuItem.setOnActionExpandListener(this);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700394 mSearchView.setOnQueryTextListener(this);
395 mSearchView.setOnCloseListener(this);
396
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700397 if (mSearchMenuItemExpanded) {
398 mSearchMenuItem.expandActionView();
399 }
400 mSearchView.setQuery(query, true /* submit */);
401
402 return true;
403 }
404
405 @Override
406 protected void onCreate(Bundle savedState) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800407 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
408 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
409 }
410
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800411 Index.getInstance(this).update();
412
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800413 mAuthenticatorHelper = new AuthenticatorHelper();
414 mAuthenticatorHelper.updateAuthDescriptions(this);
415 mAuthenticatorHelper.onAccountsUpdated(this, null);
416
417 DevicePolicyManager dpm =
418 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
Fabrice Di Megliodca28062014-02-21 17:42:56 -0800419
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700420 mHeaderAdapter = new HeaderAdapter(this, mHeaders, mAuthenticatorHelper, dpm);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800421
422 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
423 Context.MODE_PRIVATE);
424
425 getMetaData();
426
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700427 super.onCreate(savedState);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800428
429 setContentView(R.layout.settings_main);
430
431 getFragmentManager().addOnBackStackChangedListener(this);
432
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700433 boolean displayHomeAsUpEnabled = true;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800434
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700435 String initialFragmentName = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
436 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800437
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700438 if (savedState != null) {
439 mSearchMenuItemExpanded = savedState.getBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED);
440 mSearchQuery = savedState.getString(SAVE_KEY_SEARCH_QUERY);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800441
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700442 // We are restarting from a previous saved state; used that to
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800443 // initialize, instead of starting fresh.
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800444 mInitialTitle = getTitle();
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800445
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700446 ArrayList<Header> headers = savedState.getParcelableArrayList(SAVE_KEY_HEADERS);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800447 if (headers != null) {
448 mHeaders.addAll(headers);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700449 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800450 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800451 } else {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800452 // We need to build the Headers in all cases
453 onBuildHeaders(mHeaders);
454
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700455 if (initialFragmentName != null) {
456 final ComponentName cn = getIntent().getComponent();
457 // No UP is we are launched thru a Settings shortcut
458 if (!cn.getClassName().equals(SubSettings.class.getName())) {
459 displayHomeAsUpEnabled = false;
460 }
461 final String initialTitle = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
462 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800463 setTitle(mInitialTitle);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700464 switchToFragment( initialFragmentName, initialArguments, true, false,
465 mInitialTitle, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000466 } else {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700467 // No UP if we are displaying the Headers
468 displayHomeAsUpEnabled = false;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000469 if (mHeaders.size() > 0) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700470 mInitialTitle = getText(R.string.dashboard_title);
471 switchToFragment(DashboardSummary.class.getName(), null, false, false,
472 mInitialTitle, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000473 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800474 }
475 }
476
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700477 mActionBar = getActionBar();
478 mActionBar.setHomeButtonEnabled(true);
479 mActionBar.setDisplayHomeAsUpEnabled(displayHomeAsUpEnabled);
480
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800481 // see if we should show Back/Next buttons
482 Intent intent = getIntent();
483 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
484
485 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
486 if (buttonBar != null) {
487 buttonBar.setVisibility(View.VISIBLE);
488
489 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
490 backButton.setOnClickListener(new OnClickListener() {
491 public void onClick(View v) {
492 setResult(RESULT_CANCELED);
493 finish();
494 }
495 });
496 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
497 skipButton.setOnClickListener(new OnClickListener() {
498 public void onClick(View v) {
499 setResult(RESULT_OK);
500 finish();
501 }
502 });
503 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
504 mNextButton.setOnClickListener(new OnClickListener() {
505 public void onClick(View v) {
506 setResult(RESULT_OK);
507 finish();
508 }
509 });
510
511 // set our various button parameters
512 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
513 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
514 if (TextUtils.isEmpty(buttonText)) {
515 mNextButton.setVisibility(View.GONE);
516 }
517 else {
518 mNextButton.setText(buttonText);
519 }
520 }
521 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
522 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
523 if (TextUtils.isEmpty(buttonText)) {
524 backButton.setVisibility(View.GONE);
525 }
526 else {
527 backButton.setText(buttonText);
528 }
529 }
530 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
531 skipButton.setVisibility(View.VISIBLE);
532 }
533 }
534 }
Fabrice Di Meglioc95be4f2014-03-07 12:57:38 -0800535 }
536
537 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800538 public void onBackStackChanged() {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700539 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800540 }
541
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700542 private int setTitleFromBackStack() {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800543 final int count = getFragmentManager().getBackStackEntryCount();
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700544
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800545 if (count == 0) {
546 setTitle(mInitialTitle);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700547 return 0;
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800548 }
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700549
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800550 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1);
551 setTitleFromBackStackEntry(bse);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700552
553 return count;
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800554 }
555
556 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
557 final CharSequence title;
558 final int titleRes = bse.getBreadCrumbTitleRes();
559 if (titleRes > 0) {
560 title = getText(titleRes);
561 } else {
562 title = bse.getBreadCrumbTitle();
563 }
564 if (title != null) {
565 setTitle(title);
566 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800567 }
568
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800569 @Override
570 protected void onSaveInstanceState(Bundle outState) {
571 super.onSaveInstanceState(outState);
572
573 if (mHeaders.size() > 0) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700574 outState.putParcelableArrayList(SAVE_KEY_HEADERS, mHeaders);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800575 if (mCurrentHeader != null) {
576 int index = mHeaders.indexOf(mCurrentHeader);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800577 if (index >= 0) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700578 outState.putInt(SAVE_KEY_CURRENT_HEADER, index);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800579 }
580 }
581 }
Fabrice Di Megliod6985df2014-04-03 16:43:26 -0700582
583 // The option menus are created if the ActionBar is visible and they are also created
584 // asynchronously. If you launch Settings with an Intent action like
585 // android.intent.action.POWER_USAGE_SUMMARY and at the same time your device is locked
586 // thru a LockScreen, onCreateOptionsMenu() is not yet called and references to the search
587 // menu item and search view are null.
588 boolean isExpanded = (mSearchMenuItem != null) && mSearchMenuItem.isActionViewExpanded();
589 outState.putBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED, isExpanded);
590
591 String query = (mSearchView != null) ? mSearchView.getQuery().toString() : EMPTY_QUERY;
592 outState.putString(SAVE_KEY_SEARCH_QUERY, query);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800593 }
594
595 @Override
596 public void onResume() {
597 super.onResume();
598
599 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
600 @Override
601 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
602 invalidateHeaders();
603 }
604 };
605 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
606 mDevelopmentPreferencesListener);
607
Matthew Xiea504c4d2014-02-14 16:32:32 -0800608 mHeaderAdapter.resume(this);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800609 invalidateHeaders();
610
611 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
612 }
613
614 @Override
615 public void onPause() {
616 super.onPause();
617
618 unregisterReceiver(mBatteryInfoReceiver);
619
620 mHeaderAdapter.pause();
621
622 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
623 mDevelopmentPreferencesListener);
624
625 mDevelopmentPreferencesListener = null;
626 }
627
628 @Override
629 public void onDestroy() {
630 super.onDestroy();
631 if (mListeningToAccountUpdates) {
632 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
633 }
634 }
635
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800636 protected boolean isValidFragment(String fragmentName) {
637 // Almost all fragments are wrapped in this,
638 // except for a few that have their own activities.
639 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
640 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
641 }
642 return false;
643 }
644
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800645 /**
646 * When in two-pane mode, switch to the fragment pane to show the given
647 * preference fragment.
648 *
649 * @param header The new header to display.
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800650 */
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700651 private void onHeaderClick(Header header) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800652 if (header == null) {
653 return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800654 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700655 if (header.fragment != null) {
656 startWithFragment(header.fragment, header.fragmentArguments, null, 0,
657 header.getTitle(getResources()));
658 } else if (header.intent != null) {
659 startActivity(header.intent);
660 } else {
661 throw new IllegalStateException(
662 "Can't switch to header that has no Fragment nor Intent");
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800663 }
664 }
665
666 /**
667 * Called to determine whether the header list should be hidden.
668 * The default implementation returns the
669 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
670 * This is set to false, for example, when the activity is being re-launched
671 * to show a particular preference activity.
672 */
673 public boolean onIsHidingHeaders() {
674 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
675 }
676
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800677 @Override
678 public Intent getIntent() {
679 Intent superIntent = super.getIntent();
680 String startingFragment = getStartingFragmentClass(superIntent);
681 // This is called from super.onCreate, isMultiPane() is not yet reliable
682 // Do not use onIsHidingHeaders either, which relies itself on this method
683 if (startingFragment != null) {
684 Intent modIntent = new Intent(superIntent);
685 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
686 Bundle args = superIntent.getExtras();
687 if (args != null) {
688 args = new Bundle(args);
689 } else {
690 args = new Bundle();
691 }
692 args.putParcelable("intent", superIntent);
693 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
694 return modIntent;
695 }
696 return superIntent;
697 }
698
699 /**
700 * Checks if the component name in the intent is different from the Settings class and
701 * returns the class name to load as a fragment.
702 */
703 private String getStartingFragmentClass(Intent intent) {
704 if (mFragmentClass != null) return mFragmentClass;
705
706 String intentClass = intent.getComponent().getClassName();
707 if (intentClass.equals(getClass().getName())) return null;
708
709 if ("com.android.settings.ManageApplications".equals(intentClass)
710 || "com.android.settings.RunningServices".equals(intentClass)
711 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
712 // Old names of manage apps.
713 intentClass = com.android.settings.applications.ManageApplications.class.getName();
714 }
715
716 return intentClass;
717 }
718
719 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000720 * Start a new fragment containing a preference panel. If the preferences
721 * are being displayed in multi-pane mode, the given fragment class will
722 * be instantiated and placed in the appropriate pane. If running in
723 * single-pane mode, a new activity will be launched in which to show the
724 * fragment.
725 *
726 * @param fragmentClass Full name of the class implementing the fragment.
727 * @param args Any desired arguments to supply to the fragment.
728 * @param titleRes Optional resource identifier of the title of this
729 * fragment.
730 * @param titleText Optional text of the title of this fragment.
731 * @param resultTo Optional fragment that result data should be sent to.
732 * If non-null, resultTo.onActivityResult() will be called when this
733 * preference panel is done. The launched panel must use
734 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
735 * @param resultRequestCode If resultTo is non-null, this is the caller's
736 * request code to be received with the resut.
737 */
738 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700739 CharSequence titleText, Fragment resultTo, int resultRequestCode) {
740 switchToFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000741 }
742
743 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800744 * Called by a preference panel fragment to finish itself.
745 *
746 * @param caller The fragment that is asking to be finished.
747 * @param resultCode Optional result code to send back to the original
748 * launching fragment.
749 * @param resultData Optional result data to send back to the original
750 * launching fragment.
751 */
752 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
753 setResult(resultCode, resultData);
754 }
755
756 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000757 * Start a new fragment.
758 *
759 * @param fragment The fragment to start
760 * @param push If true, the current fragment will be pushed onto the back stack. If false,
761 * the current fragment will be replaced.
762 */
763 public void startPreferenceFragment(Fragment fragment, boolean push) {
764 FragmentTransaction transaction = getFragmentManager().beginTransaction();
765 transaction.replace(R.id.prefs, fragment);
766 if (push) {
767 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
768 transaction.addToBackStack(BACK_STACK_PREFS);
769 } else {
770 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
771 }
772 transaction.commitAllowingStateLoss();
773 }
774
775 /**
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700776 * Start a new fragment. Used by #startPreferencePanel.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000777 *
778 * @param fragmentName The name of the fragment to display.
779 * @param args Optional arguments to supply to the fragment.
780 * @param resultTo Option fragment that should receive the result of
781 * the activity launch.
782 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
783 * report the result.
784 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
785 * valid one then it will be used to get the title. Otherwise the titleText
786 * argument will be used as the title.
787 * @param titleText string to display for the title of.
788 */
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700789 private void switchToFragment(String fragmentName, Bundle args, Fragment resultTo,
790 int resultRequestCode, int titleRes, CharSequence titleText) {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800791 final CharSequence cs;
792 if (titleRes != 0) {
793 cs = getText(titleRes);
794 } else {
795 cs = titleText;
796 }
797
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000798 Fragment f = Fragment.instantiate(this, fragmentName, args);
799 if (resultTo != null) {
800 f.setTargetFragment(resultTo, resultRequestCode);
801 }
802 FragmentTransaction transaction = getFragmentManager().beginTransaction();
803 transaction.replace(R.id.prefs, f);
804 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
805 transaction.addToBackStack(BACK_STACK_PREFS);
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800806 transaction.setBreadCrumbTitle(cs);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000807 transaction.commitAllowingStateLoss();
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000808 }
809
810 /**
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700811 * Switch to a specific Fragment with taking care of validation, Title and BackStack
812 */
813 private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
814 boolean addToBackStack, CharSequence title, boolean withTransition) {
815 if (validate && !isValidFragment(fragmentName)) {
816 throw new IllegalArgumentException("Invalid fragment for this activity: "
817 + fragmentName);
818 }
819 Fragment f = Fragment.instantiate(this, fragmentName, args);
820 FragmentTransaction transaction = getFragmentManager().beginTransaction();
821 transaction.replace(R.id.prefs, f);
822 if (withTransition) {
823 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
824 }
825 if (addToBackStack) {
826 transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
827 }
828 if (title != null) {
829 transaction.setBreadCrumbTitle(title);
830 }
831 transaction.commitAllowingStateLoss();
832 return f;
833 }
834
835 /**
836 * Start a new instance of this activity, showing only the given fragment.
837 * When launched in this mode, the given preference fragment will be instantiated and fill the
838 * entire activity.
839 *
840 * @param fragmentName The name of the fragment to display.
841 * @param args Optional arguments to supply to the fragment.
842 * @param resultTo Option fragment that should receive the result of
843 * the activity launch.
844 * @param resultRequestCode If resultTo is non-null, this is the request
845 * code in which to report the result.
846 * @param title String to display for the title of this set of preferences.
847 */
848 public void startWithFragment(String fragmentName, Bundle args,
849 Fragment resultTo, int resultRequestCode, CharSequence title) {
850 Intent intent = onBuildStartFragmentIntent(fragmentName, args, title);
851 if (resultTo == null) {
852 startActivity(intent);
853 } else {
854 resultTo.startActivityForResult(intent, resultRequestCode);
855 }
856 }
857
858 /**
859 * Build an Intent to launch a new activity showing the selected fragment.
860 * The implementation constructs an Intent that re-launches the current activity with the
861 * appropriate arguments to display the fragment.
862 *
863 * @param fragmentName The name of the fragment to display.
864 * @param args Optional arguments to supply to the fragment.
865 * @param title Optional title to show for this item.
866 * @return Returns an Intent that can be launched to display the given
867 * fragment.
868 */
869 public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args, CharSequence title) {
870 Intent intent = new Intent(Intent.ACTION_MAIN);
871 intent.setClass(this, SubSettings.class);
872 intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
873 intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
874 intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, title);
875 intent.putExtra(EXTRA_NO_HEADERS, true);
876 return intent;
877 }
878
879 /**
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800880 * Called when the activity needs its list of headers build.
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800881 *
882 * @param headers The list in which to place the headers.
883 */
884 private void onBuildHeaders(List<Header> headers) {
885 loadHeadersFromResource(R.xml.settings_headers, headers);
886 updateHeaderList(headers);
887 }
888
889 /**
890 * Parse the given XML file as a header description, adding each
891 * parsed Header into the target list.
892 *
893 * @param resid The XML resource to load and parse.
894 * @param target The list in which the parsed headers should be placed.
895 */
896 private void loadHeadersFromResource(int resid, List<Header> target) {
897 XmlResourceParser parser = null;
898 try {
899 parser = getResources().getXml(resid);
900 AttributeSet attrs = Xml.asAttributeSet(parser);
901
902 int type;
903 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
904 && type != XmlPullParser.START_TAG) {
905 // Parse next until start tag is found
906 }
907
908 String nodeName = parser.getName();
909 if (!"preference-headers".equals(nodeName)) {
910 throw new RuntimeException(
911 "XML document must start with <preference-headers> tag; found"
912 + nodeName + " at " + parser.getPositionDescription());
913 }
914
915 Bundle curBundle = null;
916
917 final int outerDepth = parser.getDepth();
918 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
919 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
920 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
921 continue;
922 }
923
924 nodeName = parser.getName();
925 if ("header".equals(nodeName)) {
926 Header header = new Header();
927
928 TypedArray sa = obtainStyledAttributes(
929 attrs, com.android.internal.R.styleable.PreferenceHeader);
930 header.id = sa.getResourceId(
931 com.android.internal.R.styleable.PreferenceHeader_id,
932 (int)HEADER_ID_UNDEFINED);
933 TypedValue tv = sa.peekValue(
934 com.android.internal.R.styleable.PreferenceHeader_title);
935 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
936 if (tv.resourceId != 0) {
937 header.titleRes = tv.resourceId;
938 } else {
939 header.title = tv.string;
940 }
941 }
942 tv = sa.peekValue(
943 com.android.internal.R.styleable.PreferenceHeader_summary);
944 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
945 if (tv.resourceId != 0) {
946 header.summaryRes = tv.resourceId;
947 } else {
948 header.summary = tv.string;
949 }
950 }
951 header.iconRes = sa.getResourceId(
952 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
953 header.fragment = sa.getString(
954 com.android.internal.R.styleable.PreferenceHeader_fragment);
955 sa.recycle();
956
957 if (curBundle == null) {
958 curBundle = new Bundle();
959 }
960
961 final int innerDepth = parser.getDepth();
962 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
963 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
964 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
965 continue;
966 }
967
968 String innerNodeName = parser.getName();
969 if (innerNodeName.equals("extra")) {
970 getResources().parseBundleExtra("extra", attrs, curBundle);
971 XmlUtils.skipCurrentTag(parser);
972
973 } else if (innerNodeName.equals("intent")) {
974 header.intent = Intent.parseIntent(getResources(), parser, attrs);
975
976 } else {
977 XmlUtils.skipCurrentTag(parser);
978 }
979 }
980
981 if (curBundle.size() > 0) {
982 header.fragmentArguments = curBundle;
983 curBundle = null;
984 }
985
986 target.add(header);
987 } else {
988 XmlUtils.skipCurrentTag(parser);
989 }
990 }
991
992 } catch (XmlPullParserException e) {
993 throw new RuntimeException("Error parsing headers", e);
994 } catch (IOException e) {
995 throw new RuntimeException("Error parsing headers", e);
996 } finally {
997 if (parser != null) parser.close();
998 }
999 }
1000
1001 private void updateHeaderList(List<Header> target) {
1002 final boolean showDev = mDevelopmentPreferences.getBoolean(
1003 DevelopmentSettings.PREF_SHOW,
1004 android.os.Build.TYPE.equals("eng"));
1005 int i = 0;
1006
1007 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1008 mHeaderIndexMap.clear();
1009 while (i < target.size()) {
1010 Header header = target.get(i);
1011 // Ids are integers, so downcasting
1012 int id = (int) header.id;
1013 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1014 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1015 } else if (id == R.id.wifi_settings) {
1016 // Remove WiFi Settings if WiFi service is not available.
1017 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1018 target.remove(i);
1019 }
1020 } else if (id == R.id.bluetooth_settings) {
1021 // Remove Bluetooth Settings if Bluetooth service is not available.
1022 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1023 target.remove(i);
1024 }
1025 } else if (id == R.id.data_usage_settings) {
1026 // Remove data usage when kernel module not enabled
1027 final INetworkManagementService netManager = INetworkManagementService.Stub
1028 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1029 try {
1030 if (!netManager.isBandwidthControlEnabled()) {
1031 target.remove(i);
1032 }
1033 } catch (RemoteException e) {
1034 // ignored
1035 }
1036 } else if (id == R.id.battery_settings) {
1037 // Remove battery settings when battery is not available. (e.g. TV)
1038
1039 if (!mBatteryPresent) {
1040 target.remove(i);
1041 }
1042 } else if (id == R.id.account_settings) {
1043 int headerIndex = i + 1;
1044 i = insertAccountsHeaders(target, headerIndex);
1045 } else if (id == R.id.home_settings) {
1046 if (!updateHomeSettingHeaders(header)) {
1047 target.remove(i);
1048 }
1049 } else if (id == R.id.user_settings) {
1050 if (!UserHandle.MU_ENABLED
1051 || !UserManager.supportsMultipleUsers()
1052 || Utils.isMonkeyRunning()) {
1053 target.remove(i);
1054 }
1055 } else if (id == R.id.nfc_payment_settings) {
1056 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1057 target.remove(i);
1058 } else {
1059 // Only show if NFC is on and we have the HCE feature
1060 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1061 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1062 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1063 target.remove(i);
1064 }
1065 }
1066 } else if (id == R.id.development_settings) {
1067 if (!showDev) {
1068 target.remove(i);
1069 }
1070 } else if (id == R.id.account_add) {
1071 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1072 target.remove(i);
1073 }
1074 }
1075
1076 if (i < target.size() && target.get(i) == header
1077 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1078 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1079 target.remove(i);
1080 }
1081
1082 // Increment if the current one wasn't removed by the Utils code.
1083 if (i < target.size() && target.get(i) == header) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001084 mHeaderIndexMap.put(id, i);
1085 i++;
1086 }
1087 }
1088 }
1089
1090 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1091 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1092 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1093 for (String accountType : accountTypes) {
1094 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1095 if (label == null) {
1096 continue;
1097 }
1098
1099 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1100 boolean skipToAccount = accounts.length == 1
1101 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1102 Header accHeader = new Header();
1103 accHeader.title = label;
1104 if (accHeader.extras == null) {
1105 accHeader.extras = new Bundle();
1106 }
1107 if (skipToAccount) {
1108 accHeader.fragment = AccountSyncSettings.class.getName();
1109 accHeader.fragmentArguments = new Bundle();
1110 // Need this for the icon
1111 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1112 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1113 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1114 accounts[0]);
1115 } else {
1116 accHeader.fragment = ManageAccountsSettings.class.getName();
1117 accHeader.fragmentArguments = new Bundle();
1118 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1119 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1120 accountType);
1121 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1122 label.toString());
1123 }
1124 accountHeaders.add(accHeader);
1125 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1126 }
1127
1128 // Sort by label
1129 Collections.sort(accountHeaders, new Comparator<Header>() {
1130 @Override
1131 public int compare(Header h1, Header h2) {
1132 return h1.title.toString().compareTo(h2.title.toString());
1133 }
1134 });
1135
1136 for (Header header : accountHeaders) {
1137 target.add(headerIndex++, header);
1138 }
1139 if (!mListeningToAccountUpdates) {
1140 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1141 mListeningToAccountUpdates = true;
1142 }
1143 return headerIndex;
1144 }
1145
1146 private boolean updateHomeSettingHeaders(Header header) {
1147 // Once we decide to show Home settings, keep showing it forever
1148 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1149 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1150 return true;
1151 }
1152
1153 try {
1154 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1155 getPackageManager().getHomeActivities(homeApps);
1156 if (homeApps.size() < 2) {
1157 // When there's only one available home app, omit this settings
1158 // category entirely at the top level UI. If the user just
1159 // uninstalled the penultimate home app candidiate, we also
1160 // now tell them about why they aren't seeing 'Home' in the list.
1161 if (sShowNoHomeNotice) {
1162 sShowNoHomeNotice = false;
1163 NoHomeDialogFragment.show(this);
1164 }
1165 return false;
1166 } else {
1167 // Okay, we're allowing the Home settings category. Tell it, when
1168 // invoked via this front door, that we'll need to be told about the
1169 // case when the user uninstalls all but one home app.
1170 if (header.fragmentArguments == null) {
1171 header.fragmentArguments = new Bundle();
1172 }
1173 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1174 }
1175 } catch (Exception e) {
1176 // Can't look up the home activity; bail on configuring the icon
1177 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1178 }
1179
1180 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1181 return true;
1182 }
1183
1184 private void getMetaData() {
1185 try {
1186 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1187 PackageManager.GET_META_DATA);
1188 if (ai == null || ai.metaData == null) return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001189 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1190 } catch (NameNotFoundException nnfe) {
1191 // No recovery
1192 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1193 }
1194 }
1195
1196 // give subclasses access to the Next button
1197 public boolean hasNextButton() {
1198 return mNextButton != null;
1199 }
1200
1201 public Button getNextButton() {
1202 return mNextButton;
1203 }
1204
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001205 public HeaderAdapter getHeaderAdapter() {
1206 return mHeaderAdapter;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001207 }
1208
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001209 public void onListItemClick(ListView l, View v, int position, long id) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001210 if (!isResumed()) {
1211 return;
1212 }
1213 Object item = mHeaderAdapter.getItem(position);
1214 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001215 mSelectedHeader = (Header) item;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001216 onHeaderClick(mSelectedHeader);
1217 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001218 }
1219 }
1220
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001221 @Override
1222 public boolean shouldUpRecreateTask(Intent targetIntent) {
1223 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1224 }
1225
1226 @Override
1227 public void onAccountsUpdated(Account[] accounts) {
1228 // TODO: watch for package upgrades to invalidate cache; see 7206643
1229 mAuthenticatorHelper.updateAuthDescriptions(this);
1230 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1231 invalidateHeaders();
1232 }
1233
1234 public static void requestHomeNotice() {
1235 sShowNoHomeNotice = true;
1236 }
1237
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001238 @Override
1239 public boolean onQueryTextSubmit(String query) {
1240 switchToSearchResultsFragmentIfNeeded();
1241 mSearchQuery = query;
1242 return mSearchResultsFragment.onQueryTextSubmit(query);
1243 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001244
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001245 @Override
1246 public boolean onQueryTextChange(String newText) {
1247 mSearchQuery = newText;
1248 return false;
1249 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001250
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001251 @Override
1252 public boolean onClose() {
1253 return false;
1254 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001255
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001256 @Override
1257 public boolean onMenuItemActionExpand(MenuItem item) {
1258 if (item.getItemId() == mSearchMenuItem.getItemId()) {
1259 if (mSearchResultsFragment == null) {
1260 switchToSearchResultsFragmentIfNeeded();
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 @Override
1267 public boolean onMenuItemActionCollapse(MenuItem item) {
1268 if (item.getItemId() == mSearchMenuItem.getItemId()) {
1269 if (mIsShowingSearchResults) {
1270 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001271 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001272 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001273 return true;
1274 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001275
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001276 private void switchToSearchResultsFragmentIfNeeded() {
1277 if (!mIsShowingSearchResults) {
1278 Fragment current = getFragmentManager().findFragmentById(R.id.prefs);
1279 if (current != null && current instanceof SearchResultsSummary) {
1280 mSearchResultsFragment = (SearchResultsSummary) current;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001281 } else {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001282 String title = getString(R.string.search_results_title);
1283 mSearchResultsFragment = (SearchResultsSummary) switchToFragment(
1284 SearchResultsSummary.class.getName(), null, false, true, title, true);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001285 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001286 mIsShowingSearchResults = true;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001287 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001288 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001289
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001290 public void needToRevertToInitialFragment() {
1291 mNeedToRevertToInitialFragment = true;
1292 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001293
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001294 private void revertToInitialFragment() {
1295 mNeedToRevertToInitialFragment = false;
1296 getFragmentManager().popBackStack(SettingsActivity.BACK_STACK_PREFS,
1297 FragmentManager.POP_BACK_STACK_INCLUSIVE);
1298 mSearchResultsFragment = null;
1299 mIsShowingSearchResults = false;
1300 mSearchMenuItem.collapseActionView();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001301 }
1302}