2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings;
19 import android.app.ActionBar;
20 import android.app.Activity;
21 import android.app.Fragment;
22 import android.app.FragmentManager;
23 import android.app.FragmentTransaction;
24 import android.content.BroadcastReceiver;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.SharedPreferences;
30 import android.content.pm.ActivityInfo;
31 import android.content.pm.PackageManager;
32 import android.content.pm.PackageManager.NameNotFoundException;
33 import android.content.pm.ResolveInfo;
34 import android.content.res.Configuration;
35 import android.content.res.TypedArray;
36 import android.content.res.XmlResourceParser;
37 import android.nfc.NfcAdapter;
38 import android.os.Bundle;
39 import android.os.Handler;
40 import android.os.INetworkManagementService;
41 import android.os.Message;
42 import android.os.RemoteException;
43 import android.os.ServiceManager;
44 import android.os.UserHandle;
45 import android.os.UserManager;
46 import android.preference.Preference;
47 import android.preference.PreferenceFragment;
48 import android.preference.PreferenceManager;
49 import android.preference.PreferenceScreen;
50 import android.text.TextUtils;
51 import android.transition.TransitionManager;
52 import android.util.AttributeSet;
53 import android.util.Log;
54 import android.util.TypedValue;
55 import android.util.Xml;
56 import android.view.Menu;
57 import android.view.MenuInflater;
58 import android.view.MenuItem;
59 import android.view.View;
60 import android.view.View.OnClickListener;
61 import android.view.ViewGroup;
62 import android.widget.Button;
63 import android.widget.SearchView;
65 import com.android.internal.util.ArrayUtils;
66 import com.android.internal.util.XmlUtils;
67 import com.android.settings.accessibility.AccessibilitySettings;
68 import com.android.settings.accessibility.CaptionPropertiesFragment;
69 import com.android.settings.accounts.AccountSettings;
70 import com.android.settings.accounts.AccountSyncSettings;
71 import com.android.settings.applications.InstalledAppDetails;
72 import com.android.settings.applications.ManageApplications;
73 import com.android.settings.applications.ProcessStatsUi;
74 import com.android.settings.bluetooth.BluetoothSettings;
75 import com.android.settings.dashboard.DashboardCategory;
76 import com.android.settings.dashboard.DashboardSummary;
77 import com.android.settings.dashboard.DashboardTile;
78 import com.android.settings.dashboard.NoHomeDialogFragment;
79 import com.android.settings.dashboard.SearchResultsSummary;
80 import com.android.settings.deviceinfo.Memory;
81 import com.android.settings.deviceinfo.UsbSettings;
82 import com.android.settings.fuelgauge.BatterySaverSettings;
83 import com.android.settings.fuelgauge.PowerUsageSummary;
84 import com.android.settings.notification.NotificationAppList;
85 import com.android.settings.notification.OtherSoundSettings;
86 import com.android.settings.quicklaunch.QuickLaunchSettings;
87 import com.android.settings.search.DynamicIndexableContentMonitor;
88 import com.android.settings.search.Index;
89 import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
90 import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
91 import com.android.settings.inputmethod.SpellCheckersSettings;
92 import com.android.settings.inputmethod.UserDictionaryList;
93 import com.android.settings.location.LocationSettings;
94 import com.android.settings.nfc.AndroidBeam;
95 import com.android.settings.nfc.PaymentSettings;
96 import com.android.settings.notification.AppNotificationSettings;
97 import com.android.settings.notification.ConditionProviderSettings;
98 import com.android.settings.notification.NotificationAccessSettings;
99 import com.android.settings.notification.NotificationSettings;
100 import com.android.settings.notification.NotificationStation;
101 import com.android.settings.notification.ZenModeSettings;
102 import com.android.settings.print.PrintJobSettingsFragment;
103 import com.android.settings.print.PrintSettingsFragment;
104 import com.android.settings.sim.SimSettings;
105 import com.android.settings.tts.TextToSpeechSettings;
106 import com.android.settings.users.UserSettings;
107 import com.android.settings.voice.VoiceInputSettings;
108 import com.android.settings.vpn2.VpnSettings;
109 import com.android.settings.wfd.WifiDisplaySettings;
110 import com.android.settings.widget.SwitchBar;
111 import com.android.settings.wifi.AdvancedWifiSettings;
112 import com.android.settings.wifi.SavedAccessPointsWifiSettings;
113 import com.android.settings.wifi.WifiSettings;
114 import com.android.settings.wifi.p2p.WifiP2pSettings;
116 import org.xmlpull.v1.XmlPullParser;
117 import org.xmlpull.v1.XmlPullParserException;
119 import java.io.IOException;
120 import java.util.ArrayList;
121 import java.util.List;
122 import java.util.Set;
124 import static com.android.settings.dashboard.DashboardTile.TILE_ID_UNDEFINED;
126 public class SettingsActivity extends Activity
127 implements PreferenceManager.OnPreferenceTreeClickListener,
128 PreferenceFragment.OnPreferenceStartFragmentCallback,
129 ButtonBarHandler, FragmentManager.OnBackStackChangedListener,
130 SearchView.OnQueryTextListener, SearchView.OnCloseListener,
131 MenuItem.OnActionExpandListener {
133 private static final String LOG_TAG = "Settings";
135 // Constants for state save/restore
136 private static final String SAVE_KEY_CATEGORIES = ":settings:categories";
137 private static final String SAVE_KEY_SEARCH_MENU_EXPANDED = ":settings:search_menu_expanded";
138 private static final String SAVE_KEY_SEARCH_QUERY = ":settings:search_query";
139 private static final String SAVE_KEY_SHOW_HOME_AS_UP = ":settings:show_home_as_up";
140 private static final String SAVE_KEY_SHOW_SEARCH = ":settings:show_search";
141 private static final String SAVE_KEY_HOME_ACTIVITIES_COUNT = ":settings:home_activities_count";
144 * When starting this activity, the invoking Intent can contain this extra
145 * string to specify which fragment should be initially displayed.
146 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
147 * will call isValidFragment() to confirm that the fragment class name is valid for this
150 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
153 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
154 * this extra can also be specified to supply a Bundle of arguments to pass
155 * to that fragment when it is instantiated during the initial creation
158 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
161 * Fragment "key" argument passed thru {@link #EXTRA_SHOW_FRAGMENT_ARGUMENTS}
163 public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
165 public static final String BACK_STACK_PREFS = ":settings:prefs";
167 // extras that allow any preference activity to be launched as part of a wizard
169 // show Back and Next buttons? takes boolean parameter
170 // Back will then return RESULT_CANCELED and Next RESULT_OK
171 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
173 // add a Skip button?
174 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
176 // specify custom text for the Back or Next buttons, or cause a button to not appear
177 // at all by setting it to null
178 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
179 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
182 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
183 * those extra can also be specify to supply the title or title res id to be shown for
186 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
188 * The package name used to resolve the title resource id.
190 public static final String EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME =
191 ":settings:show_fragment_title_res_package_name";
192 public static final String EXTRA_SHOW_FRAGMENT_TITLE_RESID =
193 ":settings:show_fragment_title_resid";
194 public static final String EXTRA_SHOW_FRAGMENT_AS_SHORTCUT =
195 ":settings:show_fragment_as_shortcut";
197 public static final String EXTRA_SHOW_FRAGMENT_AS_SUBSETTING =
198 ":settings:show_fragment_as_subsetting";
200 private static final String META_DATA_KEY_FRAGMENT_CLASS =
201 "com.android.settings.FRAGMENT_CLASS";
203 private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
205 private static final String EMPTY_QUERY = "";
207 private static boolean sShowNoHomeNotice = false;
209 private String mFragmentClass;
211 private CharSequence mInitialTitle;
212 private int mInitialTitleResId;
214 // Show only these settings for restricted users
215 private int[] SETTINGS_FOR_RESTRICTED = {
216 R.id.wireless_section,
218 R.id.bluetooth_settings,
219 R.id.data_usage_settings,
221 R.id.wireless_settings,
223 R.id.notification_settings,
224 R.id.display_settings,
225 R.id.storage_settings,
226 R.id.application_settings,
227 R.id.battery_settings,
228 R.id.personal_section,
229 R.id.location_settings,
230 R.id.security_settings,
231 R.id.language_settings,
233 R.id.account_settings,
235 R.id.date_time_settings,
237 R.id.accessibility_settings,
239 R.id.nfc_payment_settings,
244 private static final String[] ENTRY_FRAGMENTS = {
245 WirelessSettings.class.getName(),
246 WifiSettings.class.getName(),
247 AdvancedWifiSettings.class.getName(),
248 SavedAccessPointsWifiSettings.class.getName(),
249 BluetoothSettings.class.getName(),
250 SimSettings.class.getName(),
251 TetherSettings.class.getName(),
252 WifiP2pSettings.class.getName(),
253 VpnSettings.class.getName(),
254 DateTimeSettings.class.getName(),
255 LocalePicker.class.getName(),
256 InputMethodAndLanguageSettings.class.getName(),
257 VoiceInputSettings.class.getName(),
258 SpellCheckersSettings.class.getName(),
259 UserDictionaryList.class.getName(),
260 UserDictionarySettings.class.getName(),
261 HomeSettings.class.getName(),
262 DisplaySettings.class.getName(),
263 DeviceInfoSettings.class.getName(),
264 ManageApplications.class.getName(),
265 ProcessStatsUi.class.getName(),
266 NotificationStation.class.getName(),
267 LocationSettings.class.getName(),
268 SecuritySettings.class.getName(),
269 UsageAccessSettings.class.getName(),
270 PrivacySettings.class.getName(),
271 DeviceAdminSettings.class.getName(),
272 AccessibilitySettings.class.getName(),
273 CaptionPropertiesFragment.class.getName(),
274 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
275 TextToSpeechSettings.class.getName(),
276 Memory.class.getName(),
277 DevelopmentSettings.class.getName(),
278 UsbSettings.class.getName(),
279 AndroidBeam.class.getName(),
280 WifiDisplaySettings.class.getName(),
281 PowerUsageSummary.class.getName(),
282 AccountSyncSettings.class.getName(),
283 AccountSettings.class.getName(),
284 CryptKeeperSettings.class.getName(),
285 DataUsageSummary.class.getName(),
286 DreamSettings.class.getName(),
287 UserSettings.class.getName(),
288 NotificationAccessSettings.class.getName(),
289 ConditionProviderSettings.class.getName(),
290 PrintSettingsFragment.class.getName(),
291 PrintJobSettingsFragment.class.getName(),
292 TrustedCredentialsSettings.class.getName(),
293 PaymentSettings.class.getName(),
294 KeyboardLayoutPickerFragment.class.getName(),
295 ZenModeSettings.class.getName(),
296 NotificationSettings.class.getName(),
297 ChooseLockPassword.ChooseLockPasswordFragment.class.getName(),
298 ChooseLockPattern.ChooseLockPatternFragment.class.getName(),
299 InstalledAppDetails.class.getName(),
300 BatterySaverSettings.class.getName(),
301 NotificationAppList.class.getName(),
302 AppNotificationSettings.class.getName(),
303 OtherSoundSettings.class.getName(),
304 QuickLaunchSettings.class.getName(),
305 ApnSettings.class.getName()
309 private static final String[] LIKE_SHORTCUT_INTENT_ACTION_ARRAY = {
310 "android.settings.APPLICATION_DETAILS_SETTINGS"
313 private SharedPreferences mDevelopmentPreferences;
314 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
316 private boolean mBatteryPresent = true;
317 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
319 public void onReceive(Context context, Intent intent) {
320 String action = intent.getAction();
321 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
322 boolean batteryPresent = Utils.isBatteryPresent(intent);
324 if (mBatteryPresent != batteryPresent) {
325 mBatteryPresent = batteryPresent;
326 invalidateCategories(true);
332 private final DynamicIndexableContentMonitor mDynamicIndexableContentMonitor =
333 new DynamicIndexableContentMonitor();
335 private ActionBar mActionBar;
336 private SwitchBar mSwitchBar;
338 private Button mNextButton;
340 private boolean mDisplayHomeAsUpEnabled;
341 private boolean mDisplaySearch;
343 private boolean mIsShowingDashboard;
344 private boolean mIsShortcut;
346 private ViewGroup mContent;
348 private SearchView mSearchView;
349 private MenuItem mSearchMenuItem;
350 private boolean mSearchMenuItemExpanded = false;
351 private SearchResultsSummary mSearchResultsFragment;
352 private String mSearchQuery;
355 private ArrayList<DashboardCategory> mCategories = new ArrayList<DashboardCategory>();
357 private static final String MSG_DATA_FORCE_REFRESH = "msg_data_force_refresh";
358 private static final int MSG_BUILD_CATEGORIES = 1;
359 private Handler mHandler = new Handler() {
361 public void handleMessage(Message msg) {
363 case MSG_BUILD_CATEGORIES: {
364 final boolean forceRefresh = msg.getData().getBoolean(MSG_DATA_FORCE_REFRESH);
366 buildDashboardCategories(mCategories);
373 private boolean mNeedToRevertToInitialFragment = false;
374 private int mHomeActivitiesCount = 1;
376 private Intent mResultIntentData;
378 public SwitchBar getSwitchBar() {
382 public List<DashboardCategory> getDashboardCategories(boolean forceRefresh) {
383 if (forceRefresh || mCategories.size() == 0) {
384 buildDashboardCategories(mCategories);
390 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
391 // Override the fragment title for Wallpaper settings
392 int titleRes = pref.getTitleRes();
393 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
394 titleRes = R.string.wallpaper_settings_fragment_title;
395 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
396 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
397 if (UserManager.get(this).isLinkedUser()) {
398 titleRes = R.string.profile_info_settings_title;
400 titleRes = R.string.user_info_settings_title;
403 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
409 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
413 private void invalidateCategories(boolean forceRefresh) {
414 if (!mHandler.hasMessages(MSG_BUILD_CATEGORIES)) {
415 Message msg = new Message();
416 msg.what = MSG_BUILD_CATEGORIES;
417 msg.getData().putBoolean(MSG_DATA_FORCE_REFRESH, forceRefresh);
422 public void onConfigurationChanged(Configuration newConfig) {
423 super.onConfigurationChanged(newConfig);
424 Index.getInstance(this).update();
428 protected void onStart() {
431 if (mNeedToRevertToInitialFragment) {
432 revertToInitialFragment();
437 public boolean onCreateOptionsMenu(Menu menu) {
438 if (!mDisplaySearch) {
442 MenuInflater inflater = getMenuInflater();
443 inflater.inflate(R.menu.options_menu, menu);
445 // Cache the search query (can be overriden by the OnQueryTextListener)
446 final String query = mSearchQuery;
448 mSearchMenuItem = menu.findItem(R.id.search);
449 mSearchView = (SearchView) mSearchMenuItem.getActionView();
451 if (mSearchMenuItem == null || mSearchView == null) {
455 if (mSearchResultsFragment != null) {
456 mSearchResultsFragment.setSearchView(mSearchView);
459 mSearchMenuItem.setOnActionExpandListener(this);
460 mSearchView.setOnQueryTextListener(this);
461 mSearchView.setOnCloseListener(this);
463 if (mSearchMenuItemExpanded) {
464 mSearchMenuItem.expandActionView();
466 mSearchView.setQuery(query, true /* submit */);
471 private static boolean isShortCutIntent(final Intent intent) {
472 Set<String> categories = intent.getCategories();
473 return (categories != null) && categories.contains("com.android.settings.SHORTCUT");
476 private static boolean isLikeShortCutIntent(final Intent intent) {
477 String action = intent.getAction();
478 if (action == null) {
481 for (int i = 0; i < LIKE_SHORTCUT_INTENT_ACTION_ARRAY.length; i++) {
482 if (LIKE_SHORTCUT_INTENT_ACTION_ARRAY[i].equals(action)) return true;
488 protected void onCreate(Bundle savedState) {
489 super.onCreate(savedState);
491 // Should happen before any call to getIntent()
494 final Intent intent = getIntent();
495 if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
496 getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
499 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
500 Context.MODE_PRIVATE);
502 // Getting Intent properties can only be done after the super.onCreate(...)
503 final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
505 mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) ||
506 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false);
508 final ComponentName cn = intent.getComponent();
509 final String className = cn.getClassName();
511 mIsShowingDashboard = className.equals(Settings.class.getName());
513 // This is a "Sub Settings" when:
514 // - this is a real SubSettings
515 // - or :settings:show_fragment_as_subsetting is passed to the Intent
516 final boolean isSubSettings = className.equals(SubSettings.class.getName()) ||
517 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
519 // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content insets
521 // Check also that we are not a Theme Dialog as we don't want to override them
522 final int themeResId = getThemeResId();
523 if (themeResId != R.style.Theme_DialogWhenLarge &&
524 themeResId != R.style.Theme_SubSettingsDialogWhenLarge) {
525 setTheme(R.style.Theme_SubSettings);
529 setContentView(mIsShowingDashboard ?
530 R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
532 mContent = (ViewGroup) findViewById(R.id.main_content);
534 getFragmentManager().addOnBackStackChangedListener(this);
536 if (mIsShowingDashboard) {
537 Index.getInstance(getApplicationContext()).update();
540 if (savedState != null) {
541 // We are restarting from a previous saved state; used that to initialize, instead
542 // of starting fresh.
543 mSearchMenuItemExpanded = savedState.getBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED);
544 mSearchQuery = savedState.getString(SAVE_KEY_SEARCH_QUERY);
546 setTitleFromIntent(intent);
548 ArrayList<DashboardCategory> categories =
549 savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);
550 if (categories != null) {
552 mCategories.addAll(categories);
553 setTitleFromBackStack();
556 mDisplayHomeAsUpEnabled = savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP);
557 mDisplaySearch = savedState.getBoolean(SAVE_KEY_SHOW_SEARCH);
558 mHomeActivitiesCount = savedState.getInt(SAVE_KEY_HOME_ACTIVITIES_COUNT,
559 1 /* one home activity by default */);
561 if (!mIsShowingDashboard) {
562 // Search is shown we are launched thru a Settings "shortcut". UP will be shown
563 // only if it is a sub settings
565 mDisplayHomeAsUpEnabled = isSubSettings;
566 mDisplaySearch = false;
567 } else if (isSubSettings) {
568 mDisplayHomeAsUpEnabled = true;
569 mDisplaySearch = true;
571 mDisplayHomeAsUpEnabled = false;
572 mDisplaySearch = false;
574 setTitleFromIntent(intent);
576 Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
577 switchToFragment(initialFragmentName, initialArguments, true, false,
578 mInitialTitleResId, mInitialTitle, false);
580 // No UP affordance if we are displaying the main Dashboard
581 mDisplayHomeAsUpEnabled = false;
582 // Show Search affordance
583 mDisplaySearch = true;
584 mInitialTitleResId = R.string.dashboard_title;
585 switchToFragment(DashboardSummary.class.getName(), null, false, false,
586 mInitialTitleResId, mInitialTitle, false);
590 mActionBar = getActionBar();
591 if (mActionBar != null) {
592 mActionBar.setDisplayHomeAsUpEnabled(mDisplayHomeAsUpEnabled);
593 mActionBar.setHomeButtonEnabled(mDisplayHomeAsUpEnabled);
595 mSwitchBar = (SwitchBar) findViewById(R.id.switch_bar);
597 // see if we should show Back/Next buttons
598 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
600 View buttonBar = findViewById(R.id.button_bar);
601 if (buttonBar != null) {
602 buttonBar.setVisibility(View.VISIBLE);
604 Button backButton = (Button)findViewById(R.id.back_button);
605 backButton.setOnClickListener(new OnClickListener() {
606 public void onClick(View v) {
607 setResult(RESULT_CANCELED, getResultIntentData());
611 Button skipButton = (Button)findViewById(R.id.skip_button);
612 skipButton.setOnClickListener(new OnClickListener() {
613 public void onClick(View v) {
614 setResult(RESULT_OK, getResultIntentData());
618 mNextButton = (Button)findViewById(R.id.next_button);
619 mNextButton.setOnClickListener(new OnClickListener() {
620 public void onClick(View v) {
621 setResult(RESULT_OK, getResultIntentData());
626 // set our various button parameters
627 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
628 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
629 if (TextUtils.isEmpty(buttonText)) {
630 mNextButton.setVisibility(View.GONE);
633 mNextButton.setText(buttonText);
636 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
637 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
638 if (TextUtils.isEmpty(buttonText)) {
639 backButton.setVisibility(View.GONE);
642 backButton.setText(buttonText);
645 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
646 skipButton.setVisibility(View.VISIBLE);
651 mHomeActivitiesCount = getHomeActivitiesCount();
654 private int getHomeActivitiesCount() {
655 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
656 getPackageManager().getHomeActivities(homeApps);
657 return homeApps.size();
660 private void setTitleFromIntent(Intent intent) {
661 final int initialTitleResId = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, -1);
662 if (initialTitleResId > 0) {
663 mInitialTitle = null;
664 mInitialTitleResId = initialTitleResId;
666 final String initialTitleResPackageName = intent.getStringExtra(
667 EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME);
668 if (initialTitleResPackageName != null) {
670 Context authContext = createPackageContextAsUser(initialTitleResPackageName,
671 0 /* flags */, new UserHandle(UserHandle.myUserId()));
672 mInitialTitle = authContext.getResources().getText(mInitialTitleResId);
673 setTitle(mInitialTitle);
674 mInitialTitleResId = -1;
676 } catch (NameNotFoundException e) {
677 Log.w(LOG_TAG, "Could not find package" + initialTitleResPackageName);
680 setTitle(mInitialTitleResId);
683 mInitialTitleResId = -1;
684 final String initialTitle = intent.getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
685 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
686 setTitle(mInitialTitle);
691 public void onBackStackChanged() {
692 setTitleFromBackStack();
695 private int setTitleFromBackStack() {
696 final int count = getFragmentManager().getBackStackEntryCount();
699 if (mInitialTitleResId > 0) {
700 setTitle(mInitialTitleResId);
702 setTitle(mInitialTitle);
707 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1);
708 setTitleFromBackStackEntry(bse);
713 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
714 final CharSequence title;
715 final int titleRes = bse.getBreadCrumbTitleRes();
717 title = getText(titleRes);
719 title = bse.getBreadCrumbTitle();
727 protected void onSaveInstanceState(Bundle outState) {
728 super.onSaveInstanceState(outState);
730 if (mCategories.size() > 0) {
731 outState.putParcelableArrayList(SAVE_KEY_CATEGORIES, mCategories);
734 outState.putBoolean(SAVE_KEY_SHOW_HOME_AS_UP, mDisplayHomeAsUpEnabled);
735 outState.putBoolean(SAVE_KEY_SHOW_SEARCH, mDisplaySearch);
737 if (mDisplaySearch) {
738 // The option menus are created if the ActionBar is visible and they are also created
739 // asynchronously. If you launch Settings with an Intent action like
740 // android.intent.action.POWER_USAGE_SUMMARY and at the same time your device is locked
741 // thru a LockScreen, onCreateOptionsMenu() is not yet called and references to the search
742 // menu item and search view are null.
743 boolean isExpanded = (mSearchMenuItem != null) && mSearchMenuItem.isActionViewExpanded();
744 outState.putBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED, isExpanded);
746 String query = (mSearchView != null) ? mSearchView.getQuery().toString() : EMPTY_QUERY;
747 outState.putString(SAVE_KEY_SEARCH_QUERY, query);
750 outState.putInt(SAVE_KEY_HOME_ACTIVITIES_COUNT, mHomeActivitiesCount);
754 public void onResume() {
757 final int newHomeActivityCount = getHomeActivitiesCount();
758 if (newHomeActivityCount != mHomeActivitiesCount) {
759 mHomeActivitiesCount = newHomeActivityCount;
760 invalidateCategories(true);
763 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
765 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
766 invalidateCategories(true);
769 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
770 mDevelopmentPreferencesListener);
772 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
774 mDynamicIndexableContentMonitor.register(this);
776 if(mDisplaySearch && !TextUtils.isEmpty(mSearchQuery)) {
777 onQueryTextSubmit(mSearchQuery);
782 public void onPause() {
785 unregisterReceiver(mBatteryInfoReceiver);
786 mDynamicIndexableContentMonitor.unregister();
790 public void onDestroy() {
793 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
794 mDevelopmentPreferencesListener);
795 mDevelopmentPreferencesListener = null;
798 protected boolean isValidFragment(String fragmentName) {
799 // Almost all fragments are wrapped in this,
800 // except for a few that have their own activities.
801 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
802 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
808 public Intent getIntent() {
809 Intent superIntent = super.getIntent();
810 String startingFragment = getStartingFragmentClass(superIntent);
811 // This is called from super.onCreate, isMultiPane() is not yet reliable
812 // Do not use onIsHidingHeaders either, which relies itself on this method
813 if (startingFragment != null) {
814 Intent modIntent = new Intent(superIntent);
815 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
816 Bundle args = superIntent.getExtras();
818 args = new Bundle(args);
822 args.putParcelable("intent", superIntent);
823 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
830 * Checks if the component name in the intent is different from the Settings class and
831 * returns the class name to load as a fragment.
833 private String getStartingFragmentClass(Intent intent) {
834 if (mFragmentClass != null) return mFragmentClass;
836 String intentClass = intent.getComponent().getClassName();
837 if (intentClass.equals(getClass().getName())) return null;
839 if ("com.android.settings.ManageApplications".equals(intentClass)
840 || "com.android.settings.RunningServices".equals(intentClass)
841 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
842 // Old names of manage apps.
843 intentClass = com.android.settings.applications.ManageApplications.class.getName();
850 * Start a new fragment containing a preference panel. If the preferences
851 * are being displayed in multi-pane mode, the given fragment class will
852 * be instantiated and placed in the appropriate pane. If running in
853 * single-pane mode, a new activity will be launched in which to show the
856 * @param fragmentClass Full name of the class implementing the fragment.
857 * @param args Any desired arguments to supply to the fragment.
858 * @param titleRes Optional resource identifier of the title of this
860 * @param titleText Optional text of the title of this fragment.
861 * @param resultTo Optional fragment that result data should be sent to.
862 * If non-null, resultTo.onActivityResult() will be called when this
863 * preference panel is done. The launched panel must use
864 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
865 * @param resultRequestCode If resultTo is non-null, this is the caller's
866 * request code to be received with the result.
868 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
869 CharSequence titleText, Fragment resultTo, int resultRequestCode) {
872 if (titleText != null) {
873 title = titleText.toString();
875 // There not much we can do in that case
879 Utils.startWithFragment(this, fragmentClass, args, resultTo, resultRequestCode,
880 titleRes, title, mIsShortcut);
884 * Start a new fragment in a new activity containing a preference panel for a given user. If the
885 * preferences are being displayed in multi-pane mode, the given fragment class will be
886 * instantiated and placed in the appropriate pane. If running in single-pane mode, a new
887 * activity will be launched in which to show the fragment.
889 * @param fragmentClass Full name of the class implementing the fragment.
890 * @param args Any desired arguments to supply to the fragment.
891 * @param titleRes Optional resource identifier of the title of this fragment.
892 * @param titleText Optional text of the title of this fragment.
893 * @param userHandle The user for which the panel has to be started.
895 public void startPreferencePanelAsUser(String fragmentClass, Bundle args, int titleRes,
896 CharSequence titleText, UserHandle userHandle) {
899 if (titleText != null) {
900 title = titleText.toString();
902 // There not much we can do in that case
906 Utils.startWithFragmentAsUser(this, fragmentClass, args,
907 titleRes, title, mIsShortcut, userHandle);
911 * Called by a preference panel fragment to finish itself.
913 * @param caller The fragment that is asking to be finished.
914 * @param resultCode Optional result code to send back to the original
915 * launching fragment.
916 * @param resultData Optional result data to send back to the original
917 * launching fragment.
919 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
920 setResult(resultCode, resultData);
925 * Start a new fragment.
927 * @param fragment The fragment to start
928 * @param push If true, the current fragment will be pushed onto the back stack. If false,
929 * the current fragment will be replaced.
931 public void startPreferenceFragment(Fragment fragment, boolean push) {
932 FragmentTransaction transaction = getFragmentManager().beginTransaction();
933 transaction.replace(R.id.main_content, fragment);
935 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
936 transaction.addToBackStack(BACK_STACK_PREFS);
938 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
940 transaction.commitAllowingStateLoss();
944 * Switch to a specific Fragment with taking care of validation, Title and BackStack
946 private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
947 boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition) {
948 if (validate && !isValidFragment(fragmentName)) {
949 throw new IllegalArgumentException("Invalid fragment for this activity: "
952 Fragment f = Fragment.instantiate(this, fragmentName, args);
953 FragmentTransaction transaction = getFragmentManager().beginTransaction();
954 transaction.replace(R.id.main_content, f);
955 if (withTransition) {
956 TransitionManager.beginDelayedTransition(mContent);
958 if (addToBackStack) {
959 transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
961 if (titleResId > 0) {
962 transaction.setBreadCrumbTitle(titleResId);
963 } else if (title != null) {
964 transaction.setBreadCrumbTitle(title);
966 transaction.commitAllowingStateLoss();
967 getFragmentManager().executePendingTransactions();
972 * Called when the activity needs its list of categories/tiles built.
974 * @param categories The list in which to place the tiles categories.
976 private void buildDashboardCategories(List<DashboardCategory> categories) {
978 loadCategoriesFromResource(R.xml.dashboard_categories, categories);
979 updateTilesList(categories);
983 * Parse the given XML file as a categories description, adding each
984 * parsed categories and tiles into the target list.
986 * @param resid The XML resource to load and parse.
987 * @param target The list in which the parsed categories and tiles should be placed.
989 private void loadCategoriesFromResource(int resid, List<DashboardCategory> target) {
990 XmlResourceParser parser = null;
992 parser = getResources().getXml(resid);
993 AttributeSet attrs = Xml.asAttributeSet(parser);
996 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
997 && type != XmlPullParser.START_TAG) {
998 // Parse next until start tag is found
1001 String nodeName = parser.getName();
1002 if (!"dashboard-categories".equals(nodeName)) {
1003 throw new RuntimeException(
1004 "XML document must start with <preference-categories> tag; found"
1005 + nodeName + " at " + parser.getPositionDescription());
1008 Bundle curBundle = null;
1010 final int outerDepth = parser.getDepth();
1011 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1012 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1013 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1017 nodeName = parser.getName();
1018 if ("dashboard-category".equals(nodeName)) {
1019 DashboardCategory category = new DashboardCategory();
1021 TypedArray sa = obtainStyledAttributes(
1022 attrs, com.android.internal.R.styleable.PreferenceHeader);
1023 category.id = sa.getResourceId(
1024 com.android.internal.R.styleable.PreferenceHeader_id,
1025 (int)DashboardCategory.CAT_ID_UNDEFINED);
1027 TypedValue tv = sa.peekValue(
1028 com.android.internal.R.styleable.PreferenceHeader_title);
1029 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1030 if (tv.resourceId != 0) {
1031 category.titleRes = tv.resourceId;
1033 category.title = tv.string;
1038 final int innerDepth = parser.getDepth();
1039 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1040 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
1041 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1045 String innerNodeName = parser.getName();
1046 if (innerNodeName.equals("dashboard-tile")) {
1047 DashboardTile tile = new DashboardTile();
1049 sa = obtainStyledAttributes(
1050 attrs, com.android.internal.R.styleable.PreferenceHeader);
1051 tile.id = sa.getResourceId(
1052 com.android.internal.R.styleable.PreferenceHeader_id,
1053 (int)TILE_ID_UNDEFINED);
1055 com.android.internal.R.styleable.PreferenceHeader_title);
1056 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1057 if (tv.resourceId != 0) {
1058 tile.titleRes = tv.resourceId;
1060 tile.title = tv.string;
1064 com.android.internal.R.styleable.PreferenceHeader_summary);
1065 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1066 if (tv.resourceId != 0) {
1067 tile.summaryRes = tv.resourceId;
1069 tile.summary = tv.string;
1072 tile.iconRes = sa.getResourceId(
1073 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
1074 tile.fragment = sa.getString(
1075 com.android.internal.R.styleable.PreferenceHeader_fragment);
1078 if (curBundle == null) {
1079 curBundle = new Bundle();
1082 final int innerDepth2 = parser.getDepth();
1083 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1084 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth2)) {
1085 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1089 String innerNodeName2 = parser.getName();
1090 if (innerNodeName2.equals("extra")) {
1091 getResources().parseBundleExtra("extra", attrs, curBundle);
1092 XmlUtils.skipCurrentTag(parser);
1094 } else if (innerNodeName2.equals("intent")) {
1095 tile.intent = Intent.parseIntent(getResources(), parser, attrs);
1098 XmlUtils.skipCurrentTag(parser);
1102 if (curBundle.size() > 0) {
1103 tile.fragmentArguments = curBundle;
1107 // Show the SIM Cards setting if there are more than 2 SIMs installed.
1108 if(tile.id != R.id.sim_settings || Utils.showSimCardTile(this)){
1109 category.addTile(tile);
1113 XmlUtils.skipCurrentTag(parser);
1117 target.add(category);
1119 XmlUtils.skipCurrentTag(parser);
1123 } catch (XmlPullParserException e) {
1124 throw new RuntimeException("Error parsing categories", e);
1125 } catch (IOException e) {
1126 throw new RuntimeException("Error parsing categories", e);
1128 if (parser != null) parser.close();
1132 private void updateTilesList(List<DashboardCategory> target) {
1133 final boolean showDev = mDevelopmentPreferences.getBoolean(
1134 DevelopmentSettings.PREF_SHOW,
1135 android.os.Build.TYPE.equals("eng"));
1137 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1139 final int size = target.size();
1140 for (int i = 0; i < size; i++) {
1142 DashboardCategory category = target.get(i);
1144 // Ids are integers, so downcasting is ok
1145 int id = (int) category.id;
1146 int n = category.getTilesCount() - 1;
1149 DashboardTile tile = category.getTile(n);
1150 boolean removeTile = false;
1152 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1153 if (!Utils.updateTileToSpecificActivityFromMetaDataOrRemove(this, tile)) {
1156 } else if (id == R.id.wifi_settings) {
1157 // Remove WiFi Settings if WiFi service is not available.
1158 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1161 } else if (id == R.id.bluetooth_settings) {
1162 // Remove Bluetooth Settings if Bluetooth service is not available.
1163 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1166 } else if (id == R.id.data_usage_settings) {
1167 // Remove data usage when kernel module not enabled
1168 final INetworkManagementService netManager = INetworkManagementService.Stub
1169 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1171 if (!netManager.isBandwidthControlEnabled()) {
1174 } catch (RemoteException e) {
1177 } else if (id == R.id.battery_settings) {
1178 // Remove battery settings when battery is not available. (e.g. TV)
1180 if (!mBatteryPresent) {
1183 } else if (id == R.id.home_settings) {
1184 if (!updateHomeSettingTiles(tile)) {
1187 } else if (id == R.id.user_settings) {
1188 boolean hasMultipleUsers =
1189 ((UserManager) getSystemService(Context.USER_SERVICE))
1190 .getUserCount() > 1;
1191 if (!UserHandle.MU_ENABLED
1192 || (!UserManager.supportsMultipleUsers()
1193 && !hasMultipleUsers)
1194 || Utils.isMonkeyRunning()) {
1197 } else if (id == R.id.nfc_payment_settings) {
1198 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1201 // Only show if NFC is on and we have the HCE feature
1202 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1203 if (adapter == null || !adapter.isEnabled() ||
1204 !getPackageManager().hasSystemFeature(
1205 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1209 } else if (id == R.id.print_settings) {
1210 boolean hasPrintingSupport = getPackageManager().hasSystemFeature(
1211 PackageManager.FEATURE_PRINTING);
1212 if (!hasPrintingSupport) {
1215 } else if (id == R.id.development_settings) {
1216 if (!showDev || um.hasUserRestriction(
1217 UserManager.DISALLOW_DEBUGGING_FEATURES)) {
1222 if (UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1223 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1227 if (removeTile && n < category.getTilesCount()) {
1228 category.removeTile(n);
1235 private boolean updateHomeSettingTiles(DashboardTile tile) {
1236 // Once we decide to show Home settings, keep showing it forever
1237 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1238 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1243 mHomeActivitiesCount = getHomeActivitiesCount();
1244 if (mHomeActivitiesCount < 2) {
1245 // When there's only one available home app, omit this settings
1246 // category entirely at the top level UI. If the user just
1247 // uninstalled the penultimate home app candidiate, we also
1248 // now tell them about why they aren't seeing 'Home' in the list.
1249 if (sShowNoHomeNotice) {
1250 sShowNoHomeNotice = false;
1251 NoHomeDialogFragment.show(this);
1255 // Okay, we're allowing the Home settings category. Tell it, when
1256 // invoked via this front door, that we'll need to be told about the
1257 // case when the user uninstalls all but one home app.
1258 if (tile.fragmentArguments == null) {
1259 tile.fragmentArguments = new Bundle();
1261 tile.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1263 } catch (Exception e) {
1264 // Can't look up the home activity; bail on configuring the icon
1265 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1268 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1272 private void getMetaData() {
1274 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1275 PackageManager.GET_META_DATA);
1276 if (ai == null || ai.metaData == null) return;
1277 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1278 } catch (NameNotFoundException nnfe) {
1280 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1284 // give subclasses access to the Next button
1285 public boolean hasNextButton() {
1286 return mNextButton != null;
1289 public Button getNextButton() {
1294 public boolean shouldUpRecreateTask(Intent targetIntent) {
1295 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1298 public static void requestHomeNotice() {
1299 sShowNoHomeNotice = true;
1303 public boolean onQueryTextSubmit(String query) {
1304 switchToSearchResultsFragmentIfNeeded();
1305 mSearchQuery = query;
1306 return mSearchResultsFragment.onQueryTextSubmit(query);
1310 public boolean onQueryTextChange(String newText) {
1311 mSearchQuery = newText;
1312 if (mSearchResultsFragment == null) {
1315 return mSearchResultsFragment.onQueryTextChange(newText);
1319 public boolean onClose() {
1324 public boolean onMenuItemActionExpand(MenuItem item) {
1325 if (item.getItemId() == mSearchMenuItem.getItemId()) {
1326 switchToSearchResultsFragmentIfNeeded();
1332 public boolean onMenuItemActionCollapse(MenuItem item) {
1333 if (item.getItemId() == mSearchMenuItem.getItemId()) {
1334 if (mSearchMenuItemExpanded) {
1335 revertToInitialFragment();
1341 private void switchToSearchResultsFragmentIfNeeded() {
1342 if (mSearchResultsFragment != null) {
1345 Fragment current = getFragmentManager().findFragmentById(R.id.main_content);
1346 if (current != null && current instanceof SearchResultsSummary) {
1347 mSearchResultsFragment = (SearchResultsSummary) current;
1349 mSearchResultsFragment = (SearchResultsSummary) switchToFragment(
1350 SearchResultsSummary.class.getName(), null, false, true,
1351 R.string.search_results_title, null, true);
1353 mSearchResultsFragment.setSearchView(mSearchView);
1354 mSearchMenuItemExpanded = true;
1357 public void needToRevertToInitialFragment() {
1358 mNeedToRevertToInitialFragment = true;
1361 private void revertToInitialFragment() {
1362 mNeedToRevertToInitialFragment = false;
1363 mSearchResultsFragment = null;
1364 mSearchMenuItemExpanded = false;
1365 getFragmentManager().popBackStackImmediate(SettingsActivity.BACK_STACK_PREFS,
1366 FragmentManager.POP_BACK_STACK_INCLUSIVE);
1367 if (mSearchMenuItem != null) {
1368 mSearchMenuItem.collapseActionView();
1372 public Intent getResultIntentData() {
1373 return mResultIntentData;
1376 public void setResultIntentData(Intent resultIntentData) {
1377 mResultIntentData = resultIntentData;