blob: 699955a9d10b1b53fb2dfb37195c4c5c1fe2219d [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;
Fabrice Di Meglio63bbb8e2014-04-23 16:44:30 -070079import com.android.settings.dashboard.DashboardCategory;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080080import com.android.settings.dashboard.DashboardSummary;
Fabrice Di Meglio63bbb8e2014-04-23 16:44:30 -070081import com.android.settings.dashboard.DashboardTile;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070082import com.android.settings.dashboard.Header;
83import com.android.settings.dashboard.HeaderAdapter;
84import com.android.settings.dashboard.NoHomeDialogFragment;
85import com.android.settings.dashboard.SearchResultsSummary;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080086import com.android.settings.deviceinfo.Memory;
87import com.android.settings.deviceinfo.UsbSettings;
88import com.android.settings.fuelgauge.PowerUsageSummary;
Fabrice Di Meglio7a6bfd12014-04-14 19:49:18 -070089import com.android.settings.search.DynamicIndexableContentMonitor;
Fabrice Di Megliofa7dc242014-03-12 19:24:43 -070090import com.android.settings.search.Index;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080091import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
92import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
93import com.android.settings.inputmethod.SpellCheckersSettings;
94import com.android.settings.inputmethod.UserDictionaryList;
95import com.android.settings.location.LocationSettings;
96import com.android.settings.nfc.AndroidBeam;
97import com.android.settings.nfc.PaymentSettings;
John Spurlock4a350512014-04-08 14:08:21 -040098import com.android.settings.notification.NotificationAccessSettings;
99import com.android.settings.notification.NotificationSettings;
100import com.android.settings.notification.NotificationStation;
101import com.android.settings.notification.ZenModeSettings;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800102import com.android.settings.print.PrintJobSettingsFragment;
103import com.android.settings.print.PrintSettingsFragment;
104import com.android.settings.tts.TextToSpeechSettings;
105import com.android.settings.users.UserSettings;
106import com.android.settings.vpn2.VpnSettings;
107import com.android.settings.wfd.WifiDisplaySettings;
108import com.android.settings.wifi.AdvancedWifiSettings;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800109import com.android.settings.wifi.WifiSettings;
110import com.android.settings.wifi.p2p.WifiP2pSettings;
111import org.xmlpull.v1.XmlPullParser;
112import org.xmlpull.v1.XmlPullParserException;
113
114import java.io.IOException;
115import java.util.ArrayList;
116import java.util.Collections;
117import java.util.Comparator;
118import java.util.HashMap;
119import java.util.List;
120
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700121import static com.android.settings.dashboard.Header.HEADER_ID_UNDEFINED;
122
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800123public class SettingsActivity extends Activity
124 implements PreferenceManager.OnPreferenceTreeClickListener,
125 PreferenceFragment.OnPreferenceStartFragmentCallback,
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700126 ButtonBarHandler, OnAccountsUpdateListener, FragmentManager.OnBackStackChangedListener,
127 SearchView.OnQueryTextListener, SearchView.OnCloseListener,
128 MenuItem.OnActionExpandListener {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800129
130 private static final String LOG_TAG = "Settings";
131
132 // Constants for state save/restore
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700133 private static final String SAVE_KEY_HEADERS = ":settings:headers";
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700134 private static final String SAVE_KEY_SEARCH_MENU_EXPANDED = ":settings:search_menu_expanded";
135 private static final String SAVE_KEY_SEARCH_QUERY = ":settings:search_query";
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700136 private static final String SAVE_KEY_SHOW_HOME_AS_UP = ":settings:show_home_as_up";
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800137
138 /**
139 * When starting this activity, the invoking Intent can contain this extra
140 * string to specify which fragment should be initially displayed.
141 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
142 * will call isValidFragment() to confirm that the fragment class name is valid for this
143 * activity.
144 */
145 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
146
147 /**
148 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
149 * this extra can also be specified to supply a Bundle of arguments to pass
150 * to that fragment when it is instantiated during the initial creation
151 * of the activity.
152 */
153 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
154
155 /**
Fabrice Di Meglioc1457322014-04-04 19:07:50 -0700156 * Fragment "key" argument passed thru {@link #EXTRA_SHOW_FRAGMENT_ARGUMENTS}
157 */
158 public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
159
160 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800161 * When starting this activity, the invoking Intent can contain this extra
162 * boolean that the header list should not be displayed. This is most often
163 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
164 * the activity to display a specific fragment that the user has navigated
165 * to.
166 */
167 public static final String EXTRA_NO_HEADERS = ":settings:no_headers";
168
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800169 public static final String BACK_STACK_PREFS = ":settings:prefs";
170
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800171 // extras that allow any preference activity to be launched as part of a wizard
172
173 // show Back and Next buttons? takes boolean parameter
174 // Back will then return RESULT_CANCELED and Next RESULT_OK
175 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
176
177 // add a Skip button?
178 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
179
180 // specify custom text for the Back or Next buttons, or cause a button to not appear
181 // at all by setting it to null
182 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
183 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
184
185 /**
186 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
187 * this extra can also be specify to supply the title to be shown for
188 * that fragment.
189 */
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700190 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800191
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800192 private static final String META_DATA_KEY_FRAGMENT_CLASS =
193 "com.android.settings.FRAGMENT_CLASS";
194
195 private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
196
Fabrice Di Megliod6985df2014-04-03 16:43:26 -0700197 private static final String EMPTY_QUERY = "";
198
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800199 private static boolean sShowNoHomeNotice = false;
200
201 private String mFragmentClass;
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800202 private Header mSelectedHeader;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800203
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800204 private CharSequence mInitialTitle;
205
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800206 // Show only these settings for restricted users
207 private int[] SETTINGS_FOR_RESTRICTED = {
208 R.id.wireless_section,
209 R.id.wifi_settings,
210 R.id.bluetooth_settings,
211 R.id.data_usage_settings,
212 R.id.wireless_settings,
213 R.id.device_section,
214 R.id.sound_settings,
215 R.id.display_settings,
216 R.id.storage_settings,
217 R.id.application_settings,
218 R.id.battery_settings,
219 R.id.personal_section,
220 R.id.location_settings,
221 R.id.security_settings,
222 R.id.language_settings,
223 R.id.user_settings,
224 R.id.account_settings,
225 R.id.account_add,
226 R.id.system_section,
227 R.id.date_time_settings,
228 R.id.about_settings,
229 R.id.accessibility_settings,
230 R.id.print_settings,
231 R.id.nfc_payment_settings,
Fabrice Di Meglio2858b792014-02-18 19:35:08 -0800232 R.id.home_settings,
233 R.id.dashboard
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800234 };
235
236 private static final String[] ENTRY_FRAGMENTS = {
237 WirelessSettings.class.getName(),
238 WifiSettings.class.getName(),
239 AdvancedWifiSettings.class.getName(),
240 BluetoothSettings.class.getName(),
241 TetherSettings.class.getName(),
242 WifiP2pSettings.class.getName(),
243 VpnSettings.class.getName(),
244 DateTimeSettings.class.getName(),
245 LocalePicker.class.getName(),
246 InputMethodAndLanguageSettings.class.getName(),
247 SpellCheckersSettings.class.getName(),
248 UserDictionaryList.class.getName(),
249 UserDictionarySettings.class.getName(),
250 SoundSettings.class.getName(),
251 DisplaySettings.class.getName(),
252 DeviceInfoSettings.class.getName(),
253 ManageApplications.class.getName(),
254 ProcessStatsUi.class.getName(),
255 NotificationStation.class.getName(),
256 LocationSettings.class.getName(),
257 SecuritySettings.class.getName(),
258 PrivacySettings.class.getName(),
259 DeviceAdminSettings.class.getName(),
260 AccessibilitySettings.class.getName(),
261 CaptionPropertiesFragment.class.getName(),
262 com.android.settings.accessibility.ToggleInversionPreferenceFragment.class.getName(),
263 com.android.settings.accessibility.ToggleContrastPreferenceFragment.class.getName(),
264 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
265 TextToSpeechSettings.class.getName(),
266 Memory.class.getName(),
267 DevelopmentSettings.class.getName(),
268 UsbSettings.class.getName(),
269 AndroidBeam.class.getName(),
270 WifiDisplaySettings.class.getName(),
271 PowerUsageSummary.class.getName(),
272 AccountSyncSettings.class.getName(),
273 CryptKeeperSettings.class.getName(),
274 DataUsageSummary.class.getName(),
275 DreamSettings.class.getName(),
276 UserSettings.class.getName(),
277 NotificationAccessSettings.class.getName(),
278 ManageAccountsSettings.class.getName(),
279 PrintSettingsFragment.class.getName(),
280 PrintJobSettingsFragment.class.getName(),
281 TrustedCredentialsSettings.class.getName(),
282 PaymentSettings.class.getName(),
283 KeyboardLayoutPickerFragment.class.getName(),
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700284 ZenModeSettings.class.getName(),
285 NotificationSettings.class.getName(),
286 ChooseLockPassword.ChooseLockPasswordFragment.class.getName(),
287 ChooseLockPattern.ChooseLockPatternFragment.class.getName(),
288 InstalledAppDetails.class.getName()
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800289 };
290
291 private SharedPreferences mDevelopmentPreferences;
292 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
293
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800294 private AuthenticatorHelper mAuthenticatorHelper;
295 private boolean mListeningToAccountUpdates;
296
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800297 private boolean mBatteryPresent = true;
298 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
299
300 @Override
301 public void onReceive(Context context, Intent intent) {
302 String action = intent.getAction();
303 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
304 boolean batteryPresent = Utils.isBatteryPresent(intent);
305
306 if (mBatteryPresent != batteryPresent) {
307 mBatteryPresent = batteryPresent;
308 invalidateHeaders();
309 }
310 }
311 }
312 };
313
Svetoslav990159a2014-04-14 17:14:59 -0700314 private final DynamicIndexableContentMonitor mDynamicIndexableContentMonitor =
315 new DynamicIndexableContentMonitor();
Svetoslav853e4712014-04-14 10:10:25 -0700316
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700317 private Button mNextButton;
318 private ActionBar mActionBar;
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700319 private boolean mDisplayHomeAsUpEnabled;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700320
321 private SearchView mSearchView;
322 private MenuItem mSearchMenuItem;
323 private boolean mSearchMenuItemExpanded = false;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700324 private SearchResultsSummary mSearchResultsFragment;
325 private String mSearchQuery;
326
327 // Headers
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800328 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800329 private HeaderAdapter mHeaderAdapter;
330
Fabrice Di Meglio63bbb8e2014-04-23 16:44:30 -0700331 private List<DashboardCategory> mCategories = new ArrayList<DashboardCategory>();
332
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800333 private static final int MSG_BUILD_HEADERS = 1;
334 private Handler mHandler = new Handler() {
335 @Override
336 public void handleMessage(Message msg) {
337 switch (msg.what) {
338 case MSG_BUILD_HEADERS: {
339 mHeaders.clear();
340 onBuildHeaders(mHeaders);
341 mHeaderAdapter.notifyDataSetChanged();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800342 } break;
343 }
344 }
345 };
346
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700347 private boolean mNeedToRevertToInitialFragment = false;
348
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800349 @Override
350 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
351 // Override the fragment title for Wallpaper settings
352 int titleRes = pref.getTitleRes();
353 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
354 titleRes = R.string.wallpaper_settings_fragment_title;
355 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
356 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
357 if (UserManager.get(this).isLinkedUser()) {
358 titleRes = R.string.profile_info_settings_title;
359 } else {
360 titleRes = R.string.user_info_settings_title;
361 }
362 }
363 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
364 null, 0);
365 return true;
366 }
367
368 @Override
369 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
370 return false;
371 }
372
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800373 private void invalidateHeaders() {
374 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
375 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
376 }
377 }
378
379 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800380 public void onConfigurationChanged(Configuration newConfig) {
381 super.onConfigurationChanged(newConfig);
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800382 Index.getInstance(this).update();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800383 }
384
385 @Override
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700386 protected void onStart() {
387 super.onStart();
388
389 if (mNeedToRevertToInitialFragment) {
390 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800391 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800392 }
393
394 @Override
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700395 public boolean onCreateOptionsMenu(Menu menu) {
396 MenuInflater inflater = getMenuInflater();
397 inflater.inflate(R.menu.options_menu, menu);
398
399 // Cache the search query (can be overriden by the OnQueryTextListener)
400 final String query = mSearchQuery;
401
Fabrice Di Meglio95937822014-03-31 19:46:42 -0700402 mSearchMenuItem = menu.findItem(R.id.search);
403 mSearchView = (SearchView) mSearchMenuItem.getActionView();
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700404
Fabrice Di Meglio23ae00c2014-04-21 12:43:20 -0700405 if (mSearchMenuItem == null || mSearchView == null) {
406 return false;
407 }
408
Fabrice Di Meglio95937822014-03-31 19:46:42 -0700409 mSearchMenuItem.setOnActionExpandListener(this);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700410 mSearchView.setOnQueryTextListener(this);
411 mSearchView.setOnCloseListener(this);
412
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700413 if (mSearchMenuItemExpanded) {
414 mSearchMenuItem.expandActionView();
415 }
416 mSearchView.setQuery(query, true /* submit */);
417
418 return true;
419 }
420
421 @Override
422 protected void onCreate(Bundle savedState) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800423 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
424 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
425 }
Fabrice Di Meglio5ebabfc2014-04-21 09:40:46 -0700426
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800427 mAuthenticatorHelper = new AuthenticatorHelper();
428 mAuthenticatorHelper.updateAuthDescriptions(this);
429 mAuthenticatorHelper.onAccountsUpdated(this, null);
430
431 DevicePolicyManager dpm =
432 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
Fabrice Di Megliodca28062014-02-21 17:42:56 -0800433
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700434 mHeaderAdapter = new HeaderAdapter(this, mHeaders, mAuthenticatorHelper, dpm);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800435
436 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
437 Context.MODE_PRIVATE);
438
439 getMetaData();
440
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700441 super.onCreate(savedState);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800442
443 setContentView(R.layout.settings_main);
444
445 getFragmentManager().addOnBackStackChangedListener(this);
446
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700447 mDisplayHomeAsUpEnabled = true;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800448
Fabrice Di Meglio5cda21b2014-04-21 10:14:28 -0700449 // Getting Intent properties can only be done after the super.onCreate(...)
450 final String initialFragmentName = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
451
452 if (initialFragmentName == null) {
453 Index.getInstance(this).update();
454 }
455
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700456 if (savedState != null) {
Fabrice Di Meglio1800a9f2014-04-03 19:31:07 -0700457 // We are restarting from a previous saved state; used that to initialize, instead
458 // of starting fresh.
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700459 mSearchMenuItemExpanded = savedState.getBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED);
460 mSearchQuery = savedState.getString(SAVE_KEY_SEARCH_QUERY);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800461
Fabrice Di Meglio1800a9f2014-04-03 19:31:07 -0700462 final String initialTitle = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
463 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
464 setTitle(mInitialTitle);
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800465
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700466 ArrayList<Header> headers = savedState.getParcelableArrayList(SAVE_KEY_HEADERS);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800467 if (headers != null) {
468 mHeaders.addAll(headers);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700469 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800470 }
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700471
472 mDisplayHomeAsUpEnabled = savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800473 } else {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800474 // We need to build the Headers in all cases
475 onBuildHeaders(mHeaders);
476
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700477 if (initialFragmentName != null) {
478 final ComponentName cn = getIntent().getComponent();
479 // No UP is we are launched thru a Settings shortcut
480 if (!cn.getClassName().equals(SubSettings.class.getName())) {
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700481 mDisplayHomeAsUpEnabled = false;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700482 }
483 final String initialTitle = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
484 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800485 setTitle(mInitialTitle);
Fabrice Di Meglioc1457322014-04-04 19:07:50 -0700486
Fabrice Di Meglio5ebabfc2014-04-21 09:40:46 -0700487 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700488 switchToFragment( initialFragmentName, initialArguments, true, false,
489 mInitialTitle, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000490 } else {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700491 // No UP if we are displaying the Headers
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700492 mDisplayHomeAsUpEnabled = false;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000493 if (mHeaders.size() > 0) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700494 mInitialTitle = getText(R.string.dashboard_title);
495 switchToFragment(DashboardSummary.class.getName(), null, false, false,
496 mInitialTitle, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000497 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800498 }
499 }
500
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700501 mActionBar = getActionBar();
502 mActionBar.setHomeButtonEnabled(true);
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700503 mActionBar.setDisplayHomeAsUpEnabled(mDisplayHomeAsUpEnabled);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700504
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800505 // see if we should show Back/Next buttons
506 Intent intent = getIntent();
507 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
508
509 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
510 if (buttonBar != null) {
511 buttonBar.setVisibility(View.VISIBLE);
512
513 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
514 backButton.setOnClickListener(new OnClickListener() {
515 public void onClick(View v) {
516 setResult(RESULT_CANCELED);
517 finish();
518 }
519 });
520 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
521 skipButton.setOnClickListener(new OnClickListener() {
522 public void onClick(View v) {
523 setResult(RESULT_OK);
524 finish();
525 }
526 });
527 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
528 mNextButton.setOnClickListener(new OnClickListener() {
529 public void onClick(View v) {
530 setResult(RESULT_OK);
531 finish();
532 }
533 });
534
535 // set our various button parameters
536 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
537 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
538 if (TextUtils.isEmpty(buttonText)) {
539 mNextButton.setVisibility(View.GONE);
540 }
541 else {
542 mNextButton.setText(buttonText);
543 }
544 }
545 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
546 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
547 if (TextUtils.isEmpty(buttonText)) {
548 backButton.setVisibility(View.GONE);
549 }
550 else {
551 backButton.setText(buttonText);
552 }
553 }
554 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
555 skipButton.setVisibility(View.VISIBLE);
556 }
557 }
558 }
Fabrice Di Meglioc95be4f2014-03-07 12:57:38 -0800559 }
560
561 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800562 public void onBackStackChanged() {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700563 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800564 }
565
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700566 private int setTitleFromBackStack() {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800567 final int count = getFragmentManager().getBackStackEntryCount();
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700568
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800569 if (count == 0) {
570 setTitle(mInitialTitle);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700571 return 0;
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800572 }
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700573
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800574 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1);
575 setTitleFromBackStackEntry(bse);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700576
577 return count;
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800578 }
579
580 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
581 final CharSequence title;
582 final int titleRes = bse.getBreadCrumbTitleRes();
583 if (titleRes > 0) {
584 title = getText(titleRes);
585 } else {
586 title = bse.getBreadCrumbTitle();
587 }
588 if (title != null) {
589 setTitle(title);
590 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800591 }
592
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800593 @Override
594 protected void onSaveInstanceState(Bundle outState) {
595 super.onSaveInstanceState(outState);
596
597 if (mHeaders.size() > 0) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700598 outState.putParcelableArrayList(SAVE_KEY_HEADERS, mHeaders);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800599 }
Fabrice Di Megliod6985df2014-04-03 16:43:26 -0700600
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700601 outState.putBoolean(SAVE_KEY_SHOW_HOME_AS_UP, mDisplayHomeAsUpEnabled);
602
Fabrice Di Megliod6985df2014-04-03 16:43:26 -0700603 // The option menus are created if the ActionBar is visible and they are also created
604 // asynchronously. If you launch Settings with an Intent action like
605 // android.intent.action.POWER_USAGE_SUMMARY and at the same time your device is locked
606 // thru a LockScreen, onCreateOptionsMenu() is not yet called and references to the search
607 // menu item and search view are null.
608 boolean isExpanded = (mSearchMenuItem != null) && mSearchMenuItem.isActionViewExpanded();
609 outState.putBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED, isExpanded);
610
611 String query = (mSearchView != null) ? mSearchView.getQuery().toString() : EMPTY_QUERY;
612 outState.putString(SAVE_KEY_SEARCH_QUERY, query);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800613 }
614
615 @Override
616 public void onResume() {
617 super.onResume();
618
619 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
620 @Override
621 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
622 invalidateHeaders();
623 }
624 };
625 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
626 mDevelopmentPreferencesListener);
627
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800628 invalidateHeaders();
629
630 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Svetoslav853e4712014-04-14 10:10:25 -0700631
Svetoslav990159a2014-04-14 17:14:59 -0700632 mDynamicIndexableContentMonitor.register(this);
Fabrice Di Meglioa3270762014-04-16 16:54:56 -0700633
634 if(!TextUtils.isEmpty(mSearchQuery)) {
635 onQueryTextSubmit(mSearchQuery);
636 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800637 }
638
639 @Override
640 public void onPause() {
641 super.onPause();
642
643 unregisterReceiver(mBatteryInfoReceiver);
644
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800645 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
646 mDevelopmentPreferencesListener);
647
648 mDevelopmentPreferencesListener = null;
Svetoslav853e4712014-04-14 10:10:25 -0700649
Svetoslav990159a2014-04-14 17:14:59 -0700650 mDynamicIndexableContentMonitor.unregister();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800651 }
652
653 @Override
654 public void onDestroy() {
655 super.onDestroy();
656 if (mListeningToAccountUpdates) {
657 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
658 }
659 }
660
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800661 protected boolean isValidFragment(String fragmentName) {
662 // Almost all fragments are wrapped in this,
663 // except for a few that have their own activities.
664 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
665 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
666 }
667 return false;
668 }
669
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800670 /**
671 * When in two-pane mode, switch to the fragment pane to show the given
672 * preference fragment.
673 *
674 * @param header The new header to display.
Fabrice Di Megliobbdada82014-04-04 10:16:59 -0700675 * @param position The position of the Header in the list.
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800676 */
Fabrice Di Megliobbdada82014-04-04 10:16:59 -0700677 private void onHeaderClick(Header header, int position) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800678 if (header == null) {
679 return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800680 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700681 if (header.fragment != null) {
682 startWithFragment(header.fragment, header.fragmentArguments, null, 0,
683 header.getTitle(getResources()));
684 } else if (header.intent != null) {
685 startActivity(header.intent);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800686 }
687 }
688
689 /**
690 * Called to determine whether the header list should be hidden.
691 * The default implementation returns the
692 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
693 * This is set to false, for example, when the activity is being re-launched
694 * to show a particular preference activity.
695 */
696 public boolean onIsHidingHeaders() {
697 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
698 }
699
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800700 @Override
701 public Intent getIntent() {
702 Intent superIntent = super.getIntent();
703 String startingFragment = getStartingFragmentClass(superIntent);
704 // This is called from super.onCreate, isMultiPane() is not yet reliable
705 // Do not use onIsHidingHeaders either, which relies itself on this method
706 if (startingFragment != null) {
707 Intent modIntent = new Intent(superIntent);
708 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
709 Bundle args = superIntent.getExtras();
710 if (args != null) {
711 args = new Bundle(args);
712 } else {
713 args = new Bundle();
714 }
715 args.putParcelable("intent", superIntent);
716 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
717 return modIntent;
718 }
719 return superIntent;
720 }
721
722 /**
723 * Checks if the component name in the intent is different from the Settings class and
724 * returns the class name to load as a fragment.
725 */
726 private String getStartingFragmentClass(Intent intent) {
727 if (mFragmentClass != null) return mFragmentClass;
728
729 String intentClass = intent.getComponent().getClassName();
730 if (intentClass.equals(getClass().getName())) return null;
731
732 if ("com.android.settings.ManageApplications".equals(intentClass)
733 || "com.android.settings.RunningServices".equals(intentClass)
734 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
735 // Old names of manage apps.
736 intentClass = com.android.settings.applications.ManageApplications.class.getName();
737 }
738
739 return intentClass;
740 }
741
742 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000743 * Start a new fragment containing a preference panel. If the preferences
744 * are being displayed in multi-pane mode, the given fragment class will
745 * be instantiated and placed in the appropriate pane. If running in
746 * single-pane mode, a new activity will be launched in which to show the
747 * fragment.
748 *
749 * @param fragmentClass Full name of the class implementing the fragment.
750 * @param args Any desired arguments to supply to the fragment.
751 * @param titleRes Optional resource identifier of the title of this
752 * fragment.
753 * @param titleText Optional text of the title of this fragment.
754 * @param resultTo Optional fragment that result data should be sent to.
755 * If non-null, resultTo.onActivityResult() will be called when this
756 * preference panel is done. The launched panel must use
757 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
758 * @param resultRequestCode If resultTo is non-null, this is the caller's
759 * request code to be received with the resut.
760 */
761 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700762 CharSequence titleText, Fragment resultTo, int resultRequestCode) {
Fabrice Di Meglio911fb2a2014-04-04 17:55:57 -0700763 String title;
764 if (titleRes > 0) {
765 title = getString(titleRes);
Fabrice Di Meglio821a0722014-04-08 13:56:06 -0700766 } else if (titleText != null) {
Fabrice Di Meglio911fb2a2014-04-04 17:55:57 -0700767 title = titleText.toString();
Fabrice Di Meglio821a0722014-04-08 13:56:06 -0700768 } else {
769 // There not much we can do in that case
770 title = "";
Fabrice Di Meglio911fb2a2014-04-04 17:55:57 -0700771 }
772 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, title);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000773 }
774
775 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800776 * Called by a preference panel fragment to finish itself.
777 *
778 * @param caller The fragment that is asking to be finished.
779 * @param resultCode Optional result code to send back to the original
780 * launching fragment.
781 * @param resultData Optional result data to send back to the original
782 * launching fragment.
783 */
784 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
785 setResult(resultCode, resultData);
786 }
787
788 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000789 * Start a new fragment.
790 *
791 * @param fragment The fragment to start
792 * @param push If true, the current fragment will be pushed onto the back stack. If false,
793 * the current fragment will be replaced.
794 */
795 public void startPreferenceFragment(Fragment fragment, boolean push) {
796 FragmentTransaction transaction = getFragmentManager().beginTransaction();
797 transaction.replace(R.id.prefs, fragment);
798 if (push) {
799 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
800 transaction.addToBackStack(BACK_STACK_PREFS);
801 } else {
802 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
803 }
804 transaction.commitAllowingStateLoss();
805 }
806
807 /**
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700808 * Switch to a specific Fragment with taking care of validation, Title and BackStack
809 */
810 private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
811 boolean addToBackStack, CharSequence title, boolean withTransition) {
812 if (validate && !isValidFragment(fragmentName)) {
813 throw new IllegalArgumentException("Invalid fragment for this activity: "
814 + fragmentName);
815 }
816 Fragment f = Fragment.instantiate(this, fragmentName, args);
817 FragmentTransaction transaction = getFragmentManager().beginTransaction();
818 transaction.replace(R.id.prefs, f);
819 if (withTransition) {
820 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
821 }
822 if (addToBackStack) {
823 transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
824 }
825 if (title != null) {
826 transaction.setBreadCrumbTitle(title);
827 }
828 transaction.commitAllowingStateLoss();
829 return f;
830 }
831
832 /**
833 * Start a new instance of this activity, showing only the given fragment.
834 * When launched in this mode, the given preference fragment will be instantiated and fill the
835 * entire activity.
836 *
837 * @param fragmentName The name of the fragment to display.
838 * @param args Optional arguments to supply to the fragment.
839 * @param resultTo Option fragment that should receive the result of
840 * the activity launch.
841 * @param resultRequestCode If resultTo is non-null, this is the request
842 * code in which to report the result.
843 * @param title String to display for the title of this set of preferences.
844 */
845 public void startWithFragment(String fragmentName, Bundle args,
846 Fragment resultTo, int resultRequestCode, CharSequence title) {
847 Intent intent = onBuildStartFragmentIntent(fragmentName, args, title);
848 if (resultTo == null) {
849 startActivity(intent);
850 } else {
851 resultTo.startActivityForResult(intent, resultRequestCode);
852 }
853 }
854
855 /**
856 * Build an Intent to launch a new activity showing the selected fragment.
857 * The implementation constructs an Intent that re-launches the current activity with the
858 * appropriate arguments to display the fragment.
859 *
860 * @param fragmentName The name of the fragment to display.
861 * @param args Optional arguments to supply to the fragment.
862 * @param title Optional title to show for this item.
863 * @return Returns an Intent that can be launched to display the given
864 * fragment.
865 */
866 public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args, CharSequence title) {
867 Intent intent = new Intent(Intent.ACTION_MAIN);
868 intent.setClass(this, SubSettings.class);
869 intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
870 intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
871 intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, title);
872 intent.putExtra(EXTRA_NO_HEADERS, true);
873 return intent;
874 }
875
876 /**
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800877 * Called when the activity needs its list of headers build.
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800878 *
879 * @param headers The list in which to place the headers.
880 */
881 private void onBuildHeaders(List<Header> headers) {
882 loadHeadersFromResource(R.xml.settings_headers, headers);
883 updateHeaderList(headers);
884 }
885
886 /**
887 * Parse the given XML file as a header description, adding each
888 * parsed Header into the target list.
889 *
890 * @param resid The XML resource to load and parse.
891 * @param target The list in which the parsed headers should be placed.
892 */
893 private void loadHeadersFromResource(int resid, List<Header> target) {
894 XmlResourceParser parser = null;
895 try {
896 parser = getResources().getXml(resid);
897 AttributeSet attrs = Xml.asAttributeSet(parser);
898
899 int type;
900 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
901 && type != XmlPullParser.START_TAG) {
902 // Parse next until start tag is found
903 }
904
905 String nodeName = parser.getName();
906 if (!"preference-headers".equals(nodeName)) {
907 throw new RuntimeException(
908 "XML document must start with <preference-headers> tag; found"
909 + nodeName + " at " + parser.getPositionDescription());
910 }
911
912 Bundle curBundle = null;
913
914 final int outerDepth = parser.getDepth();
915 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
916 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
917 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
918 continue;
919 }
920
921 nodeName = parser.getName();
922 if ("header".equals(nodeName)) {
923 Header header = new Header();
924
925 TypedArray sa = obtainStyledAttributes(
926 attrs, com.android.internal.R.styleable.PreferenceHeader);
927 header.id = sa.getResourceId(
928 com.android.internal.R.styleable.PreferenceHeader_id,
929 (int)HEADER_ID_UNDEFINED);
930 TypedValue tv = sa.peekValue(
931 com.android.internal.R.styleable.PreferenceHeader_title);
932 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
933 if (tv.resourceId != 0) {
934 header.titleRes = tv.resourceId;
935 } else {
936 header.title = tv.string;
937 }
938 }
939 tv = sa.peekValue(
940 com.android.internal.R.styleable.PreferenceHeader_summary);
941 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
942 if (tv.resourceId != 0) {
943 header.summaryRes = tv.resourceId;
944 } else {
945 header.summary = tv.string;
946 }
947 }
948 header.iconRes = sa.getResourceId(
949 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
950 header.fragment = sa.getString(
951 com.android.internal.R.styleable.PreferenceHeader_fragment);
952 sa.recycle();
953
954 if (curBundle == null) {
955 curBundle = new Bundle();
956 }
957
958 final int innerDepth = parser.getDepth();
959 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
960 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
961 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
962 continue;
963 }
964
965 String innerNodeName = parser.getName();
966 if (innerNodeName.equals("extra")) {
967 getResources().parseBundleExtra("extra", attrs, curBundle);
968 XmlUtils.skipCurrentTag(parser);
969
970 } else if (innerNodeName.equals("intent")) {
971 header.intent = Intent.parseIntent(getResources(), parser, attrs);
972
973 } else {
974 XmlUtils.skipCurrentTag(parser);
975 }
976 }
977
978 if (curBundle.size() > 0) {
979 header.fragmentArguments = curBundle;
980 curBundle = null;
981 }
982
983 target.add(header);
984 } else {
985 XmlUtils.skipCurrentTag(parser);
986 }
987 }
988
989 } catch (XmlPullParserException e) {
990 throw new RuntimeException("Error parsing headers", e);
991 } catch (IOException e) {
992 throw new RuntimeException("Error parsing headers", e);
993 } finally {
994 if (parser != null) parser.close();
995 }
996 }
997
998 private void updateHeaderList(List<Header> target) {
999 final boolean showDev = mDevelopmentPreferences.getBoolean(
1000 DevelopmentSettings.PREF_SHOW,
1001 android.os.Build.TYPE.equals("eng"));
1002 int i = 0;
1003
1004 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001005 while (i < target.size()) {
1006 Header header = target.get(i);
1007 // Ids are integers, so downcasting
1008 int id = (int) header.id;
1009 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1010 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1011 } else if (id == R.id.wifi_settings) {
1012 // Remove WiFi Settings if WiFi service is not available.
1013 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1014 target.remove(i);
1015 }
1016 } else if (id == R.id.bluetooth_settings) {
1017 // Remove Bluetooth Settings if Bluetooth service is not available.
1018 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1019 target.remove(i);
1020 }
1021 } else if (id == R.id.data_usage_settings) {
1022 // Remove data usage when kernel module not enabled
1023 final INetworkManagementService netManager = INetworkManagementService.Stub
1024 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1025 try {
1026 if (!netManager.isBandwidthControlEnabled()) {
1027 target.remove(i);
1028 }
1029 } catch (RemoteException e) {
1030 // ignored
1031 }
1032 } else if (id == R.id.battery_settings) {
1033 // Remove battery settings when battery is not available. (e.g. TV)
1034
1035 if (!mBatteryPresent) {
1036 target.remove(i);
1037 }
1038 } else if (id == R.id.account_settings) {
1039 int headerIndex = i + 1;
1040 i = insertAccountsHeaders(target, headerIndex);
1041 } else if (id == R.id.home_settings) {
1042 if (!updateHomeSettingHeaders(header)) {
1043 target.remove(i);
1044 }
1045 } else if (id == R.id.user_settings) {
1046 if (!UserHandle.MU_ENABLED
1047 || !UserManager.supportsMultipleUsers()
1048 || Utils.isMonkeyRunning()) {
1049 target.remove(i);
1050 }
1051 } else if (id == R.id.nfc_payment_settings) {
1052 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1053 target.remove(i);
1054 } else {
1055 // Only show if NFC is on and we have the HCE feature
1056 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1057 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1058 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1059 target.remove(i);
1060 }
1061 }
1062 } else if (id == R.id.development_settings) {
1063 if (!showDev) {
1064 target.remove(i);
1065 }
1066 } else if (id == R.id.account_add) {
1067 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1068 target.remove(i);
1069 }
1070 }
1071
1072 if (i < target.size() && target.get(i) == header
1073 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1074 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1075 target.remove(i);
1076 }
1077
1078 // Increment if the current one wasn't removed by the Utils code.
1079 if (i < target.size() && target.get(i) == header) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001080 i++;
1081 }
1082 }
1083 }
1084
1085 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1086 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1087 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1088 for (String accountType : accountTypes) {
1089 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1090 if (label == null) {
1091 continue;
1092 }
1093
1094 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1095 boolean skipToAccount = accounts.length == 1
1096 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1097 Header accHeader = new Header();
1098 accHeader.title = label;
1099 if (accHeader.extras == null) {
1100 accHeader.extras = new Bundle();
1101 }
1102 if (skipToAccount) {
1103 accHeader.fragment = AccountSyncSettings.class.getName();
1104 accHeader.fragmentArguments = new Bundle();
1105 // Need this for the icon
1106 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1107 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1108 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1109 accounts[0]);
1110 } else {
1111 accHeader.fragment = ManageAccountsSettings.class.getName();
1112 accHeader.fragmentArguments = new Bundle();
1113 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1114 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1115 accountType);
1116 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1117 label.toString());
1118 }
1119 accountHeaders.add(accHeader);
1120 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1121 }
1122
1123 // Sort by label
1124 Collections.sort(accountHeaders, new Comparator<Header>() {
1125 @Override
1126 public int compare(Header h1, Header h2) {
1127 return h1.title.toString().compareTo(h2.title.toString());
1128 }
1129 });
1130
1131 for (Header header : accountHeaders) {
1132 target.add(headerIndex++, header);
1133 }
1134 if (!mListeningToAccountUpdates) {
1135 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1136 mListeningToAccountUpdates = true;
1137 }
1138 return headerIndex;
1139 }
1140
1141 private boolean updateHomeSettingHeaders(Header header) {
1142 // Once we decide to show Home settings, keep showing it forever
1143 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1144 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1145 return true;
1146 }
1147
1148 try {
1149 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1150 getPackageManager().getHomeActivities(homeApps);
1151 if (homeApps.size() < 2) {
1152 // When there's only one available home app, omit this settings
1153 // category entirely at the top level UI. If the user just
1154 // uninstalled the penultimate home app candidiate, we also
1155 // now tell them about why they aren't seeing 'Home' in the list.
1156 if (sShowNoHomeNotice) {
1157 sShowNoHomeNotice = false;
1158 NoHomeDialogFragment.show(this);
1159 }
1160 return false;
1161 } else {
1162 // Okay, we're allowing the Home settings category. Tell it, when
1163 // invoked via this front door, that we'll need to be told about the
1164 // case when the user uninstalls all but one home app.
1165 if (header.fragmentArguments == null) {
1166 header.fragmentArguments = new Bundle();
1167 }
1168 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1169 }
1170 } catch (Exception e) {
1171 // Can't look up the home activity; bail on configuring the icon
1172 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1173 }
1174
1175 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1176 return true;
1177 }
1178
Fabrice Di Meglio63bbb8e2014-04-23 16:44:30 -07001179 /**
1180 * Called when the activity needs its list of categories/tiles build.
1181 *
1182 * @param categories The list in which to place the tiles categories.
1183 */
1184 private void onBuildDashboardCategories(List<DashboardCategory> categories) {
1185 loadCategoriesFromResource(R.xml.dashboard_categories, categories);
1186 updateTilesList(categories);
1187 }
1188
1189 /**
1190 * Parse the given XML file as a categories description, adding each
1191 * parsed categories and tiles into the target list.
1192 *
1193 * @param resid The XML resource to load and parse.
1194 * @param target The list in which the parsed categories and tiles should be placed.
1195 */
1196 private void loadCategoriesFromResource(int resid, List<DashboardCategory> target) {
1197 XmlResourceParser parser = null;
1198 try {
1199 parser = getResources().getXml(resid);
1200 AttributeSet attrs = Xml.asAttributeSet(parser);
1201
1202 int type;
1203 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1204 && type != XmlPullParser.START_TAG) {
1205 // Parse next until start tag is found
1206 }
1207
1208 String nodeName = parser.getName();
1209 if (!"dashboard-categories".equals(nodeName)) {
1210 throw new RuntimeException(
1211 "XML document must start with <preference-categories> tag; found"
1212 + nodeName + " at " + parser.getPositionDescription());
1213 }
1214
1215 Bundle curBundle = null;
1216
1217 final int outerDepth = parser.getDepth();
1218 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1219 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1220 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1221 continue;
1222 }
1223
1224 nodeName = parser.getName();
1225 if ("dashboard-category".equals(nodeName)) {
1226 DashboardCategory category = new DashboardCategory();
1227
1228 TypedArray sa = obtainStyledAttributes(
1229 attrs, com.android.internal.R.styleable.PreferenceHeader);
1230 category.id = sa.getResourceId(
1231 com.android.internal.R.styleable.PreferenceHeader_id,
1232 (int)DashboardCategory.CAT_ID_UNDEFINED);
1233
1234 TypedValue tv = sa.peekValue(
1235 com.android.internal.R.styleable.PreferenceHeader_title);
1236 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1237 if (tv.resourceId != 0) {
1238 category.titleRes = tv.resourceId;
1239 } else {
1240 category.title = tv.string;
1241 }
1242 }
1243 sa.recycle();
1244
1245 final int innerDepth = parser.getDepth();
1246 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1247 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
1248 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1249 continue;
1250 }
1251
1252 String innerNodeName = parser.getName();
1253 if (innerNodeName.equals("dashboard-tile")) {
1254 DashboardTile tile = new DashboardTile();
1255
1256 sa = obtainStyledAttributes(
1257 attrs, com.android.internal.R.styleable.PreferenceHeader);
1258 tile.id = sa.getResourceId(
1259 com.android.internal.R.styleable.PreferenceHeader_id,
1260 (int)HEADER_ID_UNDEFINED);
1261 tv = sa.peekValue(
1262 com.android.internal.R.styleable.PreferenceHeader_title);
1263 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1264 if (tv.resourceId != 0) {
1265 tile.titleRes = tv.resourceId;
1266 } else {
1267 tile.title = tv.string;
1268 }
1269 }
1270 tv = sa.peekValue(
1271 com.android.internal.R.styleable.PreferenceHeader_summary);
1272 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1273 if (tv.resourceId != 0) {
1274 tile.summaryRes = tv.resourceId;
1275 } else {
1276 tile.summary = tv.string;
1277 }
1278 }
1279 tile.iconRes = sa.getResourceId(
1280 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
1281 tile.fragment = sa.getString(
1282 com.android.internal.R.styleable.PreferenceHeader_fragment);
1283 sa.recycle();
1284
1285 if (curBundle == null) {
1286 curBundle = new Bundle();
1287 }
1288
1289 final int innerDepth2 = parser.getDepth();
1290 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1291 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth2)) {
1292 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1293 continue;
1294 }
1295
1296 String innerNodeName2 = parser.getName();
1297 if (innerNodeName2.equals("extra")) {
1298 getResources().parseBundleExtra("extra", attrs, curBundle);
1299 XmlUtils.skipCurrentTag(parser);
1300
1301 } else if (innerNodeName2.equals("intent")) {
1302 tile.intent = Intent.parseIntent(getResources(), parser, attrs);
1303
1304 } else {
1305 XmlUtils.skipCurrentTag(parser);
1306 }
1307 }
1308
1309 if (curBundle.size() > 0) {
1310 tile.fragmentArguments = curBundle;
1311 curBundle = null;
1312 }
1313
1314 category.addTile(tile);
1315
1316 } else {
1317 XmlUtils.skipCurrentTag(parser);
1318 }
1319 }
1320
1321 target.add(category);
1322 } else {
1323 XmlUtils.skipCurrentTag(parser);
1324 }
1325 }
1326
1327 } catch (XmlPullParserException e) {
1328 throw new RuntimeException("Error parsing categories", e);
1329 } catch (IOException e) {
1330 throw new RuntimeException("Error parsing categories", e);
1331 } finally {
1332 if (parser != null) parser.close();
1333 }
1334 }
1335
1336 private void updateTilesList(List<DashboardCategory> target) {
1337 final boolean showDev = mDevelopmentPreferences.getBoolean(
1338 DevelopmentSettings.PREF_SHOW,
1339 android.os.Build.TYPE.equals("eng"));
1340
1341 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1342
1343 final int size = target.size();
1344 for (int i = 0; i < size; i++) {
1345
1346 DashboardCategory category = target.get(i);
1347
1348 // Ids are integers, so downcasting is ok
1349 int id = (int) category.id;
1350 if (id == R.id.account_settings) {
1351 insertAccountsTiles(category);
1352 continue;
1353 }
1354 int n = category.getTilesCount() - 1;
1355 while (n >= 0) {
1356
1357 DashboardTile tile = category.getTile(n);
1358
1359 id = (int) tile.id;
1360 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1361 Utils.updateTileToSpecificActivityFromMetaDataOrRemove(this, category, tile);
1362 } else if (id == R.id.wifi_settings) {
1363 // Remove WiFi Settings if WiFi service is not available.
1364 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1365 category.removeTile(n);
1366 }
1367 } else if (id == R.id.bluetooth_settings) {
1368 // Remove Bluetooth Settings if Bluetooth service is not available.
1369 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1370 category.removeTile(n);
1371 }
1372 } else if (id == R.id.data_usage_settings) {
1373 // Remove data usage when kernel module not enabled
1374 final INetworkManagementService netManager = INetworkManagementService.Stub
1375 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1376 try {
1377 if (!netManager.isBandwidthControlEnabled()) {
1378 category.removeTile(n);
1379 }
1380 } catch (RemoteException e) {
1381 // ignored
1382 }
1383 } else if (id == R.id.battery_settings) {
1384 // Remove battery settings when battery is not available. (e.g. TV)
1385
1386 if (!mBatteryPresent) {
1387 category.removeTile(n);
1388 }
1389 } else if (id == R.id.home_settings) {
1390 if (!updateHomeSettingTiles(tile)) {
1391 category.removeTile(n);
1392 }
1393 } else if (id == R.id.user_settings) {
1394 if (!UserHandle.MU_ENABLED
1395 || !UserManager.supportsMultipleUsers()
1396 || Utils.isMonkeyRunning()) {
1397 category.removeTile(n);
1398 }
1399 } else if (id == R.id.nfc_payment_settings) {
1400 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1401 category.removeTile(n);
1402 } else {
1403 // Only show if NFC is on and we have the HCE feature
1404 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1405 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1406 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1407 category.removeTile(n);
1408 }
1409 }
1410 } else if (id == R.id.development_settings) {
1411 if (!showDev) {
1412 category.removeTile(n);
1413 }
1414 } else if (id == R.id.account_add) {
1415 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1416 category.removeTile(n);
1417 }
1418 }
1419
1420 if (UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1421 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1422 category.removeTile(n);
1423 }
1424
1425 n--;
1426 }
1427 }
1428 }
1429
1430 private boolean updateHomeSettingTiles(DashboardTile tile) {
1431 // Once we decide to show Home settings, keep showing it forever
1432 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1433 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1434 return true;
1435 }
1436
1437 try {
1438 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1439 getPackageManager().getHomeActivities(homeApps);
1440 if (homeApps.size() < 2) {
1441 // When there's only one available home app, omit this settings
1442 // category entirely at the top level UI. If the user just
1443 // uninstalled the penultimate home app candidiate, we also
1444 // now tell them about why they aren't seeing 'Home' in the list.
1445 if (sShowNoHomeNotice) {
1446 sShowNoHomeNotice = false;
1447 NoHomeDialogFragment.show(this);
1448 }
1449 return false;
1450 } else {
1451 // Okay, we're allowing the Home settings category. Tell it, when
1452 // invoked via this front door, that we'll need to be told about the
1453 // case when the user uninstalls all but one home app.
1454 if (tile.fragmentArguments == null) {
1455 tile.fragmentArguments = new Bundle();
1456 }
1457 tile.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1458 }
1459 } catch (Exception e) {
1460 // Can't look up the home activity; bail on configuring the icon
1461 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1462 }
1463
1464 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1465 return true;
1466 }
1467
1468 private void insertAccountsTiles(DashboardCategory target) {
1469 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1470 List<DashboardTile> dashboardTiles = new ArrayList<DashboardTile>(accountTypes.length);
1471 for (String accountType : accountTypes) {
1472 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1473 if (label == null) {
1474 continue;
1475 }
1476
1477 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1478 boolean skipToAccount = accounts.length == 1
1479 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1480 DashboardTile accountTile = new DashboardTile();
1481 accountTile.title = label;
1482 if (accountTile.extras == null) {
1483 accountTile.extras = new Bundle();
1484 }
1485 if (skipToAccount) {
1486 accountTile.fragment = AccountSyncSettings.class.getName();
1487 accountTile.fragmentArguments = new Bundle();
1488 // Need this for the icon
1489 accountTile.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1490 accountTile.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1491 accountTile.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1492 accounts[0]);
1493 } else {
1494 accountTile.fragment = ManageAccountsSettings.class.getName();
1495 accountTile.fragmentArguments = new Bundle();
1496 accountTile.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1497 accountTile.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1498 accountType);
1499 accountTile.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1500 label.toString());
1501 }
1502 dashboardTiles.add(accountTile);
1503 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1504 }
1505
1506 // Sort by label
1507 Collections.sort(dashboardTiles, new Comparator<DashboardTile>() {
1508 @Override
1509 public int compare(DashboardTile t1, DashboardTile t2) {
1510 return t1.title.toString().compareTo(t2.title.toString());
1511 }
1512 });
1513 int index = 0;
1514 for (DashboardTile tile : dashboardTiles) {
1515 target.addTile(index, tile);
1516 index++;
1517 }
1518 if (!mListeningToAccountUpdates) {
1519 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1520 mListeningToAccountUpdates = true;
1521 }
1522 }
1523
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001524 private void getMetaData() {
1525 try {
1526 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1527 PackageManager.GET_META_DATA);
1528 if (ai == null || ai.metaData == null) return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001529 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1530 } catch (NameNotFoundException nnfe) {
1531 // No recovery
1532 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1533 }
1534 }
1535
1536 // give subclasses access to the Next button
1537 public boolean hasNextButton() {
1538 return mNextButton != null;
1539 }
1540
1541 public Button getNextButton() {
1542 return mNextButton;
1543 }
1544
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001545 public HeaderAdapter getHeaderAdapter() {
1546 return mHeaderAdapter;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001547 }
1548
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001549 public void onListItemClick(ListView l, View v, int position, long id) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001550 if (!isResumed()) {
1551 return;
1552 }
1553 Object item = mHeaderAdapter.getItem(position);
1554 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001555 mSelectedHeader = (Header) item;
Fabrice Di Megliobbdada82014-04-04 10:16:59 -07001556 onHeaderClick(mSelectedHeader, position);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001557 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001558 }
1559 }
1560
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001561 @Override
1562 public boolean shouldUpRecreateTask(Intent targetIntent) {
1563 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1564 }
1565
1566 @Override
1567 public void onAccountsUpdated(Account[] accounts) {
1568 // TODO: watch for package upgrades to invalidate cache; see 7206643
1569 mAuthenticatorHelper.updateAuthDescriptions(this);
1570 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1571 invalidateHeaders();
1572 }
1573
1574 public static void requestHomeNotice() {
1575 sShowNoHomeNotice = true;
1576 }
1577
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001578 @Override
1579 public boolean onQueryTextSubmit(String query) {
1580 switchToSearchResultsFragmentIfNeeded();
1581 mSearchQuery = query;
1582 return mSearchResultsFragment.onQueryTextSubmit(query);
1583 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001584
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001585 @Override
1586 public boolean onQueryTextChange(String newText) {
1587 mSearchQuery = newText;
Fabrice Di Meglioa3270762014-04-16 16:54:56 -07001588 if (TextUtils.isEmpty(newText) && mSearchResultsFragment == null) {
1589 return false;
1590 }
1591 return mSearchResultsFragment.onQueryTextChange(newText);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001592 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001593
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001594 @Override
1595 public boolean onClose() {
1596 return false;
1597 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001598
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001599 @Override
1600 public boolean onMenuItemActionExpand(MenuItem item) {
1601 if (item.getItemId() == mSearchMenuItem.getItemId()) {
Fabrice Di Megliobb16fd82014-04-04 14:48:05 -07001602 switchToSearchResultsFragmentIfNeeded();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001603 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001604 return true;
1605 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001606
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001607 @Override
1608 public boolean onMenuItemActionCollapse(MenuItem item) {
1609 if (item.getItemId() == mSearchMenuItem.getItemId()) {
Fabrice Di Megliobb16fd82014-04-04 14:48:05 -07001610 if (mSearchMenuItemExpanded) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001611 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001612 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001613 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001614 return true;
1615 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001616
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001617 private void switchToSearchResultsFragmentIfNeeded() {
Fabrice Di Megliobb16fd82014-04-04 14:48:05 -07001618 if (mSearchResultsFragment != null) {
1619 return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001620 }
Fabrice Di Megliobb16fd82014-04-04 14:48:05 -07001621 Fragment current = getFragmentManager().findFragmentById(R.id.prefs);
1622 if (current != null && current instanceof SearchResultsSummary) {
1623 mSearchResultsFragment = (SearchResultsSummary) current;
1624 } else {
1625 String title = getString(R.string.search_results_title);
1626 mSearchResultsFragment = (SearchResultsSummary) switchToFragment(
1627 SearchResultsSummary.class.getName(), null, false, true, title, true);
1628 }
Fabrice Di Megliod297a582014-04-22 17:23:23 -07001629 mSearchResultsFragment.setSearchView(mSearchView);
Fabrice Di Megliobb16fd82014-04-04 14:48:05 -07001630 mSearchMenuItemExpanded = true;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001631 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001632
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001633 public void needToRevertToInitialFragment() {
1634 mNeedToRevertToInitialFragment = true;
1635 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001636
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001637 private void revertToInitialFragment() {
1638 mNeedToRevertToInitialFragment = false;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001639 mSearchResultsFragment = null;
Fabrice Di Megliobb16fd82014-04-04 14:48:05 -07001640 mSearchMenuItemExpanded = false;
1641 getFragmentManager().popBackStackImmediate(SettingsActivity.BACK_STACK_PREFS,
1642 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fabrice Di Meglio23ae00c2014-04-21 12:43:20 -07001643 if (mSearchMenuItem != null) {
1644 mSearchMenuItem.collapseActionView();
1645 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001646 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001647}