blob: df413c75466e8d9fbda2e62734d20f53ca67d9dd [file] [log] [blame]
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings;
18
19import android.accounts.Account;
20import android.accounts.AccountManager;
21import android.accounts.OnAccountsUpdateListener;
22import android.app.ActionBar;
23import android.app.Activity;
24import android.app.Fragment;
25import android.app.FragmentManager;
26import android.app.FragmentTransaction;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080027import android.app.admin.DevicePolicyManager;
28import android.content.BroadcastReceiver;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070029import android.content.ComponentName;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080030import android.content.Context;
31import android.content.Intent;
32import android.content.IntentFilter;
33import android.content.SharedPreferences;
34import android.content.pm.ActivityInfo;
35import android.content.pm.PackageManager;
36import android.content.pm.PackageManager.NameNotFoundException;
37import android.content.pm.ResolveInfo;
38import android.content.res.Configuration;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080039import android.content.res.TypedArray;
40import android.content.res.XmlResourceParser;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080041import android.nfc.NfcAdapter;
42import android.os.Bundle;
43import android.os.Handler;
44import android.os.INetworkManagementService;
45import android.os.Message;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080046import android.os.RemoteException;
47import android.os.ServiceManager;
48import android.os.UserHandle;
49import android.os.UserManager;
50import android.preference.Preference;
51import android.preference.PreferenceFragment;
52import android.preference.PreferenceManager;
53import android.preference.PreferenceScreen;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080054import android.text.TextUtils;
55import android.util.AttributeSet;
56import android.util.Log;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080057import android.util.TypedValue;
58import android.util.Xml;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070059import android.view.Menu;
60import android.view.MenuInflater;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080061import android.view.MenuItem;
62import android.view.View;
63import android.view.View.OnClickListener;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080064import android.widget.Button;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080065import android.widget.ListView;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080066
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070067import android.widget.SearchView;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080068import com.android.internal.util.ArrayUtils;
69import com.android.internal.util.XmlUtils;
70import com.android.settings.accessibility.AccessibilitySettings;
71import com.android.settings.accessibility.CaptionPropertiesFragment;
72import com.android.settings.accounts.AccountSyncSettings;
73import com.android.settings.accounts.AuthenticatorHelper;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080074import com.android.settings.accounts.ManageAccountsSettings;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070075import com.android.settings.applications.InstalledAppDetails;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080076import com.android.settings.applications.ManageApplications;
77import com.android.settings.applications.ProcessStatsUi;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080078import com.android.settings.bluetooth.BluetoothSettings;
79import com.android.settings.dashboard.DashboardSummary;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070080import com.android.settings.dashboard.Header;
81import com.android.settings.dashboard.HeaderAdapter;
82import com.android.settings.dashboard.NoHomeDialogFragment;
83import com.android.settings.dashboard.SearchResultsSummary;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080084import com.android.settings.deviceinfo.Memory;
85import com.android.settings.deviceinfo.UsbSettings;
86import com.android.settings.fuelgauge.PowerUsageSummary;
Fabrice Di Megliofa7dc242014-03-12 19:24:43 -070087import com.android.settings.search.Index;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080088import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
89import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
90import com.android.settings.inputmethod.SpellCheckersSettings;
91import com.android.settings.inputmethod.UserDictionaryList;
92import com.android.settings.location.LocationSettings;
93import com.android.settings.nfc.AndroidBeam;
94import com.android.settings.nfc.PaymentSettings;
95import com.android.settings.print.PrintJobSettingsFragment;
96import com.android.settings.print.PrintSettingsFragment;
97import com.android.settings.tts.TextToSpeechSettings;
98import com.android.settings.users.UserSettings;
99import com.android.settings.vpn2.VpnSettings;
100import com.android.settings.wfd.WifiDisplaySettings;
101import com.android.settings.wifi.AdvancedWifiSettings;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800102import com.android.settings.wifi.WifiSettings;
103import com.android.settings.wifi.p2p.WifiP2pSettings;
104import org.xmlpull.v1.XmlPullParser;
105import org.xmlpull.v1.XmlPullParserException;
106
107import java.io.IOException;
108import java.util.ArrayList;
109import java.util.Collections;
110import java.util.Comparator;
111import java.util.HashMap;
112import java.util.List;
113
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700114import static com.android.settings.dashboard.Header.HEADER_ID_UNDEFINED;
115
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800116public class SettingsActivity extends Activity
117 implements PreferenceManager.OnPreferenceTreeClickListener,
118 PreferenceFragment.OnPreferenceStartFragmentCallback,
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700119 ButtonBarHandler, OnAccountsUpdateListener, FragmentManager.OnBackStackChangedListener,
120 SearchView.OnQueryTextListener, SearchView.OnCloseListener,
121 MenuItem.OnActionExpandListener {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800122
123 private static final String LOG_TAG = "Settings";
124
125 // Constants for state save/restore
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700126 private static final String SAVE_KEY_HEADERS = ":settings:headers";
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700127 private static final String SAVE_KEY_SEARCH_MENU_EXPANDED = ":settings:search_menu_expanded";
128 private static final String SAVE_KEY_SEARCH_QUERY = ":settings:search_query";
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800129
130 /**
131 * When starting this activity, the invoking Intent can contain this extra
132 * string to specify which fragment should be initially displayed.
133 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
134 * will call isValidFragment() to confirm that the fragment class name is valid for this
135 * activity.
136 */
137 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
138
139 /**
140 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
141 * this extra can also be specified to supply a Bundle of arguments to pass
142 * to that fragment when it is instantiated during the initial creation
143 * of the activity.
144 */
145 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
146
147 /**
148 * When starting this activity, the invoking Intent can contain this extra
149 * boolean that the header list should not be displayed. This is most often
150 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
151 * the activity to display a specific fragment that the user has navigated
152 * to.
153 */
154 public static final String EXTRA_NO_HEADERS = ":settings:no_headers";
155
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800156 public static final String BACK_STACK_PREFS = ":settings:prefs";
157
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800158 // extras that allow any preference activity to be launched as part of a wizard
159
160 // show Back and Next buttons? takes boolean parameter
161 // Back will then return RESULT_CANCELED and Next RESULT_OK
162 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
163
164 // add a Skip button?
165 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
166
167 // specify custom text for the Back or Next buttons, or cause a button to not appear
168 // at all by setting it to null
169 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
170 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
171
172 /**
173 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
174 * this extra can also be specify to supply the title to be shown for
175 * that fragment.
176 */
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700177 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800178
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800179 private static final String META_DATA_KEY_FRAGMENT_CLASS =
180 "com.android.settings.FRAGMENT_CLASS";
181
182 private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
183
Fabrice Di Megliod6985df2014-04-03 16:43:26 -0700184 private static final String EMPTY_QUERY = "";
185
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800186 private static boolean sShowNoHomeNotice = false;
187
188 private String mFragmentClass;
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800189 private Header mSelectedHeader;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800190
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800191 private CharSequence mInitialTitle;
192
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800193 // Show only these settings for restricted users
194 private int[] SETTINGS_FOR_RESTRICTED = {
195 R.id.wireless_section,
196 R.id.wifi_settings,
197 R.id.bluetooth_settings,
198 R.id.data_usage_settings,
199 R.id.wireless_settings,
200 R.id.device_section,
201 R.id.sound_settings,
202 R.id.display_settings,
203 R.id.storage_settings,
204 R.id.application_settings,
205 R.id.battery_settings,
206 R.id.personal_section,
207 R.id.location_settings,
208 R.id.security_settings,
209 R.id.language_settings,
210 R.id.user_settings,
211 R.id.account_settings,
212 R.id.account_add,
213 R.id.system_section,
214 R.id.date_time_settings,
215 R.id.about_settings,
216 R.id.accessibility_settings,
217 R.id.print_settings,
218 R.id.nfc_payment_settings,
Fabrice Di Meglio2858b792014-02-18 19:35:08 -0800219 R.id.home_settings,
220 R.id.dashboard
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800221 };
222
223 private static final String[] ENTRY_FRAGMENTS = {
224 WirelessSettings.class.getName(),
225 WifiSettings.class.getName(),
226 AdvancedWifiSettings.class.getName(),
227 BluetoothSettings.class.getName(),
228 TetherSettings.class.getName(),
229 WifiP2pSettings.class.getName(),
230 VpnSettings.class.getName(),
231 DateTimeSettings.class.getName(),
232 LocalePicker.class.getName(),
233 InputMethodAndLanguageSettings.class.getName(),
234 SpellCheckersSettings.class.getName(),
235 UserDictionaryList.class.getName(),
236 UserDictionarySettings.class.getName(),
237 SoundSettings.class.getName(),
238 DisplaySettings.class.getName(),
239 DeviceInfoSettings.class.getName(),
240 ManageApplications.class.getName(),
241 ProcessStatsUi.class.getName(),
242 NotificationStation.class.getName(),
243 LocationSettings.class.getName(),
244 SecuritySettings.class.getName(),
245 PrivacySettings.class.getName(),
246 DeviceAdminSettings.class.getName(),
247 AccessibilitySettings.class.getName(),
248 CaptionPropertiesFragment.class.getName(),
249 com.android.settings.accessibility.ToggleInversionPreferenceFragment.class.getName(),
250 com.android.settings.accessibility.ToggleContrastPreferenceFragment.class.getName(),
251 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
252 TextToSpeechSettings.class.getName(),
253 Memory.class.getName(),
254 DevelopmentSettings.class.getName(),
255 UsbSettings.class.getName(),
256 AndroidBeam.class.getName(),
257 WifiDisplaySettings.class.getName(),
258 PowerUsageSummary.class.getName(),
259 AccountSyncSettings.class.getName(),
260 CryptKeeperSettings.class.getName(),
261 DataUsageSummary.class.getName(),
262 DreamSettings.class.getName(),
263 UserSettings.class.getName(),
264 NotificationAccessSettings.class.getName(),
265 ManageAccountsSettings.class.getName(),
266 PrintSettingsFragment.class.getName(),
267 PrintJobSettingsFragment.class.getName(),
268 TrustedCredentialsSettings.class.getName(),
269 PaymentSettings.class.getName(),
270 KeyboardLayoutPickerFragment.class.getName(),
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700271 ZenModeSettings.class.getName(),
272 NotificationSettings.class.getName(),
273 ChooseLockPassword.ChooseLockPasswordFragment.class.getName(),
274 ChooseLockPattern.ChooseLockPatternFragment.class.getName(),
275 InstalledAppDetails.class.getName()
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800276 };
277
278 private SharedPreferences mDevelopmentPreferences;
279 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
280
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800281 private AuthenticatorHelper mAuthenticatorHelper;
282 private boolean mListeningToAccountUpdates;
283
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800284 private boolean mBatteryPresent = true;
285 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
286
287 @Override
288 public void onReceive(Context context, Intent intent) {
289 String action = intent.getAction();
290 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
291 boolean batteryPresent = Utils.isBatteryPresent(intent);
292
293 if (mBatteryPresent != batteryPresent) {
294 mBatteryPresent = batteryPresent;
295 invalidateHeaders();
296 }
297 }
298 }
299 };
300
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700301 private Button mNextButton;
302 private ActionBar mActionBar;
303
304 private SearchView mSearchView;
305 private MenuItem mSearchMenuItem;
306 private boolean mSearchMenuItemExpanded = false;
307 private boolean mIsShowingSearchResults = false;
308 private SearchResultsSummary mSearchResultsFragment;
309 private String mSearchQuery;
310
311 // Headers
312 protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800313 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800314 private HeaderAdapter mHeaderAdapter;
315
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800316 private static final int MSG_BUILD_HEADERS = 1;
317 private Handler mHandler = new Handler() {
318 @Override
319 public void handleMessage(Message msg) {
320 switch (msg.what) {
321 case MSG_BUILD_HEADERS: {
322 mHeaders.clear();
323 onBuildHeaders(mHeaders);
324 mHeaderAdapter.notifyDataSetChanged();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800325 } break;
326 }
327 }
328 };
329
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700330 private boolean mNeedToRevertToInitialFragment = false;
331
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800332 @Override
333 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
334 // Override the fragment title for Wallpaper settings
335 int titleRes = pref.getTitleRes();
336 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
337 titleRes = R.string.wallpaper_settings_fragment_title;
338 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
339 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
340 if (UserManager.get(this).isLinkedUser()) {
341 titleRes = R.string.profile_info_settings_title;
342 } else {
343 titleRes = R.string.user_info_settings_title;
344 }
345 }
346 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
347 null, 0);
348 return true;
349 }
350
351 @Override
352 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
353 return false;
354 }
355
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800356 private void invalidateHeaders() {
357 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
358 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
359 }
360 }
361
362 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800363 public void onConfigurationChanged(Configuration newConfig) {
364 super.onConfigurationChanged(newConfig);
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800365 Index.getInstance(this).update();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800366 }
367
368 @Override
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700369 protected void onStart() {
370 super.onStart();
371
372 if (mNeedToRevertToInitialFragment) {
373 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800374 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800375 }
376
377 @Override
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700378 public boolean onCreateOptionsMenu(Menu menu) {
379 MenuInflater inflater = getMenuInflater();
380 inflater.inflate(R.menu.options_menu, menu);
381
382 // Cache the search query (can be overriden by the OnQueryTextListener)
383 final String query = mSearchQuery;
384
Fabrice Di Meglio95937822014-03-31 19:46:42 -0700385 mSearchMenuItem = menu.findItem(R.id.search);
386 mSearchView = (SearchView) mSearchMenuItem.getActionView();
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700387
Fabrice Di Meglio95937822014-03-31 19:46:42 -0700388 mSearchMenuItem.setOnActionExpandListener(this);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700389 mSearchView.setOnQueryTextListener(this);
390 mSearchView.setOnCloseListener(this);
391
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700392 if (mSearchMenuItemExpanded) {
393 mSearchMenuItem.expandActionView();
394 }
395 mSearchView.setQuery(query, true /* submit */);
396
397 return true;
398 }
399
400 @Override
401 protected void onCreate(Bundle savedState) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800402 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
403 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
404 }
405
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800406 Index.getInstance(this).update();
407
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800408 mAuthenticatorHelper = new AuthenticatorHelper();
409 mAuthenticatorHelper.updateAuthDescriptions(this);
410 mAuthenticatorHelper.onAccountsUpdated(this, null);
411
412 DevicePolicyManager dpm =
413 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
Fabrice Di Megliodca28062014-02-21 17:42:56 -0800414
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700415 mHeaderAdapter = new HeaderAdapter(this, mHeaders, mAuthenticatorHelper, dpm);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800416
417 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
418 Context.MODE_PRIVATE);
419
420 getMetaData();
421
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700422 super.onCreate(savedState);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800423
424 setContentView(R.layout.settings_main);
425
426 getFragmentManager().addOnBackStackChangedListener(this);
427
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700428 boolean displayHomeAsUpEnabled = true;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800429
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700430 String initialFragmentName = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
431 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800432
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700433 if (savedState != null) {
434 mSearchMenuItemExpanded = savedState.getBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED);
435 mSearchQuery = savedState.getString(SAVE_KEY_SEARCH_QUERY);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800436
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700437 // We are restarting from a previous saved state; used that to
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800438 // initialize, instead of starting fresh.
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800439 mInitialTitle = getTitle();
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800440
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700441 ArrayList<Header> headers = savedState.getParcelableArrayList(SAVE_KEY_HEADERS);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800442 if (headers != null) {
443 mHeaders.addAll(headers);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700444 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800445 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800446 } else {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800447 // We need to build the Headers in all cases
448 onBuildHeaders(mHeaders);
449
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700450 if (initialFragmentName != null) {
451 final ComponentName cn = getIntent().getComponent();
452 // No UP is we are launched thru a Settings shortcut
453 if (!cn.getClassName().equals(SubSettings.class.getName())) {
454 displayHomeAsUpEnabled = false;
455 }
456 final String initialTitle = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
457 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800458 setTitle(mInitialTitle);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700459 switchToFragment( initialFragmentName, initialArguments, true, false,
460 mInitialTitle, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000461 } else {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700462 // No UP if we are displaying the Headers
463 displayHomeAsUpEnabled = false;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000464 if (mHeaders.size() > 0) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700465 mInitialTitle = getText(R.string.dashboard_title);
466 switchToFragment(DashboardSummary.class.getName(), null, false, false,
467 mInitialTitle, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000468 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800469 }
470 }
471
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700472 mActionBar = getActionBar();
473 mActionBar.setHomeButtonEnabled(true);
474 mActionBar.setDisplayHomeAsUpEnabled(displayHomeAsUpEnabled);
475
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800476 // see if we should show Back/Next buttons
477 Intent intent = getIntent();
478 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
479
480 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
481 if (buttonBar != null) {
482 buttonBar.setVisibility(View.VISIBLE);
483
484 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
485 backButton.setOnClickListener(new OnClickListener() {
486 public void onClick(View v) {
487 setResult(RESULT_CANCELED);
488 finish();
489 }
490 });
491 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
492 skipButton.setOnClickListener(new OnClickListener() {
493 public void onClick(View v) {
494 setResult(RESULT_OK);
495 finish();
496 }
497 });
498 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
499 mNextButton.setOnClickListener(new OnClickListener() {
500 public void onClick(View v) {
501 setResult(RESULT_OK);
502 finish();
503 }
504 });
505
506 // set our various button parameters
507 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
508 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
509 if (TextUtils.isEmpty(buttonText)) {
510 mNextButton.setVisibility(View.GONE);
511 }
512 else {
513 mNextButton.setText(buttonText);
514 }
515 }
516 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
517 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
518 if (TextUtils.isEmpty(buttonText)) {
519 backButton.setVisibility(View.GONE);
520 }
521 else {
522 backButton.setText(buttonText);
523 }
524 }
525 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
526 skipButton.setVisibility(View.VISIBLE);
527 }
528 }
529 }
Fabrice Di Meglioc95be4f2014-03-07 12:57:38 -0800530 }
531
532 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800533 public void onBackStackChanged() {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700534 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800535 }
536
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700537 private int setTitleFromBackStack() {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800538 final int count = getFragmentManager().getBackStackEntryCount();
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700539
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800540 if (count == 0) {
541 setTitle(mInitialTitle);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700542 return 0;
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800543 }
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700544
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800545 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1);
546 setTitleFromBackStackEntry(bse);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700547
548 return count;
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800549 }
550
551 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
552 final CharSequence title;
553 final int titleRes = bse.getBreadCrumbTitleRes();
554 if (titleRes > 0) {
555 title = getText(titleRes);
556 } else {
557 title = bse.getBreadCrumbTitle();
558 }
559 if (title != null) {
560 setTitle(title);
561 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800562 }
563
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800564 @Override
565 protected void onSaveInstanceState(Bundle outState) {
566 super.onSaveInstanceState(outState);
567
568 if (mHeaders.size() > 0) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700569 outState.putParcelableArrayList(SAVE_KEY_HEADERS, mHeaders);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800570 }
Fabrice Di Megliod6985df2014-04-03 16:43:26 -0700571
572 // The option menus are created if the ActionBar is visible and they are also created
573 // asynchronously. If you launch Settings with an Intent action like
574 // android.intent.action.POWER_USAGE_SUMMARY and at the same time your device is locked
575 // thru a LockScreen, onCreateOptionsMenu() is not yet called and references to the search
576 // menu item and search view are null.
577 boolean isExpanded = (mSearchMenuItem != null) && mSearchMenuItem.isActionViewExpanded();
578 outState.putBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED, isExpanded);
579
580 String query = (mSearchView != null) ? mSearchView.getQuery().toString() : EMPTY_QUERY;
581 outState.putString(SAVE_KEY_SEARCH_QUERY, query);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800582 }
583
584 @Override
585 public void onResume() {
586 super.onResume();
587
588 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
589 @Override
590 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
591 invalidateHeaders();
592 }
593 };
594 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
595 mDevelopmentPreferencesListener);
596
Matthew Xiea504c4d2014-02-14 16:32:32 -0800597 mHeaderAdapter.resume(this);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800598 invalidateHeaders();
599
600 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
601 }
602
603 @Override
604 public void onPause() {
605 super.onPause();
606
607 unregisterReceiver(mBatteryInfoReceiver);
608
609 mHeaderAdapter.pause();
610
611 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
612 mDevelopmentPreferencesListener);
613
614 mDevelopmentPreferencesListener = null;
615 }
616
617 @Override
618 public void onDestroy() {
619 super.onDestroy();
620 if (mListeningToAccountUpdates) {
621 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
622 }
623 }
624
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800625 protected boolean isValidFragment(String fragmentName) {
626 // Almost all fragments are wrapped in this,
627 // except for a few that have their own activities.
628 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
629 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
630 }
631 return false;
632 }
633
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800634 /**
635 * When in two-pane mode, switch to the fragment pane to show the given
636 * preference fragment.
637 *
638 * @param header The new header to display.
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800639 */
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700640 private void onHeaderClick(Header header) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800641 if (header == null) {
642 return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800643 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700644 if (header.fragment != null) {
645 startWithFragment(header.fragment, header.fragmentArguments, null, 0,
646 header.getTitle(getResources()));
647 } else if (header.intent != null) {
648 startActivity(header.intent);
649 } else {
650 throw new IllegalStateException(
651 "Can't switch to header that has no Fragment nor Intent");
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800652 }
653 }
654
655 /**
656 * Called to determine whether the header list should be hidden.
657 * The default implementation returns the
658 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
659 * This is set to false, for example, when the activity is being re-launched
660 * to show a particular preference activity.
661 */
662 public boolean onIsHidingHeaders() {
663 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
664 }
665
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800666 @Override
667 public Intent getIntent() {
668 Intent superIntent = super.getIntent();
669 String startingFragment = getStartingFragmentClass(superIntent);
670 // This is called from super.onCreate, isMultiPane() is not yet reliable
671 // Do not use onIsHidingHeaders either, which relies itself on this method
672 if (startingFragment != null) {
673 Intent modIntent = new Intent(superIntent);
674 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
675 Bundle args = superIntent.getExtras();
676 if (args != null) {
677 args = new Bundle(args);
678 } else {
679 args = new Bundle();
680 }
681 args.putParcelable("intent", superIntent);
682 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
683 return modIntent;
684 }
685 return superIntent;
686 }
687
688 /**
689 * Checks if the component name in the intent is different from the Settings class and
690 * returns the class name to load as a fragment.
691 */
692 private String getStartingFragmentClass(Intent intent) {
693 if (mFragmentClass != null) return mFragmentClass;
694
695 String intentClass = intent.getComponent().getClassName();
696 if (intentClass.equals(getClass().getName())) return null;
697
698 if ("com.android.settings.ManageApplications".equals(intentClass)
699 || "com.android.settings.RunningServices".equals(intentClass)
700 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
701 // Old names of manage apps.
702 intentClass = com.android.settings.applications.ManageApplications.class.getName();
703 }
704
705 return intentClass;
706 }
707
708 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000709 * Start a new fragment containing a preference panel. If the preferences
710 * are being displayed in multi-pane mode, the given fragment class will
711 * be instantiated and placed in the appropriate pane. If running in
712 * single-pane mode, a new activity will be launched in which to show the
713 * fragment.
714 *
715 * @param fragmentClass Full name of the class implementing the fragment.
716 * @param args Any desired arguments to supply to the fragment.
717 * @param titleRes Optional resource identifier of the title of this
718 * fragment.
719 * @param titleText Optional text of the title of this fragment.
720 * @param resultTo Optional fragment that result data should be sent to.
721 * If non-null, resultTo.onActivityResult() will be called when this
722 * preference panel is done. The launched panel must use
723 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
724 * @param resultRequestCode If resultTo is non-null, this is the caller's
725 * request code to be received with the resut.
726 */
727 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700728 CharSequence titleText, Fragment resultTo, int resultRequestCode) {
729 switchToFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000730 }
731
732 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800733 * Called by a preference panel fragment to finish itself.
734 *
735 * @param caller The fragment that is asking to be finished.
736 * @param resultCode Optional result code to send back to the original
737 * launching fragment.
738 * @param resultData Optional result data to send back to the original
739 * launching fragment.
740 */
741 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
742 setResult(resultCode, resultData);
743 }
744
745 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000746 * Start a new fragment.
747 *
748 * @param fragment The fragment to start
749 * @param push If true, the current fragment will be pushed onto the back stack. If false,
750 * the current fragment will be replaced.
751 */
752 public void startPreferenceFragment(Fragment fragment, boolean push) {
753 FragmentTransaction transaction = getFragmentManager().beginTransaction();
754 transaction.replace(R.id.prefs, fragment);
755 if (push) {
756 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
757 transaction.addToBackStack(BACK_STACK_PREFS);
758 } else {
759 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
760 }
761 transaction.commitAllowingStateLoss();
762 }
763
764 /**
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700765 * Start a new fragment. Used by #startPreferencePanel.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000766 *
767 * @param fragmentName The name of the fragment to display.
768 * @param args Optional arguments to supply to the fragment.
769 * @param resultTo Option fragment that should receive the result of
770 * the activity launch.
771 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
772 * report the result.
773 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
774 * valid one then it will be used to get the title. Otherwise the titleText
775 * argument will be used as the title.
776 * @param titleText string to display for the title of.
777 */
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700778 private void switchToFragment(String fragmentName, Bundle args, Fragment resultTo,
779 int resultRequestCode, int titleRes, CharSequence titleText) {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800780 final CharSequence cs;
781 if (titleRes != 0) {
782 cs = getText(titleRes);
783 } else {
784 cs = titleText;
785 }
786
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000787 Fragment f = Fragment.instantiate(this, fragmentName, args);
788 if (resultTo != null) {
789 f.setTargetFragment(resultTo, resultRequestCode);
790 }
791 FragmentTransaction transaction = getFragmentManager().beginTransaction();
792 transaction.replace(R.id.prefs, f);
793 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
794 transaction.addToBackStack(BACK_STACK_PREFS);
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800795 transaction.setBreadCrumbTitle(cs);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000796 transaction.commitAllowingStateLoss();
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000797 }
798
799 /**
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700800 * Switch to a specific Fragment with taking care of validation, Title and BackStack
801 */
802 private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
803 boolean addToBackStack, CharSequence title, boolean withTransition) {
804 if (validate && !isValidFragment(fragmentName)) {
805 throw new IllegalArgumentException("Invalid fragment for this activity: "
806 + fragmentName);
807 }
808 Fragment f = Fragment.instantiate(this, fragmentName, args);
809 FragmentTransaction transaction = getFragmentManager().beginTransaction();
810 transaction.replace(R.id.prefs, f);
811 if (withTransition) {
812 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
813 }
814 if (addToBackStack) {
815 transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
816 }
817 if (title != null) {
818 transaction.setBreadCrumbTitle(title);
819 }
820 transaction.commitAllowingStateLoss();
821 return f;
822 }
823
824 /**
825 * Start a new instance of this activity, showing only the given fragment.
826 * When launched in this mode, the given preference fragment will be instantiated and fill the
827 * entire activity.
828 *
829 * @param fragmentName The name of the fragment to display.
830 * @param args Optional arguments to supply to the fragment.
831 * @param resultTo Option fragment that should receive the result of
832 * the activity launch.
833 * @param resultRequestCode If resultTo is non-null, this is the request
834 * code in which to report the result.
835 * @param title String to display for the title of this set of preferences.
836 */
837 public void startWithFragment(String fragmentName, Bundle args,
838 Fragment resultTo, int resultRequestCode, CharSequence title) {
839 Intent intent = onBuildStartFragmentIntent(fragmentName, args, title);
840 if (resultTo == null) {
841 startActivity(intent);
842 } else {
843 resultTo.startActivityForResult(intent, resultRequestCode);
844 }
845 }
846
847 /**
848 * Build an Intent to launch a new activity showing the selected fragment.
849 * The implementation constructs an Intent that re-launches the current activity with the
850 * appropriate arguments to display the fragment.
851 *
852 * @param fragmentName The name of the fragment to display.
853 * @param args Optional arguments to supply to the fragment.
854 * @param title Optional title to show for this item.
855 * @return Returns an Intent that can be launched to display the given
856 * fragment.
857 */
858 public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args, CharSequence title) {
859 Intent intent = new Intent(Intent.ACTION_MAIN);
860 intent.setClass(this, SubSettings.class);
861 intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
862 intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
863 intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, title);
864 intent.putExtra(EXTRA_NO_HEADERS, true);
865 return intent;
866 }
867
868 /**
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800869 * Called when the activity needs its list of headers build.
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800870 *
871 * @param headers The list in which to place the headers.
872 */
873 private void onBuildHeaders(List<Header> headers) {
874 loadHeadersFromResource(R.xml.settings_headers, headers);
875 updateHeaderList(headers);
876 }
877
878 /**
879 * Parse the given XML file as a header description, adding each
880 * parsed Header into the target list.
881 *
882 * @param resid The XML resource to load and parse.
883 * @param target The list in which the parsed headers should be placed.
884 */
885 private void loadHeadersFromResource(int resid, List<Header> target) {
886 XmlResourceParser parser = null;
887 try {
888 parser = getResources().getXml(resid);
889 AttributeSet attrs = Xml.asAttributeSet(parser);
890
891 int type;
892 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
893 && type != XmlPullParser.START_TAG) {
894 // Parse next until start tag is found
895 }
896
897 String nodeName = parser.getName();
898 if (!"preference-headers".equals(nodeName)) {
899 throw new RuntimeException(
900 "XML document must start with <preference-headers> tag; found"
901 + nodeName + " at " + parser.getPositionDescription());
902 }
903
904 Bundle curBundle = null;
905
906 final int outerDepth = parser.getDepth();
907 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
908 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
909 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
910 continue;
911 }
912
913 nodeName = parser.getName();
914 if ("header".equals(nodeName)) {
915 Header header = new Header();
916
917 TypedArray sa = obtainStyledAttributes(
918 attrs, com.android.internal.R.styleable.PreferenceHeader);
919 header.id = sa.getResourceId(
920 com.android.internal.R.styleable.PreferenceHeader_id,
921 (int)HEADER_ID_UNDEFINED);
922 TypedValue tv = sa.peekValue(
923 com.android.internal.R.styleable.PreferenceHeader_title);
924 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
925 if (tv.resourceId != 0) {
926 header.titleRes = tv.resourceId;
927 } else {
928 header.title = tv.string;
929 }
930 }
931 tv = sa.peekValue(
932 com.android.internal.R.styleable.PreferenceHeader_summary);
933 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
934 if (tv.resourceId != 0) {
935 header.summaryRes = tv.resourceId;
936 } else {
937 header.summary = tv.string;
938 }
939 }
940 header.iconRes = sa.getResourceId(
941 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
942 header.fragment = sa.getString(
943 com.android.internal.R.styleable.PreferenceHeader_fragment);
944 sa.recycle();
945
946 if (curBundle == null) {
947 curBundle = new Bundle();
948 }
949
950 final int innerDepth = parser.getDepth();
951 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
952 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
953 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
954 continue;
955 }
956
957 String innerNodeName = parser.getName();
958 if (innerNodeName.equals("extra")) {
959 getResources().parseBundleExtra("extra", attrs, curBundle);
960 XmlUtils.skipCurrentTag(parser);
961
962 } else if (innerNodeName.equals("intent")) {
963 header.intent = Intent.parseIntent(getResources(), parser, attrs);
964
965 } else {
966 XmlUtils.skipCurrentTag(parser);
967 }
968 }
969
970 if (curBundle.size() > 0) {
971 header.fragmentArguments = curBundle;
972 curBundle = null;
973 }
974
975 target.add(header);
976 } else {
977 XmlUtils.skipCurrentTag(parser);
978 }
979 }
980
981 } catch (XmlPullParserException e) {
982 throw new RuntimeException("Error parsing headers", e);
983 } catch (IOException e) {
984 throw new RuntimeException("Error parsing headers", e);
985 } finally {
986 if (parser != null) parser.close();
987 }
988 }
989
990 private void updateHeaderList(List<Header> target) {
991 final boolean showDev = mDevelopmentPreferences.getBoolean(
992 DevelopmentSettings.PREF_SHOW,
993 android.os.Build.TYPE.equals("eng"));
994 int i = 0;
995
996 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
997 mHeaderIndexMap.clear();
998 while (i < target.size()) {
999 Header header = target.get(i);
1000 // Ids are integers, so downcasting
1001 int id = (int) header.id;
1002 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1003 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1004 } else if (id == R.id.wifi_settings) {
1005 // Remove WiFi Settings if WiFi service is not available.
1006 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1007 target.remove(i);
1008 }
1009 } else if (id == R.id.bluetooth_settings) {
1010 // Remove Bluetooth Settings if Bluetooth service is not available.
1011 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1012 target.remove(i);
1013 }
1014 } else if (id == R.id.data_usage_settings) {
1015 // Remove data usage when kernel module not enabled
1016 final INetworkManagementService netManager = INetworkManagementService.Stub
1017 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1018 try {
1019 if (!netManager.isBandwidthControlEnabled()) {
1020 target.remove(i);
1021 }
1022 } catch (RemoteException e) {
1023 // ignored
1024 }
1025 } else if (id == R.id.battery_settings) {
1026 // Remove battery settings when battery is not available. (e.g. TV)
1027
1028 if (!mBatteryPresent) {
1029 target.remove(i);
1030 }
1031 } else if (id == R.id.account_settings) {
1032 int headerIndex = i + 1;
1033 i = insertAccountsHeaders(target, headerIndex);
1034 } else if (id == R.id.home_settings) {
1035 if (!updateHomeSettingHeaders(header)) {
1036 target.remove(i);
1037 }
1038 } else if (id == R.id.user_settings) {
1039 if (!UserHandle.MU_ENABLED
1040 || !UserManager.supportsMultipleUsers()
1041 || Utils.isMonkeyRunning()) {
1042 target.remove(i);
1043 }
1044 } else if (id == R.id.nfc_payment_settings) {
1045 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1046 target.remove(i);
1047 } else {
1048 // Only show if NFC is on and we have the HCE feature
1049 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1050 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1051 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1052 target.remove(i);
1053 }
1054 }
1055 } else if (id == R.id.development_settings) {
1056 if (!showDev) {
1057 target.remove(i);
1058 }
1059 } else if (id == R.id.account_add) {
1060 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1061 target.remove(i);
1062 }
1063 }
1064
1065 if (i < target.size() && target.get(i) == header
1066 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1067 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1068 target.remove(i);
1069 }
1070
1071 // Increment if the current one wasn't removed by the Utils code.
1072 if (i < target.size() && target.get(i) == header) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001073 mHeaderIndexMap.put(id, i);
1074 i++;
1075 }
1076 }
1077 }
1078
1079 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1080 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1081 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1082 for (String accountType : accountTypes) {
1083 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1084 if (label == null) {
1085 continue;
1086 }
1087
1088 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1089 boolean skipToAccount = accounts.length == 1
1090 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1091 Header accHeader = new Header();
1092 accHeader.title = label;
1093 if (accHeader.extras == null) {
1094 accHeader.extras = new Bundle();
1095 }
1096 if (skipToAccount) {
1097 accHeader.fragment = AccountSyncSettings.class.getName();
1098 accHeader.fragmentArguments = new Bundle();
1099 // Need this for the icon
1100 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1101 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1102 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1103 accounts[0]);
1104 } else {
1105 accHeader.fragment = ManageAccountsSettings.class.getName();
1106 accHeader.fragmentArguments = new Bundle();
1107 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1108 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1109 accountType);
1110 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1111 label.toString());
1112 }
1113 accountHeaders.add(accHeader);
1114 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1115 }
1116
1117 // Sort by label
1118 Collections.sort(accountHeaders, new Comparator<Header>() {
1119 @Override
1120 public int compare(Header h1, Header h2) {
1121 return h1.title.toString().compareTo(h2.title.toString());
1122 }
1123 });
1124
1125 for (Header header : accountHeaders) {
1126 target.add(headerIndex++, header);
1127 }
1128 if (!mListeningToAccountUpdates) {
1129 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1130 mListeningToAccountUpdates = true;
1131 }
1132 return headerIndex;
1133 }
1134
1135 private boolean updateHomeSettingHeaders(Header header) {
1136 // Once we decide to show Home settings, keep showing it forever
1137 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1138 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1139 return true;
1140 }
1141
1142 try {
1143 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1144 getPackageManager().getHomeActivities(homeApps);
1145 if (homeApps.size() < 2) {
1146 // When there's only one available home app, omit this settings
1147 // category entirely at the top level UI. If the user just
1148 // uninstalled the penultimate home app candidiate, we also
1149 // now tell them about why they aren't seeing 'Home' in the list.
1150 if (sShowNoHomeNotice) {
1151 sShowNoHomeNotice = false;
1152 NoHomeDialogFragment.show(this);
1153 }
1154 return false;
1155 } else {
1156 // Okay, we're allowing the Home settings category. Tell it, when
1157 // invoked via this front door, that we'll need to be told about the
1158 // case when the user uninstalls all but one home app.
1159 if (header.fragmentArguments == null) {
1160 header.fragmentArguments = new Bundle();
1161 }
1162 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1163 }
1164 } catch (Exception e) {
1165 // Can't look up the home activity; bail on configuring the icon
1166 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1167 }
1168
1169 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1170 return true;
1171 }
1172
1173 private void getMetaData() {
1174 try {
1175 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1176 PackageManager.GET_META_DATA);
1177 if (ai == null || ai.metaData == null) return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001178 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1179 } catch (NameNotFoundException nnfe) {
1180 // No recovery
1181 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1182 }
1183 }
1184
1185 // give subclasses access to the Next button
1186 public boolean hasNextButton() {
1187 return mNextButton != null;
1188 }
1189
1190 public Button getNextButton() {
1191 return mNextButton;
1192 }
1193
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001194 public HeaderAdapter getHeaderAdapter() {
1195 return mHeaderAdapter;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001196 }
1197
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001198 public void onListItemClick(ListView l, View v, int position, long id) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001199 if (!isResumed()) {
1200 return;
1201 }
1202 Object item = mHeaderAdapter.getItem(position);
1203 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001204 mSelectedHeader = (Header) item;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001205 onHeaderClick(mSelectedHeader);
1206 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001207 }
1208 }
1209
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001210 @Override
1211 public boolean shouldUpRecreateTask(Intent targetIntent) {
1212 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1213 }
1214
1215 @Override
1216 public void onAccountsUpdated(Account[] accounts) {
1217 // TODO: watch for package upgrades to invalidate cache; see 7206643
1218 mAuthenticatorHelper.updateAuthDescriptions(this);
1219 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1220 invalidateHeaders();
1221 }
1222
1223 public static void requestHomeNotice() {
1224 sShowNoHomeNotice = true;
1225 }
1226
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001227 @Override
1228 public boolean onQueryTextSubmit(String query) {
1229 switchToSearchResultsFragmentIfNeeded();
1230 mSearchQuery = query;
1231 return mSearchResultsFragment.onQueryTextSubmit(query);
1232 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001233
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001234 @Override
1235 public boolean onQueryTextChange(String newText) {
1236 mSearchQuery = newText;
1237 return false;
1238 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001239
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001240 @Override
1241 public boolean onClose() {
1242 return false;
1243 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001244
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001245 @Override
1246 public boolean onMenuItemActionExpand(MenuItem item) {
1247 if (item.getItemId() == mSearchMenuItem.getItemId()) {
1248 if (mSearchResultsFragment == null) {
1249 switchToSearchResultsFragmentIfNeeded();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001250 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001251 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001252 return true;
1253 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001254
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001255 @Override
1256 public boolean onMenuItemActionCollapse(MenuItem item) {
1257 if (item.getItemId() == mSearchMenuItem.getItemId()) {
1258 if (mIsShowingSearchResults) {
1259 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001260 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001261 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001262 return true;
1263 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001264
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001265 private void switchToSearchResultsFragmentIfNeeded() {
1266 if (!mIsShowingSearchResults) {
1267 Fragment current = getFragmentManager().findFragmentById(R.id.prefs);
1268 if (current != null && current instanceof SearchResultsSummary) {
1269 mSearchResultsFragment = (SearchResultsSummary) current;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001270 } else {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001271 String title = getString(R.string.search_results_title);
1272 mSearchResultsFragment = (SearchResultsSummary) switchToFragment(
1273 SearchResultsSummary.class.getName(), null, false, true, title, true);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001274 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001275 mIsShowingSearchResults = true;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001276 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001277 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001278
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001279 public void needToRevertToInitialFragment() {
1280 mNeedToRevertToInitialFragment = true;
1281 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001282
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001283 private void revertToInitialFragment() {
1284 mNeedToRevertToInitialFragment = false;
1285 getFragmentManager().popBackStack(SettingsActivity.BACK_STACK_PREFS,
1286 FragmentManager.POP_BACK_STACK_INCLUSIVE);
1287 mSearchResultsFragment = null;
1288 mIsShowingSearchResults = false;
1289 mSearchMenuItem.collapseActionView();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001290 }
1291}