blob: 789ad2a14606de0aadd12ed4420e209d7bfb2145 [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
Svetoslav853e4712014-04-14 10:10:25 -070019import android.accessibilityservice.AccessibilityService;
20import android.accessibilityservice.AccessibilityServiceInfo;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080021import android.accounts.Account;
22import android.accounts.AccountManager;
23import android.accounts.OnAccountsUpdateListener;
24import android.app.ActionBar;
25import android.app.Activity;
26import android.app.Fragment;
27import android.app.FragmentManager;
28import android.app.FragmentTransaction;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080029import android.app.admin.DevicePolicyManager;
30import android.content.BroadcastReceiver;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070031import android.content.ComponentName;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080032import android.content.Context;
33import android.content.Intent;
34import android.content.IntentFilter;
35import android.content.SharedPreferences;
36import android.content.pm.ActivityInfo;
37import android.content.pm.PackageManager;
38import android.content.pm.PackageManager.NameNotFoundException;
39import android.content.pm.ResolveInfo;
40import android.content.res.Configuration;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080041import android.content.res.TypedArray;
42import android.content.res.XmlResourceParser;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080043import android.nfc.NfcAdapter;
44import android.os.Bundle;
45import android.os.Handler;
46import android.os.INetworkManagementService;
Svetoslav853e4712014-04-14 10:10:25 -070047import android.os.Looper;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080048import android.os.Message;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080049import android.os.RemoteException;
50import android.os.ServiceManager;
51import android.os.UserHandle;
52import android.os.UserManager;
53import android.preference.Preference;
54import android.preference.PreferenceFragment;
55import android.preference.PreferenceManager;
56import android.preference.PreferenceScreen;
Svetoslav853e4712014-04-14 10:10:25 -070057import android.print.PrintManager;
58import android.printservice.PrintService;
59import android.printservice.PrintServiceInfo;
60import android.provider.SearchIndexableData;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080061import android.text.TextUtils;
62import android.util.AttributeSet;
63import android.util.Log;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080064import android.util.TypedValue;
65import android.util.Xml;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070066import android.view.Menu;
67import android.view.MenuInflater;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080068import android.view.MenuItem;
69import android.view.View;
70import android.view.View.OnClickListener;
Svetoslav853e4712014-04-14 10:10:25 -070071import android.view.accessibility.AccessibilityManager;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080072import android.widget.Button;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080073import android.widget.ListView;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080074
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070075import android.widget.SearchView;
Svetoslav853e4712014-04-14 10:10:25 -070076import com.android.internal.content.PackageMonitor;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080077import com.android.internal.util.ArrayUtils;
78import com.android.internal.util.XmlUtils;
79import com.android.settings.accessibility.AccessibilitySettings;
80import com.android.settings.accessibility.CaptionPropertiesFragment;
81import com.android.settings.accounts.AccountSyncSettings;
82import com.android.settings.accounts.AuthenticatorHelper;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080083import com.android.settings.accounts.ManageAccountsSettings;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070084import com.android.settings.applications.InstalledAppDetails;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080085import com.android.settings.applications.ManageApplications;
86import com.android.settings.applications.ProcessStatsUi;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080087import com.android.settings.bluetooth.BluetoothSettings;
88import com.android.settings.dashboard.DashboardSummary;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -070089import com.android.settings.dashboard.Header;
90import com.android.settings.dashboard.HeaderAdapter;
91import com.android.settings.dashboard.NoHomeDialogFragment;
92import com.android.settings.dashboard.SearchResultsSummary;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080093import com.android.settings.deviceinfo.Memory;
94import com.android.settings.deviceinfo.UsbSettings;
95import com.android.settings.fuelgauge.PowerUsageSummary;
Fabrice Di Megliofa7dc242014-03-12 19:24:43 -070096import com.android.settings.search.Index;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080097import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
98import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
99import com.android.settings.inputmethod.SpellCheckersSettings;
100import com.android.settings.inputmethod.UserDictionaryList;
101import com.android.settings.location.LocationSettings;
102import com.android.settings.nfc.AndroidBeam;
103import com.android.settings.nfc.PaymentSettings;
104import com.android.settings.print.PrintJobSettingsFragment;
105import com.android.settings.print.PrintSettingsFragment;
Svetoslav853e4712014-04-14 10:10:25 -0700106import com.android.settings.search.SearchIndexableRaw;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800107import com.android.settings.tts.TextToSpeechSettings;
108import com.android.settings.users.UserSettings;
109import com.android.settings.vpn2.VpnSettings;
110import com.android.settings.wfd.WifiDisplaySettings;
111import com.android.settings.wifi.AdvancedWifiSettings;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800112import com.android.settings.wifi.WifiSettings;
113import com.android.settings.wifi.p2p.WifiP2pSettings;
114import org.xmlpull.v1.XmlPullParser;
115import org.xmlpull.v1.XmlPullParserException;
116
117import java.io.IOException;
118import java.util.ArrayList;
119import java.util.Collections;
120import java.util.Comparator;
121import java.util.HashMap;
Svetoslav853e4712014-04-14 10:10:25 -0700122import java.util.Iterator;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800123import java.util.List;
124
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700125import static com.android.settings.dashboard.Header.HEADER_ID_UNDEFINED;
126
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800127public class SettingsActivity extends Activity
128 implements PreferenceManager.OnPreferenceTreeClickListener,
129 PreferenceFragment.OnPreferenceStartFragmentCallback,
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700130 ButtonBarHandler, OnAccountsUpdateListener, FragmentManager.OnBackStackChangedListener,
131 SearchView.OnQueryTextListener, SearchView.OnCloseListener,
132 MenuItem.OnActionExpandListener {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800133
134 private static final String LOG_TAG = "Settings";
135
136 // Constants for state save/restore
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700137 private static final String SAVE_KEY_HEADERS = ":settings:headers";
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700138 private static final String SAVE_KEY_SEARCH_MENU_EXPANDED = ":settings:search_menu_expanded";
139 private static final String SAVE_KEY_SEARCH_QUERY = ":settings:search_query";
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700140 private static final String SAVE_KEY_SHOW_HOME_AS_UP = ":settings:show_home_as_up";
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800141
142 /**
143 * When starting this activity, the invoking Intent can contain this extra
144 * string to specify which fragment should be initially displayed.
145 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
146 * will call isValidFragment() to confirm that the fragment class name is valid for this
147 * activity.
148 */
149 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
150
151 /**
152 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
153 * this extra can also be specified to supply a Bundle of arguments to pass
154 * to that fragment when it is instantiated during the initial creation
155 * of the activity.
156 */
157 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
158
159 /**
Fabrice Di Meglioc1457322014-04-04 19:07:50 -0700160 * Fragment "key" argument passed thru {@link #EXTRA_SHOW_FRAGMENT_ARGUMENTS}
161 */
162 public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
163
164 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800165 * When starting this activity, the invoking Intent can contain this extra
166 * boolean that the header list should not be displayed. This is most often
167 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
168 * the activity to display a specific fragment that the user has navigated
169 * to.
170 */
171 public static final String EXTRA_NO_HEADERS = ":settings:no_headers";
172
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800173 public static final String BACK_STACK_PREFS = ":settings:prefs";
174
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800175 // extras that allow any preference activity to be launched as part of a wizard
176
177 // show Back and Next buttons? takes boolean parameter
178 // Back will then return RESULT_CANCELED and Next RESULT_OK
179 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
180
181 // add a Skip button?
182 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
183
184 // specify custom text for the Back or Next buttons, or cause a button to not appear
185 // at all by setting it to null
186 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
187 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
188
189 /**
190 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
191 * this extra can also be specify to supply the title to be shown for
192 * that fragment.
193 */
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700194 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800195
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800196 private static final String META_DATA_KEY_FRAGMENT_CLASS =
197 "com.android.settings.FRAGMENT_CLASS";
198
199 private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
200
Fabrice Di Megliod6985df2014-04-03 16:43:26 -0700201 private static final String EMPTY_QUERY = "";
202
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800203 private static boolean sShowNoHomeNotice = false;
204
205 private String mFragmentClass;
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800206 private Header mSelectedHeader;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800207
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800208 private CharSequence mInitialTitle;
209
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800210 // Show only these settings for restricted users
211 private int[] SETTINGS_FOR_RESTRICTED = {
212 R.id.wireless_section,
213 R.id.wifi_settings,
214 R.id.bluetooth_settings,
215 R.id.data_usage_settings,
216 R.id.wireless_settings,
217 R.id.device_section,
218 R.id.sound_settings,
219 R.id.display_settings,
220 R.id.storage_settings,
221 R.id.application_settings,
222 R.id.battery_settings,
223 R.id.personal_section,
224 R.id.location_settings,
225 R.id.security_settings,
226 R.id.language_settings,
227 R.id.user_settings,
228 R.id.account_settings,
229 R.id.account_add,
230 R.id.system_section,
231 R.id.date_time_settings,
232 R.id.about_settings,
233 R.id.accessibility_settings,
234 R.id.print_settings,
235 R.id.nfc_payment_settings,
Fabrice Di Meglio2858b792014-02-18 19:35:08 -0800236 R.id.home_settings,
237 R.id.dashboard
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800238 };
239
240 private static final String[] ENTRY_FRAGMENTS = {
241 WirelessSettings.class.getName(),
242 WifiSettings.class.getName(),
243 AdvancedWifiSettings.class.getName(),
244 BluetoothSettings.class.getName(),
245 TetherSettings.class.getName(),
246 WifiP2pSettings.class.getName(),
247 VpnSettings.class.getName(),
248 DateTimeSettings.class.getName(),
249 LocalePicker.class.getName(),
250 InputMethodAndLanguageSettings.class.getName(),
251 SpellCheckersSettings.class.getName(),
252 UserDictionaryList.class.getName(),
253 UserDictionarySettings.class.getName(),
254 SoundSettings.class.getName(),
255 DisplaySettings.class.getName(),
256 DeviceInfoSettings.class.getName(),
257 ManageApplications.class.getName(),
258 ProcessStatsUi.class.getName(),
259 NotificationStation.class.getName(),
260 LocationSettings.class.getName(),
261 SecuritySettings.class.getName(),
262 PrivacySettings.class.getName(),
263 DeviceAdminSettings.class.getName(),
264 AccessibilitySettings.class.getName(),
265 CaptionPropertiesFragment.class.getName(),
266 com.android.settings.accessibility.ToggleInversionPreferenceFragment.class.getName(),
267 com.android.settings.accessibility.ToggleContrastPreferenceFragment.class.getName(),
268 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
269 TextToSpeechSettings.class.getName(),
270 Memory.class.getName(),
271 DevelopmentSettings.class.getName(),
272 UsbSettings.class.getName(),
273 AndroidBeam.class.getName(),
274 WifiDisplaySettings.class.getName(),
275 PowerUsageSummary.class.getName(),
276 AccountSyncSettings.class.getName(),
277 CryptKeeperSettings.class.getName(),
278 DataUsageSummary.class.getName(),
279 DreamSettings.class.getName(),
280 UserSettings.class.getName(),
281 NotificationAccessSettings.class.getName(),
282 ManageAccountsSettings.class.getName(),
283 PrintSettingsFragment.class.getName(),
284 PrintJobSettingsFragment.class.getName(),
285 TrustedCredentialsSettings.class.getName(),
286 PaymentSettings.class.getName(),
287 KeyboardLayoutPickerFragment.class.getName(),
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700288 ZenModeSettings.class.getName(),
289 NotificationSettings.class.getName(),
290 ChooseLockPassword.ChooseLockPasswordFragment.class.getName(),
291 ChooseLockPattern.ChooseLockPatternFragment.class.getName(),
292 InstalledAppDetails.class.getName()
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800293 };
294
295 private SharedPreferences mDevelopmentPreferences;
296 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
297
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800298 private AuthenticatorHelper mAuthenticatorHelper;
299 private boolean mListeningToAccountUpdates;
300
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800301 private boolean mBatteryPresent = true;
302 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
303
304 @Override
305 public void onReceive(Context context, Intent intent) {
306 String action = intent.getAction();
307 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
308 boolean batteryPresent = Utils.isBatteryPresent(intent);
309
310 if (mBatteryPresent != batteryPresent) {
311 mBatteryPresent = batteryPresent;
312 invalidateHeaders();
313 }
314 }
315 }
316 };
317
Svetoslav853e4712014-04-14 10:10:25 -0700318 private final DynamicIndexablePackageMonitor mDynamicIndexablePackageMonitor =
319 new DynamicIndexablePackageMonitor();
320
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700321 private Button mNextButton;
322 private ActionBar mActionBar;
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700323 private boolean mDisplayHomeAsUpEnabled;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700324
325 private SearchView mSearchView;
326 private MenuItem mSearchMenuItem;
327 private boolean mSearchMenuItemExpanded = false;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700328 private SearchResultsSummary mSearchResultsFragment;
329 private String mSearchQuery;
330
331 // Headers
332 protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800333 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800334 private HeaderAdapter mHeaderAdapter;
335
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800336 private static final int MSG_BUILD_HEADERS = 1;
337 private Handler mHandler = new Handler() {
338 @Override
339 public void handleMessage(Message msg) {
340 switch (msg.what) {
341 case MSG_BUILD_HEADERS: {
342 mHeaders.clear();
343 onBuildHeaders(mHeaders);
344 mHeaderAdapter.notifyDataSetChanged();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800345 } break;
346 }
347 }
348 };
349
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700350 private boolean mNeedToRevertToInitialFragment = false;
351
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800352 @Override
353 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
354 // Override the fragment title for Wallpaper settings
355 int titleRes = pref.getTitleRes();
356 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
357 titleRes = R.string.wallpaper_settings_fragment_title;
358 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
359 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
360 if (UserManager.get(this).isLinkedUser()) {
361 titleRes = R.string.profile_info_settings_title;
362 } else {
363 titleRes = R.string.user_info_settings_title;
364 }
365 }
366 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
367 null, 0);
368 return true;
369 }
370
371 @Override
372 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
373 return false;
374 }
375
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800376 private void invalidateHeaders() {
377 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
378 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
379 }
380 }
381
382 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800383 public void onConfigurationChanged(Configuration newConfig) {
384 super.onConfigurationChanged(newConfig);
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800385 Index.getInstance(this).update();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800386 }
387
388 @Override
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700389 protected void onStart() {
390 super.onStart();
391
392 if (mNeedToRevertToInitialFragment) {
393 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800394 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800395 }
396
397 @Override
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700398 public boolean onCreateOptionsMenu(Menu menu) {
399 MenuInflater inflater = getMenuInflater();
400 inflater.inflate(R.menu.options_menu, menu);
401
402 // Cache the search query (can be overriden by the OnQueryTextListener)
403 final String query = mSearchQuery;
404
Fabrice Di Meglio95937822014-03-31 19:46:42 -0700405 mSearchMenuItem = menu.findItem(R.id.search);
406 mSearchView = (SearchView) mSearchMenuItem.getActionView();
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700407
Fabrice Di Meglio95937822014-03-31 19:46:42 -0700408 mSearchMenuItem.setOnActionExpandListener(this);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700409 mSearchView.setOnQueryTextListener(this);
410 mSearchView.setOnCloseListener(this);
411
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700412 if (mSearchMenuItemExpanded) {
413 mSearchMenuItem.expandActionView();
414 }
415 mSearchView.setQuery(query, true /* submit */);
416
417 return true;
418 }
419
420 @Override
421 protected void onCreate(Bundle savedState) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800422 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
423 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
424 }
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800425 Index.getInstance(this).update();
426
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 Megliod25314d2014-03-21 19:24:43 -0700449 String initialFragmentName = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
450 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800451
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700452 if (savedState != null) {
Fabrice Di Meglio1800a9f2014-04-03 19:31:07 -0700453 // We are restarting from a previous saved state; used that to initialize, instead
454 // of starting fresh.
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700455 mSearchMenuItemExpanded = savedState.getBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED);
456 mSearchQuery = savedState.getString(SAVE_KEY_SEARCH_QUERY);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800457
Fabrice Di Meglio1800a9f2014-04-03 19:31:07 -0700458 final String initialTitle = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
459 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
460 setTitle(mInitialTitle);
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800461
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700462 ArrayList<Header> headers = savedState.getParcelableArrayList(SAVE_KEY_HEADERS);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800463 if (headers != null) {
464 mHeaders.addAll(headers);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700465 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800466 }
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700467
468 mDisplayHomeAsUpEnabled = savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800469 } else {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800470 // We need to build the Headers in all cases
471 onBuildHeaders(mHeaders);
472
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700473 if (initialFragmentName != null) {
474 final ComponentName cn = getIntent().getComponent();
475 // No UP is we are launched thru a Settings shortcut
476 if (!cn.getClassName().equals(SubSettings.class.getName())) {
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700477 mDisplayHomeAsUpEnabled = false;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700478 }
479 final String initialTitle = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
480 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800481 setTitle(mInitialTitle);
Fabrice Di Meglioc1457322014-04-04 19:07:50 -0700482
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700483 switchToFragment( initialFragmentName, initialArguments, true, false,
484 mInitialTitle, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000485 } else {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700486 // No UP if we are displaying the Headers
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700487 mDisplayHomeAsUpEnabled = false;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000488 if (mHeaders.size() > 0) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700489 mInitialTitle = getText(R.string.dashboard_title);
490 switchToFragment(DashboardSummary.class.getName(), null, false, false,
491 mInitialTitle, false);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000492 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800493 }
494 }
495
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700496 mActionBar = getActionBar();
497 mActionBar.setHomeButtonEnabled(true);
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700498 mActionBar.setDisplayHomeAsUpEnabled(mDisplayHomeAsUpEnabled);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700499
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800500 // see if we should show Back/Next buttons
501 Intent intent = getIntent();
502 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
503
504 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
505 if (buttonBar != null) {
506 buttonBar.setVisibility(View.VISIBLE);
507
508 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
509 backButton.setOnClickListener(new OnClickListener() {
510 public void onClick(View v) {
511 setResult(RESULT_CANCELED);
512 finish();
513 }
514 });
515 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
516 skipButton.setOnClickListener(new OnClickListener() {
517 public void onClick(View v) {
518 setResult(RESULT_OK);
519 finish();
520 }
521 });
522 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
523 mNextButton.setOnClickListener(new OnClickListener() {
524 public void onClick(View v) {
525 setResult(RESULT_OK);
526 finish();
527 }
528 });
529
530 // set our various button parameters
531 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
532 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
533 if (TextUtils.isEmpty(buttonText)) {
534 mNextButton.setVisibility(View.GONE);
535 }
536 else {
537 mNextButton.setText(buttonText);
538 }
539 }
540 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
541 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
542 if (TextUtils.isEmpty(buttonText)) {
543 backButton.setVisibility(View.GONE);
544 }
545 else {
546 backButton.setText(buttonText);
547 }
548 }
549 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
550 skipButton.setVisibility(View.VISIBLE);
551 }
552 }
553 }
Fabrice Di Meglioc95be4f2014-03-07 12:57:38 -0800554 }
555
556 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800557 public void onBackStackChanged() {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700558 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800559 }
560
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700561 private int setTitleFromBackStack() {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800562 final int count = getFragmentManager().getBackStackEntryCount();
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700563
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800564 if (count == 0) {
565 setTitle(mInitialTitle);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700566 return 0;
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800567 }
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700568
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800569 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1);
570 setTitleFromBackStackEntry(bse);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700571
572 return count;
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800573 }
574
575 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
576 final CharSequence title;
577 final int titleRes = bse.getBreadCrumbTitleRes();
578 if (titleRes > 0) {
579 title = getText(titleRes);
580 } else {
581 title = bse.getBreadCrumbTitle();
582 }
583 if (title != null) {
584 setTitle(title);
585 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800586 }
587
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800588 @Override
589 protected void onSaveInstanceState(Bundle outState) {
590 super.onSaveInstanceState(outState);
591
592 if (mHeaders.size() > 0) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700593 outState.putParcelableArrayList(SAVE_KEY_HEADERS, mHeaders);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800594 }
Fabrice Di Megliod6985df2014-04-03 16:43:26 -0700595
Fabrice Di Megliob731dd02014-04-03 18:40:38 -0700596 outState.putBoolean(SAVE_KEY_SHOW_HOME_AS_UP, mDisplayHomeAsUpEnabled);
597
Fabrice Di Megliod6985df2014-04-03 16:43:26 -0700598 // The option menus are created if the ActionBar is visible and they are also created
599 // asynchronously. If you launch Settings with an Intent action like
600 // android.intent.action.POWER_USAGE_SUMMARY and at the same time your device is locked
601 // thru a LockScreen, onCreateOptionsMenu() is not yet called and references to the search
602 // menu item and search view are null.
603 boolean isExpanded = (mSearchMenuItem != null) && mSearchMenuItem.isActionViewExpanded();
604 outState.putBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED, isExpanded);
605
606 String query = (mSearchView != null) ? mSearchView.getQuery().toString() : EMPTY_QUERY;
607 outState.putString(SAVE_KEY_SEARCH_QUERY, query);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800608 }
609
610 @Override
611 public void onResume() {
612 super.onResume();
613
614 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
615 @Override
616 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
617 invalidateHeaders();
618 }
619 };
620 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
621 mDevelopmentPreferencesListener);
622
Matthew Xiea504c4d2014-02-14 16:32:32 -0800623 mHeaderAdapter.resume(this);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800624 invalidateHeaders();
625
626 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Svetoslav853e4712014-04-14 10:10:25 -0700627
628 mDynamicIndexablePackageMonitor.register(this);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800629 }
630
631 @Override
632 public void onPause() {
633 super.onPause();
634
635 unregisterReceiver(mBatteryInfoReceiver);
636
637 mHeaderAdapter.pause();
638
639 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
640 mDevelopmentPreferencesListener);
641
642 mDevelopmentPreferencesListener = null;
Svetoslav853e4712014-04-14 10:10:25 -0700643
644 mDynamicIndexablePackageMonitor.unregister();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800645 }
646
647 @Override
648 public void onDestroy() {
649 super.onDestroy();
650 if (mListeningToAccountUpdates) {
651 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
652 }
653 }
654
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800655 protected boolean isValidFragment(String fragmentName) {
656 // Almost all fragments are wrapped in this,
657 // except for a few that have their own activities.
658 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
659 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
660 }
661 return false;
662 }
663
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800664 /**
665 * When in two-pane mode, switch to the fragment pane to show the given
666 * preference fragment.
667 *
668 * @param header The new header to display.
Fabrice Di Megliobbdada82014-04-04 10:16:59 -0700669 * @param position The position of the Header in the list.
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800670 */
Fabrice Di Megliobbdada82014-04-04 10:16:59 -0700671 private void onHeaderClick(Header header, int position) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800672 if (header == null) {
673 return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800674 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700675 if (header.fragment != null) {
676 startWithFragment(header.fragment, header.fragmentArguments, null, 0,
677 header.getTitle(getResources()));
678 } else if (header.intent != null) {
679 startActivity(header.intent);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800680 }
681 }
682
683 /**
684 * Called to determine whether the header list should be hidden.
685 * The default implementation returns the
686 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
687 * This is set to false, for example, when the activity is being re-launched
688 * to show a particular preference activity.
689 */
690 public boolean onIsHidingHeaders() {
691 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
692 }
693
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800694 @Override
695 public Intent getIntent() {
696 Intent superIntent = super.getIntent();
697 String startingFragment = getStartingFragmentClass(superIntent);
698 // This is called from super.onCreate, isMultiPane() is not yet reliable
699 // Do not use onIsHidingHeaders either, which relies itself on this method
700 if (startingFragment != null) {
701 Intent modIntent = new Intent(superIntent);
702 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
703 Bundle args = superIntent.getExtras();
704 if (args != null) {
705 args = new Bundle(args);
706 } else {
707 args = new Bundle();
708 }
709 args.putParcelable("intent", superIntent);
710 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
711 return modIntent;
712 }
713 return superIntent;
714 }
715
716 /**
717 * Checks if the component name in the intent is different from the Settings class and
718 * returns the class name to load as a fragment.
719 */
720 private String getStartingFragmentClass(Intent intent) {
721 if (mFragmentClass != null) return mFragmentClass;
722
723 String intentClass = intent.getComponent().getClassName();
724 if (intentClass.equals(getClass().getName())) return null;
725
726 if ("com.android.settings.ManageApplications".equals(intentClass)
727 || "com.android.settings.RunningServices".equals(intentClass)
728 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
729 // Old names of manage apps.
730 intentClass = com.android.settings.applications.ManageApplications.class.getName();
731 }
732
733 return intentClass;
734 }
735
736 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000737 * Start a new fragment containing a preference panel. If the preferences
738 * are being displayed in multi-pane mode, the given fragment class will
739 * be instantiated and placed in the appropriate pane. If running in
740 * single-pane mode, a new activity will be launched in which to show the
741 * fragment.
742 *
743 * @param fragmentClass Full name of the class implementing the fragment.
744 * @param args Any desired arguments to supply to the fragment.
745 * @param titleRes Optional resource identifier of the title of this
746 * fragment.
747 * @param titleText Optional text of the title of this fragment.
748 * @param resultTo Optional fragment that result data should be sent to.
749 * If non-null, resultTo.onActivityResult() will be called when this
750 * preference panel is done. The launched panel must use
751 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
752 * @param resultRequestCode If resultTo is non-null, this is the caller's
753 * request code to be received with the resut.
754 */
755 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700756 CharSequence titleText, Fragment resultTo, int resultRequestCode) {
Fabrice Di Meglio911fb2a2014-04-04 17:55:57 -0700757 String title;
758 if (titleRes > 0) {
759 title = getString(titleRes);
Fabrice Di Meglio821a0722014-04-08 13:56:06 -0700760 } else if (titleText != null) {
Fabrice Di Meglio911fb2a2014-04-04 17:55:57 -0700761 title = titleText.toString();
Fabrice Di Meglio821a0722014-04-08 13:56:06 -0700762 } else {
763 // There not much we can do in that case
764 title = "";
Fabrice Di Meglio911fb2a2014-04-04 17:55:57 -0700765 }
766 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, title);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000767 }
768
769 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800770 * Called by a preference panel fragment to finish itself.
771 *
772 * @param caller The fragment that is asking to be finished.
773 * @param resultCode Optional result code to send back to the original
774 * launching fragment.
775 * @param resultData Optional result data to send back to the original
776 * launching fragment.
777 */
778 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
779 setResult(resultCode, resultData);
780 }
781
782 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000783 * Start a new fragment.
784 *
785 * @param fragment The fragment to start
786 * @param push If true, the current fragment will be pushed onto the back stack. If false,
787 * the current fragment will be replaced.
788 */
789 public void startPreferenceFragment(Fragment fragment, boolean push) {
790 FragmentTransaction transaction = getFragmentManager().beginTransaction();
791 transaction.replace(R.id.prefs, fragment);
792 if (push) {
793 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
794 transaction.addToBackStack(BACK_STACK_PREFS);
795 } else {
796 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
797 }
798 transaction.commitAllowingStateLoss();
799 }
800
801 /**
Fabrice Di Megliod25314d2014-03-21 19:24:43 -0700802 * Switch to a specific Fragment with taking care of validation, Title and BackStack
803 */
804 private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
805 boolean addToBackStack, CharSequence title, boolean withTransition) {
806 if (validate && !isValidFragment(fragmentName)) {
807 throw new IllegalArgumentException("Invalid fragment for this activity: "
808 + fragmentName);
809 }
810 Fragment f = Fragment.instantiate(this, fragmentName, args);
811 FragmentTransaction transaction = getFragmentManager().beginTransaction();
812 transaction.replace(R.id.prefs, f);
813 if (withTransition) {
814 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
815 }
816 if (addToBackStack) {
817 transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
818 }
819 if (title != null) {
820 transaction.setBreadCrumbTitle(title);
821 }
822 transaction.commitAllowingStateLoss();
823 return f;
824 }
825
826 /**
827 * Start a new instance of this activity, showing only the given fragment.
828 * When launched in this mode, the given preference fragment will be instantiated and fill the
829 * entire activity.
830 *
831 * @param fragmentName The name of the fragment to display.
832 * @param args Optional arguments to supply to the fragment.
833 * @param resultTo Option fragment that should receive the result of
834 * the activity launch.
835 * @param resultRequestCode If resultTo is non-null, this is the request
836 * code in which to report the result.
837 * @param title String to display for the title of this set of preferences.
838 */
839 public void startWithFragment(String fragmentName, Bundle args,
840 Fragment resultTo, int resultRequestCode, CharSequence title) {
841 Intent intent = onBuildStartFragmentIntent(fragmentName, args, title);
842 if (resultTo == null) {
843 startActivity(intent);
844 } else {
845 resultTo.startActivityForResult(intent, resultRequestCode);
846 }
847 }
848
849 /**
850 * Build an Intent to launch a new activity showing the selected fragment.
851 * The implementation constructs an Intent that re-launches the current activity with the
852 * appropriate arguments to display the fragment.
853 *
854 * @param fragmentName The name of the fragment to display.
855 * @param args Optional arguments to supply to the fragment.
856 * @param title Optional title to show for this item.
857 * @return Returns an Intent that can be launched to display the given
858 * fragment.
859 */
860 public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args, CharSequence title) {
861 Intent intent = new Intent(Intent.ACTION_MAIN);
862 intent.setClass(this, SubSettings.class);
863 intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
864 intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
865 intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, title);
866 intent.putExtra(EXTRA_NO_HEADERS, true);
867 return intent;
868 }
869
870 /**
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800871 * Called when the activity needs its list of headers build.
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800872 *
873 * @param headers The list in which to place the headers.
874 */
875 private void onBuildHeaders(List<Header> headers) {
876 loadHeadersFromResource(R.xml.settings_headers, headers);
877 updateHeaderList(headers);
878 }
879
880 /**
881 * Parse the given XML file as a header description, adding each
882 * parsed Header into the target list.
883 *
884 * @param resid The XML resource to load and parse.
885 * @param target The list in which the parsed headers should be placed.
886 */
887 private void loadHeadersFromResource(int resid, List<Header> target) {
888 XmlResourceParser parser = null;
889 try {
890 parser = getResources().getXml(resid);
891 AttributeSet attrs = Xml.asAttributeSet(parser);
892
893 int type;
894 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
895 && type != XmlPullParser.START_TAG) {
896 // Parse next until start tag is found
897 }
898
899 String nodeName = parser.getName();
900 if (!"preference-headers".equals(nodeName)) {
901 throw new RuntimeException(
902 "XML document must start with <preference-headers> tag; found"
903 + nodeName + " at " + parser.getPositionDescription());
904 }
905
906 Bundle curBundle = null;
907
908 final int outerDepth = parser.getDepth();
909 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
910 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
911 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
912 continue;
913 }
914
915 nodeName = parser.getName();
916 if ("header".equals(nodeName)) {
917 Header header = new Header();
918
919 TypedArray sa = obtainStyledAttributes(
920 attrs, com.android.internal.R.styleable.PreferenceHeader);
921 header.id = sa.getResourceId(
922 com.android.internal.R.styleable.PreferenceHeader_id,
923 (int)HEADER_ID_UNDEFINED);
924 TypedValue 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 header.titleRes = tv.resourceId;
929 } else {
930 header.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 header.summaryRes = tv.resourceId;
938 } else {
939 header.summary = tv.string;
940 }
941 }
942 header.iconRes = sa.getResourceId(
943 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
944 header.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 innerDepth = parser.getDepth();
953 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
954 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
955 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
956 continue;
957 }
958
959 String innerNodeName = parser.getName();
960 if (innerNodeName.equals("extra")) {
961 getResources().parseBundleExtra("extra", attrs, curBundle);
962 XmlUtils.skipCurrentTag(parser);
963
964 } else if (innerNodeName.equals("intent")) {
965 header.intent = Intent.parseIntent(getResources(), parser, attrs);
966
967 } else {
968 XmlUtils.skipCurrentTag(parser);
969 }
970 }
971
972 if (curBundle.size() > 0) {
973 header.fragmentArguments = curBundle;
974 curBundle = null;
975 }
976
977 target.add(header);
978 } else {
979 XmlUtils.skipCurrentTag(parser);
980 }
981 }
982
983 } catch (XmlPullParserException e) {
984 throw new RuntimeException("Error parsing headers", e);
985 } catch (IOException e) {
986 throw new RuntimeException("Error parsing headers", e);
987 } finally {
988 if (parser != null) parser.close();
989 }
990 }
991
992 private void updateHeaderList(List<Header> target) {
993 final boolean showDev = mDevelopmentPreferences.getBoolean(
994 DevelopmentSettings.PREF_SHOW,
995 android.os.Build.TYPE.equals("eng"));
996 int i = 0;
997
998 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
999 mHeaderIndexMap.clear();
1000 while (i < target.size()) {
1001 Header header = target.get(i);
1002 // Ids are integers, so downcasting
1003 int id = (int) header.id;
1004 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1005 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1006 } else if (id == R.id.wifi_settings) {
1007 // Remove WiFi Settings if WiFi service is not available.
1008 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1009 target.remove(i);
1010 }
1011 } else if (id == R.id.bluetooth_settings) {
1012 // Remove Bluetooth Settings if Bluetooth service is not available.
1013 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1014 target.remove(i);
1015 }
1016 } else if (id == R.id.data_usage_settings) {
1017 // Remove data usage when kernel module not enabled
1018 final INetworkManagementService netManager = INetworkManagementService.Stub
1019 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1020 try {
1021 if (!netManager.isBandwidthControlEnabled()) {
1022 target.remove(i);
1023 }
1024 } catch (RemoteException e) {
1025 // ignored
1026 }
1027 } else if (id == R.id.battery_settings) {
1028 // Remove battery settings when battery is not available. (e.g. TV)
1029
1030 if (!mBatteryPresent) {
1031 target.remove(i);
1032 }
1033 } else if (id == R.id.account_settings) {
1034 int headerIndex = i + 1;
1035 i = insertAccountsHeaders(target, headerIndex);
1036 } else if (id == R.id.home_settings) {
1037 if (!updateHomeSettingHeaders(header)) {
1038 target.remove(i);
1039 }
1040 } else if (id == R.id.user_settings) {
1041 if (!UserHandle.MU_ENABLED
1042 || !UserManager.supportsMultipleUsers()
1043 || Utils.isMonkeyRunning()) {
1044 target.remove(i);
1045 }
1046 } else if (id == R.id.nfc_payment_settings) {
1047 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1048 target.remove(i);
1049 } else {
1050 // Only show if NFC is on and we have the HCE feature
1051 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1052 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1053 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1054 target.remove(i);
1055 }
1056 }
1057 } else if (id == R.id.development_settings) {
1058 if (!showDev) {
1059 target.remove(i);
1060 }
1061 } else if (id == R.id.account_add) {
1062 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1063 target.remove(i);
1064 }
1065 }
1066
1067 if (i < target.size() && target.get(i) == header
1068 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1069 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1070 target.remove(i);
1071 }
1072
1073 // Increment if the current one wasn't removed by the Utils code.
1074 if (i < target.size() && target.get(i) == header) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001075 mHeaderIndexMap.put(id, i);
1076 i++;
1077 }
1078 }
1079 }
1080
1081 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1082 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1083 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1084 for (String accountType : accountTypes) {
1085 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1086 if (label == null) {
1087 continue;
1088 }
1089
1090 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1091 boolean skipToAccount = accounts.length == 1
1092 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1093 Header accHeader = new Header();
1094 accHeader.title = label;
1095 if (accHeader.extras == null) {
1096 accHeader.extras = new Bundle();
1097 }
1098 if (skipToAccount) {
1099 accHeader.fragment = AccountSyncSettings.class.getName();
1100 accHeader.fragmentArguments = new Bundle();
1101 // Need this for the icon
1102 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1103 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1104 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1105 accounts[0]);
1106 } else {
1107 accHeader.fragment = ManageAccountsSettings.class.getName();
1108 accHeader.fragmentArguments = new Bundle();
1109 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1110 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1111 accountType);
1112 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1113 label.toString());
1114 }
1115 accountHeaders.add(accHeader);
1116 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1117 }
1118
1119 // Sort by label
1120 Collections.sort(accountHeaders, new Comparator<Header>() {
1121 @Override
1122 public int compare(Header h1, Header h2) {
1123 return h1.title.toString().compareTo(h2.title.toString());
1124 }
1125 });
1126
1127 for (Header header : accountHeaders) {
1128 target.add(headerIndex++, header);
1129 }
1130 if (!mListeningToAccountUpdates) {
1131 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1132 mListeningToAccountUpdates = true;
1133 }
1134 return headerIndex;
1135 }
1136
1137 private boolean updateHomeSettingHeaders(Header header) {
1138 // Once we decide to show Home settings, keep showing it forever
1139 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1140 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1141 return true;
1142 }
1143
1144 try {
1145 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1146 getPackageManager().getHomeActivities(homeApps);
1147 if (homeApps.size() < 2) {
1148 // When there's only one available home app, omit this settings
1149 // category entirely at the top level UI. If the user just
1150 // uninstalled the penultimate home app candidiate, we also
1151 // now tell them about why they aren't seeing 'Home' in the list.
1152 if (sShowNoHomeNotice) {
1153 sShowNoHomeNotice = false;
1154 NoHomeDialogFragment.show(this);
1155 }
1156 return false;
1157 } else {
1158 // Okay, we're allowing the Home settings category. Tell it, when
1159 // invoked via this front door, that we'll need to be told about the
1160 // case when the user uninstalls all but one home app.
1161 if (header.fragmentArguments == null) {
1162 header.fragmentArguments = new Bundle();
1163 }
1164 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1165 }
1166 } catch (Exception e) {
1167 // Can't look up the home activity; bail on configuring the icon
1168 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1169 }
1170
1171 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1172 return true;
1173 }
1174
1175 private void getMetaData() {
1176 try {
1177 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1178 PackageManager.GET_META_DATA);
1179 if (ai == null || ai.metaData == null) return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001180 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1181 } catch (NameNotFoundException nnfe) {
1182 // No recovery
1183 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1184 }
1185 }
1186
1187 // give subclasses access to the Next button
1188 public boolean hasNextButton() {
1189 return mNextButton != null;
1190 }
1191
1192 public Button getNextButton() {
1193 return mNextButton;
1194 }
1195
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001196 public HeaderAdapter getHeaderAdapter() {
1197 return mHeaderAdapter;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001198 }
1199
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001200 public void onListItemClick(ListView l, View v, int position, long id) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001201 if (!isResumed()) {
1202 return;
1203 }
1204 Object item = mHeaderAdapter.getItem(position);
1205 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001206 mSelectedHeader = (Header) item;
Fabrice Di Megliobbdada82014-04-04 10:16:59 -07001207 onHeaderClick(mSelectedHeader, position);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001208 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001209 }
1210 }
1211
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001212 @Override
1213 public boolean shouldUpRecreateTask(Intent targetIntent) {
1214 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1215 }
1216
1217 @Override
1218 public void onAccountsUpdated(Account[] accounts) {
1219 // TODO: watch for package upgrades to invalidate cache; see 7206643
1220 mAuthenticatorHelper.updateAuthDescriptions(this);
1221 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1222 invalidateHeaders();
1223 }
1224
1225 public static void requestHomeNotice() {
1226 sShowNoHomeNotice = true;
1227 }
1228
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001229 @Override
1230 public boolean onQueryTextSubmit(String query) {
1231 switchToSearchResultsFragmentIfNeeded();
1232 mSearchQuery = query;
1233 return mSearchResultsFragment.onQueryTextSubmit(query);
1234 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001235
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001236 @Override
1237 public boolean onQueryTextChange(String newText) {
1238 mSearchQuery = newText;
1239 return false;
1240 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001241
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001242 @Override
1243 public boolean onClose() {
1244 return false;
1245 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001246
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001247 @Override
1248 public boolean onMenuItemActionExpand(MenuItem item) {
1249 if (item.getItemId() == mSearchMenuItem.getItemId()) {
Fabrice Di Megliobb16fd82014-04-04 14:48:05 -07001250 switchToSearchResultsFragmentIfNeeded();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001251 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001252 return true;
1253 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001254
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001255 @Override
1256 public boolean onMenuItemActionCollapse(MenuItem item) {
1257 if (item.getItemId() == mSearchMenuItem.getItemId()) {
Fabrice Di Megliobb16fd82014-04-04 14:48:05 -07001258 if (mSearchMenuItemExpanded) {
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001259 revertToInitialFragment();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001260 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001261 }
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001262 return true;
1263 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001264
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001265 private void switchToSearchResultsFragmentIfNeeded() {
Fabrice Di Megliobb16fd82014-04-04 14:48:05 -07001266 if (mSearchResultsFragment != null) {
1267 return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001268 }
Fabrice Di Megliobb16fd82014-04-04 14:48:05 -07001269 Fragment current = getFragmentManager().findFragmentById(R.id.prefs);
1270 if (current != null && current instanceof SearchResultsSummary) {
1271 mSearchResultsFragment = (SearchResultsSummary) current;
1272 } else {
1273 String title = getString(R.string.search_results_title);
1274 mSearchResultsFragment = (SearchResultsSummary) switchToFragment(
1275 SearchResultsSummary.class.getName(), null, false, true, title, true);
1276 }
1277 mSearchMenuItemExpanded = true;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001278 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001279
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001280 public void needToRevertToInitialFragment() {
1281 mNeedToRevertToInitialFragment = true;
1282 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001283
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001284 private void revertToInitialFragment() {
1285 mNeedToRevertToInitialFragment = false;
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001286 mSearchResultsFragment = null;
Fabrice Di Megliobb16fd82014-04-04 14:48:05 -07001287 mSearchMenuItemExpanded = false;
1288 getFragmentManager().popBackStackImmediate(SettingsActivity.BACK_STACK_PREFS,
1289 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fabrice Di Megliod25314d2014-03-21 19:24:43 -07001290 mSearchMenuItem.collapseActionView();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001291 }
Svetoslav853e4712014-04-14 10:10:25 -07001292
1293 private static final class DynamicIndexablePackageMonitor extends PackageMonitor {
1294 private static final Intent ACCESSIBILITY_SERVICE_INTENT =
1295 new Intent(AccessibilityService.SERVICE_INTERFACE);
1296
1297 private static final Intent PRINT_SERVICE_INTENT =
1298 new Intent(PrintService.SERVICE_INTERFACE);
1299
1300 private static final long DELAY_PROCESS_PACKAGE_CHANGE = 2000;
1301
1302 private static final int MSG_PACKAGE_AVAILABLE = 1;
1303 private static final int MSG_PACKAGE_UNAVAILABLE = 2;
1304
1305 private final List<String> mAccessibilityServices = new ArrayList<String>();
1306 private final List<String> mPrintServices = new ArrayList<String>();
1307
1308 private final Handler mHandler = new Handler() {
1309 @Override
1310 public void handleMessage(Message msg) {
1311 switch (msg.what) {
1312 case MSG_PACKAGE_AVAILABLE: {
1313 String packageName = (String) msg.obj;
1314 handlePackageAvailable(packageName);
1315 } break;
1316
1317 case MSG_PACKAGE_UNAVAILABLE: {
1318 String packageName = (String) msg.obj;
1319 handlePackageUnavailable(packageName);
1320 } break;
1321 }
1322 }
1323 };
1324
1325 private Context mContext;
1326
1327 public void register(Context context) {
1328 mContext = context;
1329
1330 // Cache accessibility service packages to know when they go away.
1331 AccessibilityManager accessibilityManager = (AccessibilityManager)
1332 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
1333 List<AccessibilityServiceInfo> accessibilityServices = accessibilityManager
1334 .getInstalledAccessibilityServiceList();
1335 final int accessibilityServiceCount = accessibilityServices.size();
1336 for (int i = 0; i < accessibilityServiceCount; i++) {
1337 AccessibilityServiceInfo accessibilityService = accessibilityServices.get(i);
1338 mAccessibilityServices.add(accessibilityService.getResolveInfo()
1339 .serviceInfo.packageName);
1340 }
1341
1342 // Cache print service packages to know when they go away.
1343 PrintManager printManager = (PrintManager)
1344 mContext.getSystemService(Context.PRINT_SERVICE);
1345 List<PrintServiceInfo> printServices = printManager.getInstalledPrintServices();
1346 final int serviceCount = printServices.size();
1347 for (int i = 0; i < serviceCount; i++) {
1348 PrintServiceInfo printService = printServices.get(i);
1349 mPrintServices.add(printService.getResolveInfo()
1350 .serviceInfo.packageName);
1351 }
1352
1353 register(context, Looper.getMainLooper(), UserHandle.CURRENT, false);
1354 }
1355
1356 public void unregister() {
1357 super.unregister();
1358 mAccessibilityServices.clear();
1359 mPrintServices.clear();
1360 }
1361
1362 // Covers installed, appeared external storage with the package, upgraded.
1363 @Override
1364 public void onPackageAppeared(String packageName, int uid) {
1365 postMessage(MSG_PACKAGE_AVAILABLE, packageName);
1366 }
1367
1368 // Covers uninstalled, removed external storage with the package.
1369 @Override
1370 public void onPackageDisappeared(String packageName, int uid) {
1371 postMessage(MSG_PACKAGE_UNAVAILABLE, packageName);
1372 }
1373
1374 // Covers enabled, disabled.
1375 @Override
1376 public void onPackageModified(String packageName) {
1377 super.onPackageModified(packageName);
1378 final int state = mContext.getPackageManager().getApplicationEnabledSetting(
1379 packageName);
1380 if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
1381 || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
1382 postMessage(MSG_PACKAGE_AVAILABLE, packageName);
1383 } else {
1384 postMessage(MSG_PACKAGE_UNAVAILABLE, packageName);
1385 }
1386 }
1387
1388 private void postMessage(int what, String packageName) {
1389 Message message = mHandler.obtainMessage(what, packageName);
1390 mHandler.sendMessageDelayed(message, DELAY_PROCESS_PACKAGE_CHANGE);
1391 }
1392
1393 private void handlePackageAvailable(String packageName) {
1394 if (!mAccessibilityServices.contains(packageName)) {
1395 Intent intent = ACCESSIBILITY_SERVICE_INTENT;
1396 intent.setPackage(packageName);
1397 if (!mContext.getPackageManager().queryIntentServices(intent, 0).isEmpty()) {
1398 mAccessibilityServices.add(packageName);
1399 Index.getInstance(mContext).updateFromClassNameResource(
1400 AccessibilitySettings.class.getName(), false, true);
1401 }
1402 intent.setPackage(null);
1403 }
1404
1405 if (!mPrintServices.contains(packageName)) {
1406 Intent intent = PRINT_SERVICE_INTENT;
1407 intent.setPackage(packageName);
1408 if (!mContext.getPackageManager().queryIntentServices(intent, 0).isEmpty()) {
1409 mPrintServices.add(packageName);
1410 Index.getInstance(mContext).updateFromClassNameResource(
1411 PrintSettingsFragment.class.getName(), false, true);
1412 }
1413 intent.setPackage(null);
1414 }
1415 }
1416
1417 private void handlePackageUnavailable(String packageName) {
1418 final int accessibilityIndex = mAccessibilityServices.indexOf(packageName);
1419 if (accessibilityIndex >= 0) {
1420 mAccessibilityServices.remove(accessibilityIndex);
1421 Index.getInstance(mContext).updateFromClassNameResource(
1422 AccessibilitySettings.class.getName(), true, true);
1423 }
1424
1425 final int printIndex = mPrintServices.indexOf(packageName);
1426 if (printIndex >= 0) {
1427 mPrintServices.remove(printIndex);
1428 Index.getInstance(mContext).updateFromClassNameResource(
1429 PrintSettingsFragment.class.getName(), true, true);
1430 }
1431 }
1432 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001433}