blob: 314b3c9a43e3e3059c5de7eb68ddd5d15a379028 [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.content.BroadcastReceiver;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070028import android.content.ComponentName;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080029import android.content.Context;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.content.SharedPreferences;
33import android.content.pm.ActivityInfo;
34import android.content.pm.PackageManager;
35import android.content.pm.PackageManager.NameNotFoundException;
36import android.content.pm.ResolveInfo;
37import android.content.res.Configuration;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080038import android.content.res.TypedArray;
39import android.content.res.XmlResourceParser;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080040import android.nfc.NfcAdapter;
41import android.os.Bundle;
42import android.os.Handler;
43import android.os.INetworkManagementService;
44import android.os.Message;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080045import android.os.RemoteException;
46import android.os.ServiceManager;
47import android.os.UserHandle;
48import android.os.UserManager;
49import android.preference.Preference;
50import android.preference.PreferenceFragment;
51import android.preference.PreferenceManager;
52import android.preference.PreferenceScreen;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080053import android.text.TextUtils;
54import android.util.AttributeSet;
55import android.util.Log;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080056import android.util.TypedValue;
57import android.util.Xml;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070058import android.view.Menu;
59import android.view.MenuInflater;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080060import android.view.MenuItem;
61import android.view.View;
62import android.view.View.OnClickListener;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080063import android.widget.Button;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080064
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070065import android.widget.SearchView;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080066import com.android.internal.util.ArrayUtils;
67import com.android.internal.util.XmlUtils;
68import com.android.settings.accessibility.AccessibilitySettings;
69import com.android.settings.accessibility.CaptionPropertiesFragment;
70import com.android.settings.accounts.AccountSyncSettings;
71import com.android.settings.accounts.AuthenticatorHelper;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080072import com.android.settings.accounts.ManageAccountsSettings;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070073import com.android.settings.applications.InstalledAppDetails;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080074import com.android.settings.applications.ManageApplications;
75import com.android.settings.applications.ProcessStatsUi;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080076import com.android.settings.bluetooth.BluetoothSettings;
Fabrice Di Meglio63bbb8e2014-04-23 16:44:30 -070077import com.android.settings.dashboard.DashboardCategory;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080078import com.android.settings.dashboard.DashboardSummary;
Fabrice Di Meglio63bbb8e2014-04-23 16:44:30 -070079import com.android.settings.dashboard.DashboardTile;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070080import com.android.settings.dashboard.Header;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070081import com.android.settings.dashboard.NoHomeDialogFragment;
82import com.android.settings.dashboard.SearchResultsSummary;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080083import com.android.settings.deviceinfo.Memory;
84import com.android.settings.deviceinfo.UsbSettings;
85import com.android.settings.fuelgauge.PowerUsageSummary;
Fabrice Di Meglio7a6bfd12014-04-14 19:49:18 -070086import com.android.settings.search.DynamicIndexableContentMonitor;
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;
John Spurlockc9afadb2014-04-29 18:07:23 -040095import com.android.settings.notification.ConditionProviderSettings;
John Spurlock4a350512014-04-08 14:08:21 -040096import com.android.settings.notification.NotificationAccessSettings;
97import com.android.settings.notification.NotificationSettings;
98import com.android.settings.notification.NotificationStation;
99import com.android.settings.notification.ZenModeSettings;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800100import com.android.settings.print.PrintJobSettingsFragment;
101import com.android.settings.print.PrintSettingsFragment;
102import com.android.settings.tts.TextToSpeechSettings;
103import com.android.settings.users.UserSettings;
104import com.android.settings.vpn2.VpnSettings;
105import com.android.settings.wfd.WifiDisplaySettings;
106import com.android.settings.wifi.AdvancedWifiSettings;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800107import com.android.settings.wifi.WifiSettings;
108import com.android.settings.wifi.p2p.WifiP2pSettings;
109import org.xmlpull.v1.XmlPullParser;
110import org.xmlpull.v1.XmlPullParserException;
111
112import java.io.IOException;
113import java.util.ArrayList;
114import java.util.Collections;
115import java.util.Comparator;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800116import java.util.List;
117
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700118import static com.android.settings.dashboard.Header.HEADER_ID_UNDEFINED;
119
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800120public class SettingsActivity extends Activity
121 implements PreferenceManager.OnPreferenceTreeClickListener,
122 PreferenceFragment.OnPreferenceStartFragmentCallback,
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700123 ButtonBarHandler, OnAccountsUpdateListener, FragmentManager.OnBackStackChangedListener,
124 SearchView.OnQueryTextListener, SearchView.OnCloseListener,
125 MenuItem.OnActionExpandListener {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800126
127 private static final String LOG_TAG = "Settings";
128
129 // Constants for state save/restore
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700130 private static final String SAVE_KEY_CATEGORIES = ":settings:categories";
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700131 private static final String SAVE_KEY_SEARCH_MENU_EXPANDED = ":settings:search_menu_expanded";
132 private static final String SAVE_KEY_SEARCH_QUERY = ":settings:search_query";
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700133 private static final String SAVE_KEY_SHOW_HOME_AS_UP = ":settings:show_home_as_up";
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800134
135 /**
136 * When starting this activity, the invoking Intent can contain this extra
137 * string to specify which fragment should be initially displayed.
138 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
139 * will call isValidFragment() to confirm that the fragment class name is valid for this
140 * activity.
141 */
142 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
143
144 /**
145 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
146 * this extra can also be specified to supply a Bundle of arguments to pass
147 * to that fragment when it is instantiated during the initial creation
148 * of the activity.
149 */
150 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
151
152 /**
Fabrice Di Meglioc1457322014-04-04 19:07:50 -0700153 * Fragment "key" argument passed thru {@link #EXTRA_SHOW_FRAGMENT_ARGUMENTS}
154 */
155 public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
156
157 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800158 * When starting this activity, the invoking Intent can contain this extra
159 * boolean that the header list should not be displayed. This is most often
160 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
161 * the activity to display a specific fragment that the user has navigated
162 * to.
163 */
164 public static final String EXTRA_NO_HEADERS = ":settings:no_headers";
165
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800166 public static final String BACK_STACK_PREFS = ":settings:prefs";
167
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800168 // extras that allow any preference activity to be launched as part of a wizard
169
170 // show Back and Next buttons? takes boolean parameter
171 // Back will then return RESULT_CANCELED and Next RESULT_OK
172 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
173
174 // add a Skip button?
175 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
176
177 // specify custom text for the Back or Next buttons, or cause a button to not appear
178 // at all by setting it to null
179 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
180 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
181
182 /**
183 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
184 * this extra can also be specify to supply the title to be shown for
185 * that fragment.
186 */
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700187 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800188
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800189 private static final String META_DATA_KEY_FRAGMENT_CLASS =
190 "com.android.settings.FRAGMENT_CLASS";
191
192 private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
193
Fabrice Di Megliod6985df2014-04-03 16:43:26 -0700194 private static final String EMPTY_QUERY = "";
195
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800196 private static boolean sShowNoHomeNotice = false;
197
198 private String mFragmentClass;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800199
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800200 private CharSequence mInitialTitle;
201
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800202 // Show only these settings for restricted users
203 private int[] SETTINGS_FOR_RESTRICTED = {
204 R.id.wireless_section,
205 R.id.wifi_settings,
206 R.id.bluetooth_settings,
207 R.id.data_usage_settings,
208 R.id.wireless_settings,
209 R.id.device_section,
210 R.id.sound_settings,
211 R.id.display_settings,
212 R.id.storage_settings,
213 R.id.application_settings,
214 R.id.battery_settings,
215 R.id.personal_section,
216 R.id.location_settings,
217 R.id.security_settings,
218 R.id.language_settings,
219 R.id.user_settings,
220 R.id.account_settings,
221 R.id.account_add,
222 R.id.system_section,
223 R.id.date_time_settings,
224 R.id.about_settings,
225 R.id.accessibility_settings,
226 R.id.print_settings,
227 R.id.nfc_payment_settings,
Fabrice Di Meglio2858b792014-02-18 19:35:08 -0800228 R.id.home_settings,
229 R.id.dashboard
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800230 };
231
232 private static final String[] ENTRY_FRAGMENTS = {
233 WirelessSettings.class.getName(),
234 WifiSettings.class.getName(),
235 AdvancedWifiSettings.class.getName(),
236 BluetoothSettings.class.getName(),
237 TetherSettings.class.getName(),
238 WifiP2pSettings.class.getName(),
239 VpnSettings.class.getName(),
240 DateTimeSettings.class.getName(),
241 LocalePicker.class.getName(),
242 InputMethodAndLanguageSettings.class.getName(),
243 SpellCheckersSettings.class.getName(),
244 UserDictionaryList.class.getName(),
245 UserDictionarySettings.class.getName(),
246 SoundSettings.class.getName(),
247 DisplaySettings.class.getName(),
248 DeviceInfoSettings.class.getName(),
249 ManageApplications.class.getName(),
250 ProcessStatsUi.class.getName(),
251 NotificationStation.class.getName(),
252 LocationSettings.class.getName(),
253 SecuritySettings.class.getName(),
254 PrivacySettings.class.getName(),
255 DeviceAdminSettings.class.getName(),
256 AccessibilitySettings.class.getName(),
257 CaptionPropertiesFragment.class.getName(),
258 com.android.settings.accessibility.ToggleInversionPreferenceFragment.class.getName(),
259 com.android.settings.accessibility.ToggleContrastPreferenceFragment.class.getName(),
260 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
261 TextToSpeechSettings.class.getName(),
262 Memory.class.getName(),
263 DevelopmentSettings.class.getName(),
264 UsbSettings.class.getName(),
265 AndroidBeam.class.getName(),
266 WifiDisplaySettings.class.getName(),
267 PowerUsageSummary.class.getName(),
268 AccountSyncSettings.class.getName(),
269 CryptKeeperSettings.class.getName(),
270 DataUsageSummary.class.getName(),
271 DreamSettings.class.getName(),
272 UserSettings.class.getName(),
273 NotificationAccessSettings.class.getName(),
John Spurlockc9afadb2014-04-29 18:07:23 -0400274 ConditionProviderSettings.class.getName(),
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800275 ManageAccountsSettings.class.getName(),
276 PrintSettingsFragment.class.getName(),
277 PrintJobSettingsFragment.class.getName(),
278 TrustedCredentialsSettings.class.getName(),
279 PaymentSettings.class.getName(),
280 KeyboardLayoutPickerFragment.class.getName(),
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700281 ZenModeSettings.class.getName(),
282 NotificationSettings.class.getName(),
283 ChooseLockPassword.ChooseLockPasswordFragment.class.getName(),
284 ChooseLockPattern.ChooseLockPatternFragment.class.getName(),
285 InstalledAppDetails.class.getName()
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800286 };
287
288 private SharedPreferences mDevelopmentPreferences;
289 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
290
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800291 private AuthenticatorHelper mAuthenticatorHelper;
292 private boolean mListeningToAccountUpdates;
293
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800294 private boolean mBatteryPresent = true;
295 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
296
297 @Override
298 public void onReceive(Context context, Intent intent) {
299 String action = intent.getAction();
300 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
301 boolean batteryPresent = Utils.isBatteryPresent(intent);
302
303 if (mBatteryPresent != batteryPresent) {
304 mBatteryPresent = batteryPresent;
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700305 invalidateCategories();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800306 }
307 }
308 }
309 };
310
Svetoslav990159a2014-04-14 17:14:59 -0700311 private final DynamicIndexableContentMonitor mDynamicIndexableContentMonitor =
312 new DynamicIndexableContentMonitor();
Svetoslav853e4712014-04-14 10:10:25 -0700313
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700314 private Button mNextButton;
315 private ActionBar mActionBar;
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700316 private boolean mDisplayHomeAsUpEnabled;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700317
318 private SearchView mSearchView;
319 private MenuItem mSearchMenuItem;
320 private boolean mSearchMenuItemExpanded = false;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700321 private SearchResultsSummary mSearchResultsFragment;
322 private String mSearchQuery;
323
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700324 // Categories
325 private ArrayList<DashboardCategory> mCategories = new ArrayList<DashboardCategory>();
326 private boolean mNeedToRebuildCategories;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800327
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700328 private static final int MSG_BUILD_CATEGORIES = 1;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800329 private Handler mHandler = new Handler() {
330 @Override
331 public void handleMessage(Message msg) {
332 switch (msg.what) {
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700333 case MSG_BUILD_CATEGORIES: {
334 buildDashboardCategories(mCategories);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800335 } break;
336 }
337 }
338 };
339
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700340 private boolean mNeedToRevertToInitialFragment = false;
341
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700342 public AuthenticatorHelper getAuthenticatorHelper() {
343 return mAuthenticatorHelper;
344 }
345
346 public List<DashboardCategory> getDashboardCategories() {
347 if (mNeedToRebuildCategories) {
348 buildDashboardCategories(mCategories);
349 mNeedToRebuildCategories = false;
350 }
351 return mCategories;
352 }
353
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800354 @Override
355 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
356 // Override the fragment title for Wallpaper settings
357 int titleRes = pref.getTitleRes();
358 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
359 titleRes = R.string.wallpaper_settings_fragment_title;
360 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
361 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
362 if (UserManager.get(this).isLinkedUser()) {
363 titleRes = R.string.profile_info_settings_title;
364 } else {
365 titleRes = R.string.user_info_settings_title;
366 }
367 }
368 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
369 null, 0);
370 return true;
371 }
372
373 @Override
374 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
375 return false;
376 }
377
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700378 private void invalidateCategories() {
379 if (!mHandler.hasMessages(MSG_BUILD_CATEGORIES)) {
380 mHandler.sendEmptyMessage(MSG_BUILD_CATEGORIES);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800381 }
382 }
383
384 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800385 public void onConfigurationChanged(Configuration newConfig) {
386 super.onConfigurationChanged(newConfig);
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800387 Index.getInstance(this).update();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800388 }
389
390 @Override
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700391 protected void onStart() {
392 super.onStart();
393
394 if (mNeedToRevertToInitialFragment) {
395 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800396 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800397 }
398
399 @Override
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700400 public boolean onCreateOptionsMenu(Menu menu) {
401 MenuInflater inflater = getMenuInflater();
402 inflater.inflate(R.menu.options_menu, menu);
403
404 // Cache the search query (can be overriden by the OnQueryTextListener)
405 final String query = mSearchQuery;
406
Fabrice Di Meglio95937822014-03-31 19:46:42 -0700407 mSearchMenuItem = menu.findItem(R.id.search);
408 mSearchView = (SearchView) mSearchMenuItem.getActionView();
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700409
Fabrice Di Meglio23ae00c2014-04-21 12:43:20 -0700410 if (mSearchMenuItem == null || mSearchView == null) {
411 return false;
412 }
413
Fabrice Di Meglio8c3b0ce2014-05-12 18:54:32 -0700414 if (mSearchResultsFragment != null) {
415 mSearchResultsFragment.setSearchView(mSearchView);
416 }
417
Fabrice Di Meglio95937822014-03-31 19:46:42 -0700418 mSearchMenuItem.setOnActionExpandListener(this);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700419 mSearchView.setOnQueryTextListener(this);
420 mSearchView.setOnCloseListener(this);
421
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700422 if (mSearchMenuItemExpanded) {
423 mSearchMenuItem.expandActionView();
424 }
425 mSearchView.setQuery(query, true /* submit */);
426
427 return true;
428 }
429
430 @Override
431 protected void onCreate(Bundle savedState) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800432 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
433 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
434 }
Fabrice Di Meglio5ebabfc2014-04-21 09:40:46 -0700435
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800436 mAuthenticatorHelper = new AuthenticatorHelper();
437 mAuthenticatorHelper.updateAuthDescriptions(this);
438 mAuthenticatorHelper.onAccountsUpdated(this, null);
439
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800440 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
441 Context.MODE_PRIVATE);
442
443 getMetaData();
444
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700445 super.onCreate(savedState);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800446
447 setContentView(R.layout.settings_main);
448
449 getFragmentManager().addOnBackStackChangedListener(this);
450
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700451 mDisplayHomeAsUpEnabled = true;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800452
Fabrice Di Meglio5cda21b2014-04-21 10:14:28 -0700453 // Getting Intent properties can only be done after the super.onCreate(...)
454 final String initialFragmentName = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
455
456 if (initialFragmentName == null) {
457 Index.getInstance(this).update();
458 }
459
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700460 if (savedState != null) {
Fabrice Di Meglio1800a9f2014-04-03 19:31:07 -0700461 // We are restarting from a previous saved state; used that to initialize, instead
462 // of starting fresh.
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700463 mSearchMenuItemExpanded = savedState.getBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED);
464 mSearchQuery = savedState.getString(SAVE_KEY_SEARCH_QUERY);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800465
Fabrice Di Meglio1800a9f2014-04-03 19:31:07 -0700466 final String initialTitle = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
467 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
468 setTitle(mInitialTitle);
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800469
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700470 ArrayList<DashboardCategory> categories =
471 savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);
472 if (categories != null) {
473 mCategories.addAll(categories);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700474 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800475 }
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700476
477 mDisplayHomeAsUpEnabled = savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800478 } else {
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700479 // We need to build the Categories in all cases
480 buildDashboardCategories(mCategories);
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800481
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700482 if (initialFragmentName != null) {
483 final ComponentName cn = getIntent().getComponent();
484 // No UP is we are launched thru a Settings shortcut
485 if (!cn.getClassName().equals(SubSettings.class.getName())) {
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700486 mDisplayHomeAsUpEnabled = false;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700487 }
488 final String initialTitle = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
489 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800490 setTitle(mInitialTitle);
Fabrice Di Meglioc1457322014-04-04 19:07:50 -0700491
Fabrice Di Meglio5ebabfc2014-04-21 09:40:46 -0700492 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700493 switchToFragment( initialFragmentName, initialArguments, true, false,
494 mInitialTitle, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000495 } else {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700496 // No UP if we are displaying the Headers
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700497 mDisplayHomeAsUpEnabled = false;
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700498 if (mCategories.size() > 0) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700499 mInitialTitle = getText(R.string.dashboard_title);
500 switchToFragment(DashboardSummary.class.getName(), null, false, false,
501 mInitialTitle, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000502 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800503 }
504 }
505
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700506 mActionBar = getActionBar();
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700507 mActionBar.setDisplayHomeAsUpEnabled(mDisplayHomeAsUpEnabled);
Fabrice Di Meglio7cae7f42014-05-09 16:48:36 -0700508 mActionBar.setHomeButtonEnabled(mDisplayHomeAsUpEnabled);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700509
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800510 // see if we should show Back/Next buttons
511 Intent intent = getIntent();
512 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
513
514 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
515 if (buttonBar != null) {
516 buttonBar.setVisibility(View.VISIBLE);
517
518 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
519 backButton.setOnClickListener(new OnClickListener() {
520 public void onClick(View v) {
521 setResult(RESULT_CANCELED);
522 finish();
523 }
524 });
525 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
526 skipButton.setOnClickListener(new OnClickListener() {
527 public void onClick(View v) {
528 setResult(RESULT_OK);
529 finish();
530 }
531 });
532 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
533 mNextButton.setOnClickListener(new OnClickListener() {
534 public void onClick(View v) {
535 setResult(RESULT_OK);
536 finish();
537 }
538 });
539
540 // set our various button parameters
541 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
542 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
543 if (TextUtils.isEmpty(buttonText)) {
544 mNextButton.setVisibility(View.GONE);
545 }
546 else {
547 mNextButton.setText(buttonText);
548 }
549 }
550 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
551 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
552 if (TextUtils.isEmpty(buttonText)) {
553 backButton.setVisibility(View.GONE);
554 }
555 else {
556 backButton.setText(buttonText);
557 }
558 }
559 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
560 skipButton.setVisibility(View.VISIBLE);
561 }
562 }
563 }
Fabrice Di Meglioc95be4f2014-03-07 12:57:38 -0800564 }
565
566 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800567 public void onBackStackChanged() {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700568 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800569 }
570
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700571 private int setTitleFromBackStack() {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800572 final int count = getFragmentManager().getBackStackEntryCount();
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700573
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800574 if (count == 0) {
575 setTitle(mInitialTitle);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700576 return 0;
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800577 }
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700578
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800579 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1);
580 setTitleFromBackStackEntry(bse);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700581
582 return count;
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800583 }
584
585 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
586 final CharSequence title;
587 final int titleRes = bse.getBreadCrumbTitleRes();
588 if (titleRes > 0) {
589 title = getText(titleRes);
590 } else {
591 title = bse.getBreadCrumbTitle();
592 }
593 if (title != null) {
594 setTitle(title);
595 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800596 }
597
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800598 @Override
599 protected void onSaveInstanceState(Bundle outState) {
600 super.onSaveInstanceState(outState);
601
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700602 if (mCategories.size() > 0) {
603 outState.putParcelableArrayList(SAVE_KEY_CATEGORIES, mCategories);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800604 }
Fabrice Di Megliod6985df2014-04-03 16:43:26 -0700605
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700606 outState.putBoolean(SAVE_KEY_SHOW_HOME_AS_UP, mDisplayHomeAsUpEnabled);
607
Fabrice Di Megliod6985df2014-04-03 16:43:26 -0700608 // The option menus are created if the ActionBar is visible and they are also created
609 // asynchronously. If you launch Settings with an Intent action like
610 // android.intent.action.POWER_USAGE_SUMMARY and at the same time your device is locked
611 // thru a LockScreen, onCreateOptionsMenu() is not yet called and references to the search
612 // menu item and search view are null.
613 boolean isExpanded = (mSearchMenuItem != null) && mSearchMenuItem.isActionViewExpanded();
614 outState.putBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED, isExpanded);
615
616 String query = (mSearchView != null) ? mSearchView.getQuery().toString() : EMPTY_QUERY;
617 outState.putString(SAVE_KEY_SEARCH_QUERY, query);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800618 }
619
620 @Override
621 public void onResume() {
622 super.onResume();
623
624 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
625 @Override
626 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700627 invalidateCategories();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800628 }
629 };
630 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
631 mDevelopmentPreferencesListener);
632
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700633 invalidateCategories();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800634
635 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Svetoslav853e4712014-04-14 10:10:25 -0700636
Svetoslav990159a2014-04-14 17:14:59 -0700637 mDynamicIndexableContentMonitor.register(this);
Fabrice Di Meglioa3270762014-04-16 16:54:56 -0700638
639 if(!TextUtils.isEmpty(mSearchQuery)) {
640 onQueryTextSubmit(mSearchQuery);
641 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800642 }
643
644 @Override
645 public void onPause() {
646 super.onPause();
647
648 unregisterReceiver(mBatteryInfoReceiver);
649
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800650 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
651 mDevelopmentPreferencesListener);
652
653 mDevelopmentPreferencesListener = null;
Svetoslav853e4712014-04-14 10:10:25 -0700654
Svetoslav990159a2014-04-14 17:14:59 -0700655 mDynamicIndexableContentMonitor.unregister();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800656 }
657
658 @Override
659 public void onDestroy() {
660 super.onDestroy();
661 if (mListeningToAccountUpdates) {
662 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
663 }
664 }
665
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800666 protected boolean isValidFragment(String fragmentName) {
667 // Almost all fragments are wrapped in this,
668 // except for a few that have their own activities.
669 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
670 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
671 }
672 return false;
673 }
674
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800675 /**
676 * When in two-pane mode, switch to the fragment pane to show the given
677 * preference fragment.
678 *
679 * @param header The new header to display.
Fabrice Di Megliobbdada82014-04-04 10:16:59 -0700680 * @param position The position of the Header in the list.
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800681 */
Fabrice Di Megliobbdada82014-04-04 10:16:59 -0700682 private void onHeaderClick(Header header, int position) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800683 if (header == null) {
684 return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800685 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700686 if (header.fragment != null) {
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700687 Utils.startWithFragment(this, header.fragment, header.fragmentArguments, null, 0,
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700688 header.getTitle(getResources()));
689 } else if (header.intent != null) {
690 startActivity(header.intent);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800691 }
692 }
693
694 /**
695 * Called to determine whether the header list should be hidden.
696 * The default implementation returns the
697 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
698 * This is set to false, for example, when the activity is being re-launched
699 * to show a particular preference activity.
700 */
701 public boolean onIsHidingHeaders() {
702 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
703 }
704
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800705 @Override
706 public Intent getIntent() {
707 Intent superIntent = super.getIntent();
708 String startingFragment = getStartingFragmentClass(superIntent);
709 // This is called from super.onCreate, isMultiPane() is not yet reliable
710 // Do not use onIsHidingHeaders either, which relies itself on this method
711 if (startingFragment != null) {
712 Intent modIntent = new Intent(superIntent);
713 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
714 Bundle args = superIntent.getExtras();
715 if (args != null) {
716 args = new Bundle(args);
717 } else {
718 args = new Bundle();
719 }
720 args.putParcelable("intent", superIntent);
721 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
722 return modIntent;
723 }
724 return superIntent;
725 }
726
727 /**
728 * Checks if the component name in the intent is different from the Settings class and
729 * returns the class name to load as a fragment.
730 */
731 private String getStartingFragmentClass(Intent intent) {
732 if (mFragmentClass != null) return mFragmentClass;
733
734 String intentClass = intent.getComponent().getClassName();
735 if (intentClass.equals(getClass().getName())) return null;
736
737 if ("com.android.settings.ManageApplications".equals(intentClass)
738 || "com.android.settings.RunningServices".equals(intentClass)
739 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
740 // Old names of manage apps.
741 intentClass = com.android.settings.applications.ManageApplications.class.getName();
742 }
743
744 return intentClass;
745 }
746
747 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000748 * Start a new fragment containing a preference panel. If the preferences
749 * are being displayed in multi-pane mode, the given fragment class will
750 * be instantiated and placed in the appropriate pane. If running in
751 * single-pane mode, a new activity will be launched in which to show the
752 * fragment.
753 *
754 * @param fragmentClass Full name of the class implementing the fragment.
755 * @param args Any desired arguments to supply to the fragment.
756 * @param titleRes Optional resource identifier of the title of this
757 * fragment.
758 * @param titleText Optional text of the title of this fragment.
759 * @param resultTo Optional fragment that result data should be sent to.
760 * If non-null, resultTo.onActivityResult() will be called when this
761 * preference panel is done. The launched panel must use
762 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
763 * @param resultRequestCode If resultTo is non-null, this is the caller's
764 * request code to be received with the resut.
765 */
766 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700767 CharSequence titleText, Fragment resultTo, int resultRequestCode) {
Fabrice Di Meglio911fb2a2014-04-04 17:55:57 -0700768 String title;
769 if (titleRes > 0) {
770 title = getString(titleRes);
Fabrice Di Meglio821a0722014-04-08 13:56:06 -0700771 } else if (titleText != null) {
Fabrice Di Meglio911fb2a2014-04-04 17:55:57 -0700772 title = titleText.toString();
Fabrice Di Meglio821a0722014-04-08 13:56:06 -0700773 } else {
774 // There not much we can do in that case
775 title = "";
Fabrice Di Meglio911fb2a2014-04-04 17:55:57 -0700776 }
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700777 Utils.startWithFragment(this, fragmentClass, args, resultTo, resultRequestCode, title);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000778 }
779
780 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800781 * Called by a preference panel fragment to finish itself.
782 *
783 * @param caller The fragment that is asking to be finished.
784 * @param resultCode Optional result code to send back to the original
785 * launching fragment.
786 * @param resultData Optional result data to send back to the original
787 * launching fragment.
788 */
789 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
790 setResult(resultCode, resultData);
791 }
792
793 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000794 * Start a new fragment.
795 *
796 * @param fragment The fragment to start
797 * @param push If true, the current fragment will be pushed onto the back stack. If false,
798 * the current fragment will be replaced.
799 */
800 public void startPreferenceFragment(Fragment fragment, boolean push) {
801 FragmentTransaction transaction = getFragmentManager().beginTransaction();
802 transaction.replace(R.id.prefs, fragment);
803 if (push) {
804 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
805 transaction.addToBackStack(BACK_STACK_PREFS);
806 } else {
807 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
808 }
809 transaction.commitAllowingStateLoss();
810 }
811
812 /**
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700813 * Switch to a specific Fragment with taking care of validation, Title and BackStack
814 */
815 private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
816 boolean addToBackStack, CharSequence title, boolean withTransition) {
817 if (validate && !isValidFragment(fragmentName)) {
818 throw new IllegalArgumentException("Invalid fragment for this activity: "
819 + fragmentName);
820 }
821 Fragment f = Fragment.instantiate(this, fragmentName, args);
822 FragmentTransaction transaction = getFragmentManager().beginTransaction();
823 transaction.replace(R.id.prefs, f);
824 if (withTransition) {
825 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
826 }
827 if (addToBackStack) {
828 transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
829 }
830 if (title != null) {
831 transaction.setBreadCrumbTitle(title);
832 }
833 transaction.commitAllowingStateLoss();
834 return f;
835 }
836
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700837 public void setNeedToRebuildCategories(boolean need) {
838 mNeedToRebuildCategories = need;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700839 }
840
841 /**
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700842 * Called when the activity needs its list of categories/tiles built.
Fabrice Di Meglio63bbb8e2014-04-23 16:44:30 -0700843 *
844 * @param categories The list in which to place the tiles categories.
845 */
Fabrice Di Meglio769630c2014-04-24 14:48:48 -0700846 private void buildDashboardCategories(List<DashboardCategory> categories) {
847 mCategories.clear();
Fabrice Di Meglio63bbb8e2014-04-23 16:44:30 -0700848 loadCategoriesFromResource(R.xml.dashboard_categories, categories);
849 updateTilesList(categories);
850 }
851
852 /**
853 * Parse the given XML file as a categories description, adding each
854 * parsed categories and tiles into the target list.
855 *
856 * @param resid The XML resource to load and parse.
857 * @param target The list in which the parsed categories and tiles should be placed.
858 */
859 private void loadCategoriesFromResource(int resid, List<DashboardCategory> target) {
860 XmlResourceParser parser = null;
861 try {
862 parser = getResources().getXml(resid);
863 AttributeSet attrs = Xml.asAttributeSet(parser);
864
865 int type;
866 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
867 && type != XmlPullParser.START_TAG) {
868 // Parse next until start tag is found
869 }
870
871 String nodeName = parser.getName();
872 if (!"dashboard-categories".equals(nodeName)) {
873 throw new RuntimeException(
874 "XML document must start with <preference-categories> tag; found"
875 + nodeName + " at " + parser.getPositionDescription());
876 }
877
878 Bundle curBundle = null;
879
880 final int outerDepth = parser.getDepth();
881 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
882 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
883 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
884 continue;
885 }
886
887 nodeName = parser.getName();
888 if ("dashboard-category".equals(nodeName)) {
889 DashboardCategory category = new DashboardCategory();
890
891 TypedArray sa = obtainStyledAttributes(
892 attrs, com.android.internal.R.styleable.PreferenceHeader);
893 category.id = sa.getResourceId(
894 com.android.internal.R.styleable.PreferenceHeader_id,
895 (int)DashboardCategory.CAT_ID_UNDEFINED);
896
897 TypedValue tv = sa.peekValue(
898 com.android.internal.R.styleable.PreferenceHeader_title);
899 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
900 if (tv.resourceId != 0) {
901 category.titleRes = tv.resourceId;
902 } else {
903 category.title = tv.string;
904 }
905 }
906 sa.recycle();
907
908 final int innerDepth = parser.getDepth();
909 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
910 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
911 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
912 continue;
913 }
914
915 String innerNodeName = parser.getName();
916 if (innerNodeName.equals("dashboard-tile")) {
917 DashboardTile tile = new DashboardTile();
918
919 sa = obtainStyledAttributes(
920 attrs, com.android.internal.R.styleable.PreferenceHeader);
921 tile.id = sa.getResourceId(
922 com.android.internal.R.styleable.PreferenceHeader_id,
923 (int)HEADER_ID_UNDEFINED);
924 tv = sa.peekValue(
925 com.android.internal.R.styleable.PreferenceHeader_title);
926 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
927 if (tv.resourceId != 0) {
928 tile.titleRes = tv.resourceId;
929 } else {
930 tile.title = tv.string;
931 }
932 }
933 tv = sa.peekValue(
934 com.android.internal.R.styleable.PreferenceHeader_summary);
935 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
936 if (tv.resourceId != 0) {
937 tile.summaryRes = tv.resourceId;
938 } else {
939 tile.summary = tv.string;
940 }
941 }
942 tile.iconRes = sa.getResourceId(
943 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
944 tile.fragment = sa.getString(
945 com.android.internal.R.styleable.PreferenceHeader_fragment);
946 sa.recycle();
947
948 if (curBundle == null) {
949 curBundle = new Bundle();
950 }
951
952 final int innerDepth2 = parser.getDepth();
953 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
954 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth2)) {
955 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
956 continue;
957 }
958
959 String innerNodeName2 = parser.getName();
960 if (innerNodeName2.equals("extra")) {
961 getResources().parseBundleExtra("extra", attrs, curBundle);
962 XmlUtils.skipCurrentTag(parser);
963
964 } else if (innerNodeName2.equals("intent")) {
965 tile.intent = Intent.parseIntent(getResources(), parser, attrs);
966
967 } else {
968 XmlUtils.skipCurrentTag(parser);
969 }
970 }
971
972 if (curBundle.size() > 0) {
973 tile.fragmentArguments = curBundle;
974 curBundle = null;
975 }
976
977 category.addTile(tile);
978
979 } else {
980 XmlUtils.skipCurrentTag(parser);
981 }
982 }
983
984 target.add(category);
985 } else {
986 XmlUtils.skipCurrentTag(parser);
987 }
988 }
989
990 } catch (XmlPullParserException e) {
991 throw new RuntimeException("Error parsing categories", e);
992 } catch (IOException e) {
993 throw new RuntimeException("Error parsing categories", e);
994 } finally {
995 if (parser != null) parser.close();
996 }
997 }
998
999 private void updateTilesList(List<DashboardCategory> target) {
1000 final boolean showDev = mDevelopmentPreferences.getBoolean(
1001 DevelopmentSettings.PREF_SHOW,
1002 android.os.Build.TYPE.equals("eng"));
1003
1004 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1005
1006 final int size = target.size();
1007 for (int i = 0; i < size; i++) {
1008
1009 DashboardCategory category = target.get(i);
1010
1011 // Ids are integers, so downcasting is ok
1012 int id = (int) category.id;
1013 if (id == R.id.account_settings) {
1014 insertAccountsTiles(category);
1015 continue;
1016 }
1017 int n = category.getTilesCount() - 1;
1018 while (n >= 0) {
1019
1020 DashboardTile tile = category.getTile(n);
1021
1022 id = (int) tile.id;
1023 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1024 Utils.updateTileToSpecificActivityFromMetaDataOrRemove(this, category, tile);
1025 } else if (id == R.id.wifi_settings) {
1026 // Remove WiFi Settings if WiFi service is not available.
1027 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1028 category.removeTile(n);
1029 }
1030 } else if (id == R.id.bluetooth_settings) {
1031 // Remove Bluetooth Settings if Bluetooth service is not available.
1032 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1033 category.removeTile(n);
1034 }
1035 } else if (id == R.id.data_usage_settings) {
1036 // Remove data usage when kernel module not enabled
1037 final INetworkManagementService netManager = INetworkManagementService.Stub
1038 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1039 try {
1040 if (!netManager.isBandwidthControlEnabled()) {
1041 category.removeTile(n);
1042 }
1043 } catch (RemoteException e) {
1044 // ignored
1045 }
1046 } else if (id == R.id.battery_settings) {
1047 // Remove battery settings when battery is not available. (e.g. TV)
1048
1049 if (!mBatteryPresent) {
1050 category.removeTile(n);
1051 }
1052 } else if (id == R.id.home_settings) {
1053 if (!updateHomeSettingTiles(tile)) {
1054 category.removeTile(n);
1055 }
1056 } else if (id == R.id.user_settings) {
1057 if (!UserHandle.MU_ENABLED
1058 || !UserManager.supportsMultipleUsers()
1059 || Utils.isMonkeyRunning()) {
1060 category.removeTile(n);
1061 }
1062 } else if (id == R.id.nfc_payment_settings) {
1063 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1064 category.removeTile(n);
1065 } else {
1066 // Only show if NFC is on and we have the HCE feature
1067 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1068 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1069 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1070 category.removeTile(n);
1071 }
1072 }
1073 } else if (id == R.id.development_settings) {
Julia Reynolds6c088cb2014-05-08 09:29:41 -04001074 if (!showDev || um.hasUserRestriction(
1075 UserManager.DISALLOW_DEBUGGING_FEATURES)) {
Fabrice Di Meglio63bbb8e2014-04-23 16:44:30 -07001076 category.removeTile(n);
1077 }
1078 } else if (id == R.id.account_add) {
1079 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1080 category.removeTile(n);
1081 }
1082 }
1083
1084 if (UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
Amith Yamasania97089d2014-04-30 10:58:09 -07001085 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)
1086 && n < category.getTilesCount()) {
Fabrice Di Meglio63bbb8e2014-04-23 16:44:30 -07001087 category.removeTile(n);
1088 }
1089
1090 n--;
1091 }
1092 }
1093 }
1094
1095 private boolean updateHomeSettingTiles(DashboardTile tile) {
1096 // Once we decide to show Home settings, keep showing it forever
1097 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1098 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1099 return true;
1100 }
1101
1102 try {
1103 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1104 getPackageManager().getHomeActivities(homeApps);
1105 if (homeApps.size() < 2) {
1106 // When there's only one available home app, omit this settings
1107 // category entirely at the top level UI. If the user just
1108 // uninstalled the penultimate home app candidiate, we also
1109 // now tell them about why they aren't seeing 'Home' in the list.
1110 if (sShowNoHomeNotice) {
1111 sShowNoHomeNotice = false;
1112 NoHomeDialogFragment.show(this);
1113 }
1114 return false;
1115 } else {
1116 // Okay, we're allowing the Home settings category. Tell it, when
1117 // invoked via this front door, that we'll need to be told about the
1118 // case when the user uninstalls all but one home app.
1119 if (tile.fragmentArguments == null) {
1120 tile.fragmentArguments = new Bundle();
1121 }
1122 tile.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1123 }
1124 } catch (Exception e) {
1125 // Can't look up the home activity; bail on configuring the icon
1126 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1127 }
1128
1129 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1130 return true;
1131 }
1132
1133 private void insertAccountsTiles(DashboardCategory target) {
1134 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1135 List<DashboardTile> dashboardTiles = new ArrayList<DashboardTile>(accountTypes.length);
1136 for (String accountType : accountTypes) {
1137 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1138 if (label == null) {
1139 continue;
1140 }
1141
1142 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1143 boolean skipToAccount = accounts.length == 1
1144 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1145 DashboardTile accountTile = new DashboardTile();
1146 accountTile.title = label;
1147 if (accountTile.extras == null) {
1148 accountTile.extras = new Bundle();
1149 }
1150 if (skipToAccount) {
1151 accountTile.fragment = AccountSyncSettings.class.getName();
1152 accountTile.fragmentArguments = new Bundle();
1153 // Need this for the icon
1154 accountTile.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1155 accountTile.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1156 accountTile.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1157 accounts[0]);
1158 } else {
1159 accountTile.fragment = ManageAccountsSettings.class.getName();
1160 accountTile.fragmentArguments = new Bundle();
1161 accountTile.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1162 accountTile.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1163 accountType);
1164 accountTile.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1165 label.toString());
1166 }
1167 dashboardTiles.add(accountTile);
1168 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1169 }
1170
1171 // Sort by label
1172 Collections.sort(dashboardTiles, new Comparator<DashboardTile>() {
1173 @Override
1174 public int compare(DashboardTile t1, DashboardTile t2) {
1175 return t1.title.toString().compareTo(t2.title.toString());
1176 }
1177 });
1178 int index = 0;
1179 for (DashboardTile tile : dashboardTiles) {
1180 target.addTile(index, tile);
1181 index++;
1182 }
1183 if (!mListeningToAccountUpdates) {
1184 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1185 mListeningToAccountUpdates = true;
1186 }
1187 }
1188
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001189 private void getMetaData() {
1190 try {
1191 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1192 PackageManager.GET_META_DATA);
1193 if (ai == null || ai.metaData == null) return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001194 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1195 } catch (NameNotFoundException nnfe) {
1196 // No recovery
1197 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1198 }
1199 }
1200
1201 // give subclasses access to the Next button
1202 public boolean hasNextButton() {
1203 return mNextButton != null;
1204 }
1205
1206 public Button getNextButton() {
1207 return mNextButton;
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);
Fabrice Di Meglio769630c2014-04-24 14:48:48 -07001220 invalidateCategories();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001221 }
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;
Fabrice Di Meglio3d5aeb72014-05-09 14:59:19 -07001237 if (TextUtils.isEmpty(newText) || mSearchResultsFragment == null) {
Fabrice Di Meglioa3270762014-04-16 16:54:56 -07001238 return false;
1239 }
1240 return mSearchResultsFragment.onQueryTextChange(newText);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001241 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001242
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001243 @Override
1244 public boolean onClose() {
1245 return false;
1246 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001247
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001248 @Override
1249 public boolean onMenuItemActionExpand(MenuItem item) {
1250 if (item.getItemId() == mSearchMenuItem.getItemId()) {
Fabrice Di Megliobb16fd82014-04-04 14:48:05 -07001251 switchToSearchResultsFragmentIfNeeded();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001252 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001253 return true;
1254 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001255
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001256 @Override
1257 public boolean onMenuItemActionCollapse(MenuItem item) {
1258 if (item.getItemId() == mSearchMenuItem.getItemId()) {
Fabrice Di Megliobb16fd82014-04-04 14:48:05 -07001259 if (mSearchMenuItemExpanded) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001260 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001261 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001262 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001263 return true;
1264 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001265
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001266 private void switchToSearchResultsFragmentIfNeeded() {
Fabrice Di Megliobb16fd82014-04-04 14:48:05 -07001267 if (mSearchResultsFragment != null) {
1268 return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001269 }
Fabrice Di Megliobb16fd82014-04-04 14:48:05 -07001270 Fragment current = getFragmentManager().findFragmentById(R.id.prefs);
1271 if (current != null && current instanceof SearchResultsSummary) {
1272 mSearchResultsFragment = (SearchResultsSummary) current;
1273 } else {
1274 String title = getString(R.string.search_results_title);
1275 mSearchResultsFragment = (SearchResultsSummary) switchToFragment(
1276 SearchResultsSummary.class.getName(), null, false, true, title, true);
1277 }
Fabrice Di Megliod297a582014-04-22 17:23:23 -07001278 mSearchResultsFragment.setSearchView(mSearchView);
Fabrice Di Megliobb16fd82014-04-04 14:48:05 -07001279 mSearchMenuItemExpanded = true;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001280 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001281
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001282 public void needToRevertToInitialFragment() {
1283 mNeedToRevertToInitialFragment = true;
1284 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001285
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001286 private void revertToInitialFragment() {
1287 mNeedToRevertToInitialFragment = false;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001288 mSearchResultsFragment = null;
Fabrice Di Megliobb16fd82014-04-04 14:48:05 -07001289 mSearchMenuItemExpanded = false;
1290 getFragmentManager().popBackStackImmediate(SettingsActivity.BACK_STACK_PREFS,
1291 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fabrice Di Meglio23ae00c2014-04-21 12:43:20 -07001292 if (mSearchMenuItem != null) {
1293 mSearchMenuItem.collapseActionView();
1294 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001295 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001296}