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 static com.android.settings.dashboard.DashboardTile.TILE_ID_UNDEFINED;
21 import android.app.ActionBar;
22 import android.app.Activity;
23 import android.app.Fragment;
24 import android.app.FragmentManager;
25 import android.app.FragmentTransaction;
26 import android.content.BroadcastReceiver;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.SharedPreferences;
32 import android.content.pm.ActivityInfo;
33 import android.content.pm.PackageManager;
34 import android.content.pm.PackageManager.NameNotFoundException;
35 import android.content.pm.ResolveInfo;
36 import android.content.res.Configuration;
37 import android.content.res.TypedArray;
38 import android.content.res.XmlResourceParser;
39 import android.nfc.NfcAdapter;
40 import android.nfc.Tag;
41 import android.os.Bundle;
42 import android.os.Handler;
43 import android.os.Message;
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.ArrayMap;
53 import android.util.AttributeSet;
54 import android.util.Log;
55 import android.util.Pair;
56 import android.util.TypedValue;
57 import android.util.Xml;
58 import android.view.Menu;
59 import android.view.MenuInflater;
60 import android.view.MenuItem;
61 import android.view.View;
62 import android.view.View.OnClickListener;
63 import android.view.ViewGroup;
64 import android.widget.Button;
65 import android.widget.SearchView;
67 import com.android.internal.logging.MetricsLogger;
68 import com.android.internal.util.ArrayUtils;
69 import com.android.internal.util.XmlUtils;
70 import com.android.settings.accessibility.AccessibilitySettings;
71 import com.android.settings.accessibility.CaptionPropertiesFragment;
72 import com.android.settings.accounts.AccountSettings;
73 import com.android.settings.accounts.AccountSyncSettings;
74 import com.android.settings.applications.DrawOverlayDetails;
75 import com.android.settings.applications.InstalledAppDetails;
76 import com.android.settings.applications.ManageApplications;
77 import com.android.settings.applications.ManageAssist;
78 import com.android.settings.applications.ProcessStatsSummary;
79 import com.android.settings.applications.ProcessStatsUi;
80 import com.android.settings.applications.UsageAccessDetails;
81 import com.android.settings.applications.WriteSettingsDetails;
82 import com.android.settings.blacklist.BlacklistSettings;
83 import com.android.settings.bluetooth.BluetoothSettings;
84 import com.android.settings.contributors.ContributorsCloudFragment;
85 import com.android.settings.cyanogenmod.DisplayRotation;
86 import com.android.settings.dashboard.DashboardCategory;
87 import com.android.settings.dashboard.DashboardSummary;
88 import com.android.settings.dashboard.DashboardTile;
89 import com.android.settings.dashboard.NoHomeDialogFragment;
90 import com.android.settings.dashboard.SearchResultsSummary;
91 import com.android.settings.deviceinfo.PrivateVolumeForget;
92 import com.android.settings.deviceinfo.PrivateVolumeSettings;
93 import com.android.settings.deviceinfo.PublicVolumeSettings;
94 import com.android.settings.deviceinfo.StorageSettings;
95 import com.android.settings.fuelgauge.PowerUsageDetail;
96 import com.android.settings.fuelgauge.PowerUsageSummary;
97 import com.android.settings.livedisplay.LiveDisplay;
98 import com.android.settings.notification.NotificationManagerSettings;
99 import com.android.settings.notification.OtherSoundSettings;
100 import com.android.settings.notification.SoundSettings;
101 import com.android.settings.profiles.NFCProfileTagCallback;
102 import com.android.settings.profiles.ProfilesSettings;
103 import com.android.settings.search.DynamicIndexableContentMonitor;
104 import com.android.settings.search.Index;
105 import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
106 import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
107 import com.android.settings.inputmethod.SpellCheckersSettings;
108 import com.android.settings.inputmethod.UserDictionaryList;
109 import com.android.settings.location.LocationSettings;
110 import com.android.settings.nfc.AndroidBeam;
111 import com.android.settings.nfc.PaymentSettings;
112 import com.android.settings.notification.AppNotificationSettings;
113 import com.android.settings.notification.NotificationAccessSettings;
114 import com.android.settings.notification.NotificationStation;
115 import com.android.settings.notification.OtherSoundSettings;
116 import com.android.settings.notification.ZenAccessSettings;
117 import com.android.settings.notification.ZenModeAutomationSettings;
118 import com.android.settings.notification.ZenModeEventRuleSettings;
119 import com.android.settings.notification.ZenModeExternalRuleSettings;
120 import com.android.settings.notification.ZenModePrioritySettings;
121 import com.android.settings.notification.ZenModeSettings;
122 import com.android.settings.notification.ZenModeScheduleRuleSettings;
123 import com.android.settings.print.PrintJobSettingsFragment;
124 import com.android.settings.print.PrintSettingsFragment;
125 import com.android.settings.search.DynamicIndexableContentMonitor;
126 import com.android.settings.search.Index;
127 import com.android.settings.privacyguard.PrivacyGuardPrefs;
128 import com.android.settings.sim.SimSettings;
129 import com.android.settings.tts.TextToSpeechSettings;
130 import com.android.settings.users.UserSettings;
131 import com.android.settings.vpn2.VpnSettings;
132 import com.android.settings.wfd.WifiDisplaySettings;
133 import com.android.settings.widget.SwitchBar;
134 import com.android.settings.wifi.AdvancedWifiSettings;
135 import com.android.settings.wifi.SavedAccessPointsWifiSettings;
136 import com.android.settings.wifi.WifiSettings;
137 import com.android.settings.wifi.p2p.WifiP2pSettings;
139 import org.xmlpull.v1.XmlPullParser;
140 import org.xmlpull.v1.XmlPullParserException;
142 import java.io.IOException;
143 import java.util.ArrayList;
144 import java.util.List;
145 import java.util.Map;
146 import java.util.Set;
148 public class SettingsActivity extends Activity
149 implements PreferenceManager.OnPreferenceTreeClickListener,
150 PreferenceFragment.OnPreferenceStartFragmentCallback,
151 ButtonBarHandler, FragmentManager.OnBackStackChangedListener,
152 SearchView.OnQueryTextListener, SearchView.OnCloseListener,
153 MenuItem.OnActionExpandListener {
155 private static final String LOG_TAG = "Settings";
157 // Constants for state save/restore
158 private static final String SAVE_KEY_CATEGORIES = ":settings:categories";
159 private static final String SAVE_KEY_SEARCH_MENU_EXPANDED = ":settings:search_menu_expanded";
160 private static final String SAVE_KEY_SEARCH_QUERY = ":settings:search_query";
161 private static final String SAVE_KEY_SHOW_HOME_AS_UP = ":settings:show_home_as_up";
162 private static final String SAVE_KEY_SHOW_SEARCH = ":settings:show_search";
163 private static final String SAVE_KEY_HOME_ACTIVITIES_COUNT = ":settings:home_activities_count";
166 * When starting this activity, the invoking Intent can contain this extra
167 * string to specify which fragment should be initially displayed.
168 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
169 * will call isValidFragment() to confirm that the fragment class name is valid for this
172 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
175 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
176 * this extra can also be specified to supply a Bundle of arguments to pass
177 * to that fragment when it is instantiated during the initial creation
180 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
183 * Fragment "key" argument passed thru {@link #EXTRA_SHOW_FRAGMENT_ARGUMENTS}
185 public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
187 public static final String BACK_STACK_PREFS = ":settings:prefs";
189 // extras that allow any preference activity to be launched as part of a wizard
191 // show Back and Next buttons? takes boolean parameter
192 // Back will then return RESULT_CANCELED and Next RESULT_OK
193 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
195 // add a Skip button?
196 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
198 // specify custom text for the Back or Next buttons, or cause a button to not appear
199 // at all by setting it to null
200 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
201 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
204 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
205 * those extra can also be specify to supply the title or title res id to be shown for
208 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
210 * The package name used to resolve the title resource id.
212 public static final String EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME =
213 ":settings:show_fragment_title_res_package_name";
214 public static final String EXTRA_SHOW_FRAGMENT_TITLE_RESID =
215 ":settings:show_fragment_title_resid";
216 public static final String EXTRA_SHOW_FRAGMENT_AS_SHORTCUT =
217 ":settings:show_fragment_as_shortcut";
219 public static final String EXTRA_SHOW_FRAGMENT_AS_SUBSETTING =
220 ":settings:show_fragment_as_subsetting";
222 private static final String META_DATA_KEY_FRAGMENT_CLASS =
223 "com.android.settings.FRAGMENT_CLASS";
225 private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
227 private static final String EMPTY_QUERY = "";
230 * Settings will search for system activities of this action and add them as a top level
231 * settings tile using the following parameters.
233 * <p>A category must be specified in the meta-data for the activity named
234 * {@link #EXTRA_CATEGORY_KEY}
236 * <p>The title may be defined by meta-data named {@link Utils#META_DATA_PREFERENCE_TITLE}
237 * otherwise the label for the activity will be used.
239 * <p>The icon may be defined by meta-data named {@link Utils#META_DATA_PREFERENCE_ICON}
240 * otherwise the icon for the activity will be used.
242 * <p>A summary my be defined by meta-data named {@link Utils#META_DATA_PREFERENCE_SUMMARY}
244 private static final String EXTRA_SETTINGS_ACTION =
245 "com.android.settings.action.EXTRA_SETTINGS";
248 * The key used to get the category from metadata of activities of action
249 * {@link #EXTRA_SETTINGS_ACTION}
250 * The value must be one of:
251 * <li>com.android.settings.category.wireless</li>
252 * <li>com.android.settings.category.device</li>
253 * <li>com.android.settings.category.personal</li>
254 * <li>com.android.settings.category.system</li>
256 private static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";
258 private static boolean sShowNoHomeNotice = false;
260 private String mFragmentClass;
262 private CharSequence mInitialTitle;
263 private int mInitialTitleResId;
265 private NFCProfileTagCallback mNfcProfileCallback;
267 // Show only these settings for restricted users
268 private int[] SETTINGS_FOR_RESTRICTED = {
269 R.id.wireless_section,
271 R.id.bluetooth_settings,
272 R.id.data_usage_settings,
274 R.id.wireless_settings,
277 R.id.display_and_lights_settings,
278 R.id.lockscreen_settings,
279 R.id.notification_manager,
280 R.id.storage_settings,
281 R.id.application_settings,
282 R.id.battery_settings,
283 R.id.personal_section,
284 R.id.location_settings,
285 R.id.security_settings,
286 R.id.language_settings,
288 R.id.account_settings,
290 R.id.date_time_settings,
292 R.id.accessibility_settings,
296 R.id.privacy_settings_cyanogenmod
299 private static final String[] ENTRY_FRAGMENTS = {
300 WirelessSettings.class.getName(),
301 WifiSettings.class.getName(),
302 AdvancedWifiSettings.class.getName(),
303 SavedAccessPointsWifiSettings.class.getName(),
304 BluetoothSettings.class.getName(),
305 SimSettings.class.getName(),
306 TetherSettings.class.getName(),
307 WifiP2pSettings.class.getName(),
308 VpnSettings.class.getName(),
309 DateTimeSettings.class.getName(),
310 LocalePicker.class.getName(),
311 InputMethodAndLanguageSettings.class.getName(),
312 SpellCheckersSettings.class.getName(),
313 UserDictionaryList.class.getName(),
314 UserDictionarySettings.class.getName(),
315 HomeSettings.class.getName(),
316 DisplaySettings.class.getName(),
317 DeviceInfoSettings.class.getName(),
318 ManageApplications.class.getName(),
319 ManageAssist.class.getName(),
320 ProcessStatsUi.class.getName(),
321 NotificationStation.class.getName(),
322 LocationSettings.class.getName(),
323 SecuritySettings.class.getName(),
324 UsageAccessDetails.class.getName(),
325 PrivacySettings.class.getName(),
326 DeviceAdminSettings.class.getName(),
327 AccessibilitySettings.class.getName(),
328 CaptionPropertiesFragment.class.getName(),
329 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
330 TextToSpeechSettings.class.getName(),
331 StorageSettings.class.getName(),
332 PrivateVolumeForget.class.getName(),
333 PrivateVolumeSettings.class.getName(),
334 PublicVolumeSettings.class.getName(),
335 DevelopmentSettings.class.getName(),
336 AndroidBeam.class.getName(),
337 WifiDisplaySettings.class.getName(),
338 PowerUsageSummary.class.getName(),
339 AccountSyncSettings.class.getName(),
340 AccountSettings.class.getName(),
341 CryptKeeperSettings.class.getName(),
342 DataUsageSummary.class.getName(),
343 DreamSettings.class.getName(),
344 UserSettings.class.getName(),
345 NotificationAccessSettings.class.getName(),
346 ZenAccessSettings.class.getName(),
347 PrintSettingsFragment.class.getName(),
348 PrintJobSettingsFragment.class.getName(),
349 TrustedCredentialsSettings.class.getName(),
350 PaymentSettings.class.getName(),
351 KeyboardLayoutPickerFragment.class.getName(),
352 ZenModeSettings.class.getName(),
353 SoundSettings.class.getName(),
354 ChooseLockPassword.ChooseLockPasswordFragment.class.getName(),
355 ChooseLockPattern.ChooseLockPatternFragment.class.getName(),
356 InstalledAppDetails.class.getName(),
357 AppNotificationSettings.class.getName(),
358 OtherSoundSettings.class.getName(),
359 ApnSettings.class.getName(),
360 WifiCallingSettings.class.getName(),
361 ZenModePrioritySettings.class.getName(),
362 ZenModeAutomationSettings.class.getName(),
363 ZenModeScheduleRuleSettings.class.getName(),
364 ZenModeEventRuleSettings.class.getName(),
365 ZenModeExternalRuleSettings.class.getName(),
366 ProcessStatsUi.class.getName(),
367 PowerUsageDetail.class.getName(),
368 ProcessStatsSummary.class.getName(),
369 DrawOverlayDetails.class.getName(),
370 WriteSettingsDetails.class.getName(),
371 LiveDisplay.class.getName(),
372 com.android.settings.cyanogenmod.DisplayRotation.class.getName(),
373 com.android.settings.cyanogenmod.PrivacySettings.class.getName(),
374 BlacklistSettings.class.getName(),
375 ProfilesSettings.class.getName(),
376 ContributorsCloudFragment.class.getName(),
377 NotificationManagerSettings.class.getName()
381 private static final String[] LIKE_SHORTCUT_INTENT_ACTION_ARRAY = {
382 "android.settings.APPLICATION_DETAILS_SETTINGS"
385 private SharedPreferences mDevelopmentPreferences;
386 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
388 private boolean mBatteryPresent = true;
389 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
391 public void onReceive(Context context, Intent intent) {
392 String action = intent.getAction();
393 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
394 boolean batteryPresent = Utils.isBatteryPresent(intent);
396 if (mBatteryPresent != batteryPresent) {
397 mBatteryPresent = batteryPresent;
398 invalidateCategories(true);
404 private final DynamicIndexableContentMonitor mDynamicIndexableContentMonitor =
405 new DynamicIndexableContentMonitor();
407 private ActionBar mActionBar;
408 private SwitchBar mSwitchBar;
410 private Button mNextButton;
412 private boolean mDisplayHomeAsUpEnabled;
413 private boolean mDisplaySearch;
415 private boolean mIsShowingDashboard;
416 private boolean mIsShortcut;
418 private ViewGroup mContent;
420 private SearchView mSearchView;
421 private MenuItem mSearchMenuItem;
422 private boolean mSearchMenuItemExpanded = false;
423 private SearchResultsSummary mSearchResultsFragment;
424 private String mSearchQuery;
427 private ArrayList<DashboardCategory> mCategories = new ArrayList<DashboardCategory>();
429 private static final String MSG_DATA_FORCE_REFRESH = "msg_data_force_refresh";
430 private static final int MSG_BUILD_CATEGORIES = 1;
431 private Handler mHandler = new Handler() {
433 public void handleMessage(Message msg) {
435 case MSG_BUILD_CATEGORIES: {
436 final boolean forceRefresh = msg.getData().getBoolean(MSG_DATA_FORCE_REFRESH);
438 buildDashboardCategories(mCategories);
445 private boolean mNeedToRevertToInitialFragment = false;
446 private int mHomeActivitiesCount = 1;
448 private Intent mResultIntentData;
450 public SwitchBar getSwitchBar() {
454 public List<DashboardCategory> getDashboardCategories(boolean forceRefresh) {
455 if (forceRefresh || mCategories.size() == 0) {
456 buildDashboardCategories(mCategories);
462 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
463 // Override the fragment title for Wallpaper settings
464 int titleRes = pref.getTitleRes();
465 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
466 titleRes = R.string.wallpaper_settings_fragment_title;
467 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
468 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
469 if (UserManager.get(this).isLinkedUser()) {
470 titleRes = R.string.profile_info_settings_title;
472 titleRes = R.string.user_info_settings_title;
475 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
481 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
485 private void invalidateCategories(boolean forceRefresh) {
486 if (!mHandler.hasMessages(MSG_BUILD_CATEGORIES)) {
487 Message msg = new Message();
488 msg.what = MSG_BUILD_CATEGORIES;
489 msg.getData().putBoolean(MSG_DATA_FORCE_REFRESH, forceRefresh);
494 public void onConfigurationChanged(Configuration newConfig) {
495 super.onConfigurationChanged(newConfig);
496 Index.getInstance(this).update();
500 protected void onStart() {
503 if (mNeedToRevertToInitialFragment) {
504 revertToInitialFragment();
509 public boolean onCreateOptionsMenu(Menu menu) {
510 if (!mDisplaySearch) {
514 MenuInflater inflater = getMenuInflater();
515 inflater.inflate(R.menu.options_menu, menu);
517 // Cache the search query (can be overriden by the OnQueryTextListener)
518 final String query = mSearchQuery;
520 mSearchMenuItem = menu.findItem(R.id.search);
521 mSearchView = (SearchView) mSearchMenuItem.getActionView();
523 if (mSearchMenuItem == null || mSearchView == null) {
527 if (mSearchResultsFragment != null) {
528 mSearchResultsFragment.setSearchView(mSearchView);
531 mSearchMenuItem.setOnActionExpandListener(this);
532 mSearchView.setOnQueryTextListener(this);
533 mSearchView.setOnCloseListener(this);
535 if (mSearchMenuItemExpanded) {
536 mSearchMenuItem.expandActionView();
538 mSearchView.setQuery(query, true /* submit */);
543 private static boolean isShortCutIntent(final Intent intent) {
544 Set<String> categories = intent.getCategories();
545 return (categories != null) && categories.contains("com.android.settings.SHORTCUT");
548 private static boolean isLikeShortCutIntent(final Intent intent) {
549 String action = intent.getAction();
550 if (action == null) {
553 for (int i = 0; i < LIKE_SHORTCUT_INTENT_ACTION_ARRAY.length; i++) {
554 if (LIKE_SHORTCUT_INTENT_ACTION_ARRAY[i].equals(action)) return true;
560 protected void onCreate(Bundle savedState) {
561 super.onCreate(savedState);
563 // Should happen before any call to getIntent()
566 final Intent intent = getIntent();
567 if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
568 getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
571 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
572 Context.MODE_PRIVATE);
574 // Getting Intent properties can only be done after the super.onCreate(...)
575 final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
577 mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) ||
578 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false);
580 final ComponentName cn = intent.getComponent();
581 final String className = cn.getClassName();
583 mIsShowingDashboard = className.equals(Settings.class.getName());
585 // This is a "Sub Settings" when:
586 // - this is a real SubSettings
587 // - or :settings:show_fragment_as_subsetting is passed to the Intent
588 final boolean isSubSettings = this instanceof SubSettings ||
589 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
591 // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content insets
593 // Check also that we are not a Theme Dialog as we don't want to override them
594 final int themeResId = getThemeResId();
595 if (themeResId != R.style.Theme_DialogWhenLarge &&
596 themeResId != R.style.Theme_SubSettingsDialogWhenLarge) {
597 setTheme(R.style.Theme_SubSettings);
601 setContentView(mIsShowingDashboard ?
602 R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
604 mContent = (ViewGroup) findViewById(R.id.main_content);
606 getFragmentManager().addOnBackStackChangedListener(this);
608 if (mIsShowingDashboard) {
609 // Run the Index update only if we have some space
610 if (!Utils.isLowStorage(this)) {
611 Index.getInstance(getApplicationContext()).update();
613 Log.w(LOG_TAG, "Cannot update the Indexer as we are running low on storage space!");
617 if (savedState != null) {
618 // We are restarting from a previous saved state; used that to initialize, instead
619 // of starting fresh.
620 mSearchMenuItemExpanded = savedState.getBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED);
621 mSearchQuery = savedState.getString(SAVE_KEY_SEARCH_QUERY);
623 setTitleFromIntent(intent);
625 ArrayList<DashboardCategory> categories =
626 savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);
627 if (categories != null) {
629 mCategories.addAll(categories);
630 setTitleFromBackStack();
633 mDisplayHomeAsUpEnabled = savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP);
634 mDisplaySearch = savedState.getBoolean(SAVE_KEY_SHOW_SEARCH);
635 mHomeActivitiesCount = savedState.getInt(SAVE_KEY_HOME_ACTIVITIES_COUNT,
636 1 /* one home activity by default */);
638 if (!mIsShowingDashboard) {
639 mDisplaySearch = false;
640 // UP will be shown only if it is a sub settings
642 mDisplayHomeAsUpEnabled = isSubSettings;
643 } else if (isSubSettings) {
644 mDisplayHomeAsUpEnabled = true;
646 mDisplayHomeAsUpEnabled = false;
648 setTitleFromIntent(intent);
650 Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
651 switchToFragment(initialFragmentName, initialArguments, true, false,
652 mInitialTitleResId, mInitialTitle, false);
654 // No UP affordance if we are displaying the main Dashboard
655 mDisplayHomeAsUpEnabled = false;
656 // Show Search affordance
657 mDisplaySearch = true;
658 mInitialTitleResId = R.string.dashboard_title;
659 switchToFragment(DashboardSummary.class.getName(), null, false, false,
660 mInitialTitleResId, mInitialTitle, false);
664 mActionBar = getActionBar();
665 if (mActionBar != null) {
666 mActionBar.setDisplayHomeAsUpEnabled(mDisplayHomeAsUpEnabled);
667 mActionBar.setHomeButtonEnabled(mDisplayHomeAsUpEnabled);
669 mSwitchBar = (SwitchBar) findViewById(R.id.switch_bar);
671 // see if we should show Back/Next buttons
672 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
674 View buttonBar = findViewById(R.id.button_bar);
675 if (buttonBar != null) {
676 buttonBar.setVisibility(View.VISIBLE);
678 Button backButton = (Button)findViewById(R.id.back_button);
679 backButton.setOnClickListener(new OnClickListener() {
680 public void onClick(View v) {
681 setResult(RESULT_CANCELED, getResultIntentData());
685 Button skipButton = (Button)findViewById(R.id.skip_button);
686 skipButton.setOnClickListener(new OnClickListener() {
687 public void onClick(View v) {
688 setResult(RESULT_OK, getResultIntentData());
692 mNextButton = (Button)findViewById(R.id.next_button);
693 mNextButton.setOnClickListener(new OnClickListener() {
694 public void onClick(View v) {
695 setResult(RESULT_OK, getResultIntentData());
700 // set our various button parameters
701 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
702 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
703 if (TextUtils.isEmpty(buttonText)) {
704 mNextButton.setVisibility(View.GONE);
707 mNextButton.setText(buttonText);
710 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
711 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
712 if (TextUtils.isEmpty(buttonText)) {
713 backButton.setVisibility(View.GONE);
716 backButton.setText(buttonText);
719 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
720 skipButton.setVisibility(View.VISIBLE);
725 mHomeActivitiesCount = getHomeActivitiesCount();
728 private int getHomeActivitiesCount() {
729 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
730 getPackageManager().getHomeActivities(homeApps);
731 return homeApps.size();
734 private void setTitleFromIntent(Intent intent) {
735 final int initialTitleResId = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, -1);
736 if (initialTitleResId > 0) {
737 mInitialTitle = null;
738 mInitialTitleResId = initialTitleResId;
740 final String initialTitleResPackageName = intent.getStringExtra(
741 EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME);
742 if (initialTitleResPackageName != null) {
744 Context authContext = createPackageContextAsUser(initialTitleResPackageName,
745 0 /* flags */, new UserHandle(UserHandle.myUserId()));
746 mInitialTitle = authContext.getResources().getText(mInitialTitleResId);
747 setTitle(mInitialTitle);
748 mInitialTitleResId = -1;
750 } catch (NameNotFoundException e) {
751 Log.w(LOG_TAG, "Could not find package" + initialTitleResPackageName);
754 setTitle(mInitialTitleResId);
757 mInitialTitleResId = -1;
758 final String initialTitle = intent.getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
759 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
760 setTitle(mInitialTitle);
765 public void onBackStackChanged() {
766 setTitleFromBackStack();
769 private int setTitleFromBackStack() {
770 final int count = getFragmentManager().getBackStackEntryCount();
773 if (mInitialTitleResId > 0) {
774 setTitle(mInitialTitleResId);
776 setTitle(mInitialTitle);
781 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1);
782 setTitleFromBackStackEntry(bse);
787 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
788 final CharSequence title;
789 final int titleRes = bse.getBreadCrumbTitleRes();
791 title = getText(titleRes);
793 title = bse.getBreadCrumbTitle();
801 protected void onSaveInstanceState(Bundle outState) {
802 super.onSaveInstanceState(outState);
804 if (mCategories.size() > 0) {
805 outState.putParcelableArrayList(SAVE_KEY_CATEGORIES, mCategories);
808 outState.putBoolean(SAVE_KEY_SHOW_HOME_AS_UP, mDisplayHomeAsUpEnabled);
809 outState.putBoolean(SAVE_KEY_SHOW_SEARCH, mDisplaySearch);
811 if (mDisplaySearch) {
812 // The option menus are created if the ActionBar is visible and they are also created
813 // asynchronously. If you launch Settings with an Intent action like
814 // android.intent.action.POWER_USAGE_SUMMARY and at the same time your device is locked
815 // thru a LockScreen, onCreateOptionsMenu() is not yet called and references to the search
816 // menu item and search view are null.
817 boolean isExpanded = (mSearchMenuItem != null) && mSearchMenuItem.isActionViewExpanded();
818 outState.putBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED, isExpanded);
820 String query = (mSearchView != null) ? mSearchView.getQuery().toString() : EMPTY_QUERY;
821 outState.putString(SAVE_KEY_SEARCH_QUERY, query);
824 outState.putInt(SAVE_KEY_HOME_ACTIVITIES_COUNT, mHomeActivitiesCount);
828 public void onResume() {
830 if (mIsShowingDashboard) {
831 MetricsLogger.visible(this, MetricsLogger.MAIN_SETTINGS);
834 final int newHomeActivityCount = getHomeActivitiesCount();
835 if (newHomeActivityCount != mHomeActivitiesCount) {
836 mHomeActivitiesCount = newHomeActivityCount;
837 invalidateCategories(true);
840 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
842 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
843 invalidateCategories(true);
846 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
847 mDevelopmentPreferencesListener);
849 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
851 mDynamicIndexableContentMonitor.register(this);
853 if(mDisplaySearch && !TextUtils.isEmpty(mSearchQuery)) {
854 onQueryTextSubmit(mSearchQuery);
859 public void onPause() {
861 if (mIsShowingDashboard) {
862 MetricsLogger.hidden(this, MetricsLogger.MAIN_SETTINGS);
864 unregisterReceiver(mBatteryInfoReceiver);
865 mDynamicIndexableContentMonitor.unregister();
869 public void onDestroy() {
872 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
873 mDevelopmentPreferencesListener);
874 mDevelopmentPreferencesListener = null;
877 protected boolean isValidFragment(String fragmentName) {
878 // Almost all fragments are wrapped in this,
879 // except for a few that have their own activities.
880 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
881 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
887 public Intent getIntent() {
888 Intent superIntent = super.getIntent();
889 String startingFragment = getStartingFragmentClass(superIntent);
890 // This is called from super.onCreate, isMultiPane() is not yet reliable
891 // Do not use onIsHidingHeaders either, which relies itself on this method
892 if (startingFragment != null) {
893 Intent modIntent = new Intent(superIntent);
894 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
895 Bundle args = superIntent.getExtras();
897 args = new Bundle(args);
901 args.putParcelable("intent", superIntent);
902 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
909 * Checks if the component name in the intent is different from the Settings class and
910 * returns the class name to load as a fragment.
912 private String getStartingFragmentClass(Intent intent) {
913 if (mFragmentClass != null) return mFragmentClass;
915 String intentClass = intent.getComponent().getClassName();
916 if (intentClass.equals(getClass().getName())) return null;
918 if ("com.android.settings.ManageApplications".equals(intentClass)
919 || "com.android.settings.RunningServices".equals(intentClass)
920 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
921 // Old names of manage apps.
922 intentClass = com.android.settings.applications.ManageApplications.class.getName();
929 * Start a new fragment containing a preference panel. If the preferences
930 * are being displayed in multi-pane mode, the given fragment class will
931 * be instantiated and placed in the appropriate pane. If running in
932 * single-pane mode, a new activity will be launched in which to show the
935 * @param fragmentClass Full name of the class implementing the fragment.
936 * @param args Any desired arguments to supply to the fragment.
937 * @param titleRes Optional resource identifier of the title of this
939 * @param titleText Optional text of the title of this fragment.
940 * @param resultTo Optional fragment that result data should be sent to.
941 * If non-null, resultTo.onActivityResult() will be called when this
942 * preference panel is done. The launched panel must use
943 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
944 * @param resultRequestCode If resultTo is non-null, this is the caller's
945 * request code to be received with the result.
947 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
948 CharSequence titleText, Fragment resultTo, int resultRequestCode) {
951 if (titleText != null) {
952 title = titleText.toString();
954 // There not much we can do in that case
958 Utils.startWithFragment(this, fragmentClass, args, resultTo, resultRequestCode,
959 titleRes, title, mIsShortcut);
963 * Start a new fragment in a new activity containing a preference panel for a given user. If the
964 * preferences are being displayed in multi-pane mode, the given fragment class will be
965 * instantiated and placed in the appropriate pane. If running in single-pane mode, a new
966 * activity will be launched in which to show the fragment.
968 * @param fragmentClass Full name of the class implementing the fragment.
969 * @param args Any desired arguments to supply to the fragment.
970 * @param titleRes Optional resource identifier of the title of this fragment.
971 * @param titleText Optional text of the title of this fragment.
972 * @param userHandle The user for which the panel has to be started.
974 public void startPreferencePanelAsUser(String fragmentClass, Bundle args, int titleRes,
975 CharSequence titleText, UserHandle userHandle) {
976 // This is a workaround.
978 // Calling startWithFragmentAsUser() without specifying FLAG_ACTIVITY_NEW_TASK to the intent
979 // starting the fragment could cause a native stack corruption. See b/17523189. However,
980 // adding that flag and start the preference panel with the same UserHandler will make it
981 // impossible to use back button to return to the previous screen. See b/20042570.
983 // We work around this issue by adding FLAG_ACTIVITY_NEW_TASK to the intent, while doing
984 // another check here to call startPreferencePanel() instead of startWithFragmentAsUser()
985 // when we're calling it as the same user.
986 if (userHandle.getIdentifier() == UserHandle.myUserId()) {
987 startPreferencePanel(fragmentClass, args, titleRes, titleText, null, 0);
991 if (titleText != null) {
992 title = titleText.toString();
994 // There not much we can do in that case
998 Utils.startWithFragmentAsUser(this, fragmentClass, args,
999 titleRes, title, mIsShortcut, userHandle);
1004 * Called by a preference panel fragment to finish itself.
1006 * @param caller The fragment that is asking to be finished.
1007 * @param resultCode Optional result code to send back to the original
1008 * launching fragment.
1009 * @param resultData Optional result data to send back to the original
1010 * launching fragment.
1012 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
1013 setResult(resultCode, resultData);
1018 * Start a new fragment.
1020 * @param fragment The fragment to start
1021 * @param push If true, the current fragment will be pushed onto the back stack. If false,
1022 * the current fragment will be replaced.
1024 public void startPreferenceFragment(Fragment fragment, boolean push) {
1025 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1026 transaction.replace(R.id.main_content, fragment);
1028 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1029 transaction.addToBackStack(BACK_STACK_PREFS);
1031 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
1033 transaction.commitAllowingStateLoss();
1037 * Switch to a specific Fragment with taking care of validation, Title and BackStack
1039 private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
1040 boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition) {
1041 if (validate && !isValidFragment(fragmentName)) {
1042 throw new IllegalArgumentException("Invalid fragment for this activity: "
1045 Fragment f = Fragment.instantiate(this, fragmentName, args);
1046 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1047 transaction.replace(R.id.main_content, f);
1048 if (withTransition) {
1049 TransitionManager.beginDelayedTransition(mContent);
1051 if (addToBackStack) {
1052 transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
1054 if (titleResId > 0) {
1055 transaction.setBreadCrumbTitle(titleResId);
1056 } else if (title != null) {
1057 transaction.setBreadCrumbTitle(title);
1059 transaction.commitAllowingStateLoss();
1060 getFragmentManager().executePendingTransactions();
1065 * Called when the activity needs its list of categories/tiles built.
1067 * @param categories The list in which to place the tiles categories.
1069 private void buildDashboardCategories(List<DashboardCategory> categories) {
1071 loadCategoriesFromResource(R.xml.dashboard_categories, categories, this);
1072 updateTilesList(categories);
1076 * Parse the given XML file as a categories description, adding each
1077 * parsed categories and tiles into the target list.
1079 * @param resid The XML resource to load and parse.
1080 * @param target The list in which the parsed categories and tiles should be placed.
1082 public static void loadCategoriesFromResource(int resid, List<DashboardCategory> target,
1084 XmlResourceParser parser = null;
1086 parser = context.getResources().getXml(resid);
1087 AttributeSet attrs = Xml.asAttributeSet(parser);
1090 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1091 && type != XmlPullParser.START_TAG) {
1092 // Parse next until start tag is found
1095 String nodeName = parser.getName();
1096 if (!"dashboard-categories".equals(nodeName)) {
1097 throw new RuntimeException(
1098 "XML document must start with <preference-categories> tag; found"
1099 + nodeName + " at " + parser.getPositionDescription());
1102 Bundle curBundle = null;
1104 final int outerDepth = parser.getDepth();
1105 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1106 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1107 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1111 nodeName = parser.getName();
1112 if ("dashboard-category".equals(nodeName)) {
1113 DashboardCategory category = new DashboardCategory();
1115 TypedArray sa = context.obtainStyledAttributes(
1116 attrs, com.android.internal.R.styleable.PreferenceHeader);
1117 category.id = sa.getResourceId(
1118 com.android.internal.R.styleable.PreferenceHeader_id,
1119 (int)DashboardCategory.CAT_ID_UNDEFINED);
1121 TypedValue tv = sa.peekValue(
1122 com.android.internal.R.styleable.PreferenceHeader_title);
1123 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1124 if (tv.resourceId != 0) {
1125 category.titleRes = tv.resourceId;
1127 category.title = tv.string;
1131 sa = context.obtainStyledAttributes(attrs,
1132 com.android.internal.R.styleable.Preference);
1134 com.android.internal.R.styleable.Preference_key);
1135 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1136 if (tv.resourceId != 0) {
1137 category.key = context.getString(tv.resourceId);
1139 category.key = tv.string.toString();
1144 final int innerDepth = parser.getDepth();
1145 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1146 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
1147 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1151 String innerNodeName = parser.getName();
1152 if (innerNodeName.equals("dashboard-tile")) {
1153 DashboardTile tile = new DashboardTile();
1155 sa = context.obtainStyledAttributes(
1156 attrs, com.android.internal.R.styleable.PreferenceHeader);
1157 tile.id = sa.getResourceId(
1158 com.android.internal.R.styleable.PreferenceHeader_id,
1159 (int)TILE_ID_UNDEFINED);
1161 com.android.internal.R.styleable.PreferenceHeader_title);
1162 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1163 if (tv.resourceId != 0) {
1164 tile.titleRes = tv.resourceId;
1166 tile.title = tv.string;
1170 com.android.internal.R.styleable.PreferenceHeader_summary);
1171 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1172 if (tv.resourceId != 0) {
1173 tile.summaryRes = tv.resourceId;
1175 tile.summary = tv.string;
1178 tile.iconRes = sa.getResourceId(
1179 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
1180 tile.fragment = sa.getString(
1181 com.android.internal.R.styleable.PreferenceHeader_fragment);
1184 sa = context.obtainStyledAttributes(attrs, R.styleable.DashboardTile);
1185 tile.switchControl = sa.getString(
1186 R.styleable.DashboardTile_switchClass);
1189 if (curBundle == null) {
1190 curBundle = new Bundle();
1193 final int innerDepth2 = parser.getDepth();
1194 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1195 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth2)) {
1196 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1200 String innerNodeName2 = parser.getName();
1201 if (innerNodeName2.equals("extra")) {
1202 context.getResources().parseBundleExtra("extra", attrs,
1204 XmlUtils.skipCurrentTag(parser);
1206 } else if (innerNodeName2.equals("intent")) {
1207 tile.intent = Intent.parseIntent(context.getResources(), parser,
1211 XmlUtils.skipCurrentTag(parser);
1215 if (curBundle.size() > 0) {
1216 tile.fragmentArguments = curBundle;
1220 // Show the SIM Cards setting if there are more than 2 SIMs installed.
1221 if(tile.id != R.id.sim_settings || Utils.showSimCardTile(context)){
1222 category.addTile(tile);
1225 } else if (innerNodeName.equals("external-tiles")) {
1226 category.externalIndex = category.getTilesCount();
1228 XmlUtils.skipCurrentTag(parser);
1232 target.add(category);
1234 XmlUtils.skipCurrentTag(parser);
1238 } catch (XmlPullParserException e) {
1239 throw new RuntimeException("Error parsing categories", e);
1240 } catch (IOException e) {
1241 throw new RuntimeException("Error parsing categories", e);
1243 if (parser != null) parser.close();
1247 private void updateTilesList(List<DashboardCategory> target) {
1248 final boolean showDev = mDevelopmentPreferences.getBoolean(
1249 DevelopmentSettings.PREF_SHOW,
1250 android.os.Build.TYPE.equals("eng"));
1252 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1254 final int size = target.size();
1255 for (int i = 0; i < size; i++) {
1257 DashboardCategory category = target.get(i);
1259 // Ids are integers, so downcasting is ok
1260 int id = (int) category.id;
1261 int n = category.getTilesCount() - 1;
1264 DashboardTile tile = category.getTile(n);
1265 boolean removeTile = false;
1267 if (id == R.id.operator_settings || id == R.id.manufacturer_settings
1268 || id == R.id.device_specific_gesture_settings
1269 || id == R.id.oclick) {
1270 if (!Utils.updateTileToSpecificActivityFromMetaDataOrRemove(this, tile)) {
1273 } else if (id == R.id.wifi_settings) {
1274 // Remove WiFi Settings if WiFi service is not available.
1275 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1278 } else if (id == R.id.bluetooth_settings) {
1279 // Remove Bluetooth Settings if Bluetooth service is not available.
1280 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1283 } else if (id == R.id.mobile_networks) {
1284 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
1287 } else if (id == R.id.data_usage_settings) {
1288 // Remove data usage when kernel module not enabled
1289 if (!Utils.isBandwidthControlEnabled()) {
1292 } else if (id == R.id.battery_settings) {
1293 // Remove battery settings when battery is not available. (e.g. TV)
1295 if (!mBatteryPresent) {
1298 } else if (id == R.id.home_settings) {
1299 if (!updateHomeSettingTiles(tile)) {
1302 } else if (id == R.id.user_settings) {
1303 boolean hasMultipleUsers =
1304 ((UserManager) getSystemService(Context.USER_SERVICE))
1305 .getUserCount() > 1;
1306 if (!UserHandle.MU_ENABLED
1307 || !UserManager.supportsMultipleUsers()
1308 || Utils.isMonkeyRunning()) {
1311 } else if (id == R.id.print_settings) {
1312 boolean hasPrintingSupport = getPackageManager().hasSystemFeature(
1313 PackageManager.FEATURE_PRINTING);
1314 if (!hasPrintingSupport) {
1317 } else if (id == R.id.development_settings) {
1318 if (!showDev || um.hasUserRestriction(
1319 UserManager.DISALLOW_DEBUGGING_FEATURES)) {
1322 } else if (id == R.id.button_settings) {
1323 boolean hasDeviceKeys = getResources().getInteger(
1324 com.android.internal.R.integer.config_deviceHardwareKeys) != 0;
1325 if (!hasDeviceKeys) {
1330 if (UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1331 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1335 if (removeTile && n < category.getTilesCount()) {
1336 category.removeTile(n);
1341 addExternalTiles(target);
1344 private void addExternalTiles(List<DashboardCategory> target) {
1345 Map<Pair<String, String>, DashboardTile> addedCache =
1346 new ArrayMap<Pair<String, String>, DashboardTile>();
1347 UserManager userManager = UserManager.get(this);
1348 for (UserHandle user : userManager.getUserProfiles()) {
1349 addExternalTiles(target, user, addedCache);
1353 private void addExternalTiles(List<DashboardCategory> target, UserHandle user,
1354 Map<Pair<String, String>, DashboardTile> addedCache) {
1355 PackageManager pm = getPackageManager();
1356 Intent intent = new Intent(EXTRA_SETTINGS_ACTION);
1357 List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
1358 PackageManager.GET_META_DATA, user.getIdentifier());
1359 for (ResolveInfo resolved : results) {
1360 if (!resolved.system) {
1361 // Do not allow any app to add to settings, only system ones.
1364 ActivityInfo activityInfo = resolved.activityInfo;
1365 Bundle metaData = activityInfo.metaData;
1366 if ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY)) {
1367 Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for action "
1368 + EXTRA_SETTINGS_ACTION + " missing metadata " +
1369 (metaData == null ? "" : EXTRA_CATEGORY_KEY));
1372 String categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
1373 DashboardCategory category = getCategory(target, categoryKey);
1374 if (category == null) {
1375 Log.w(LOG_TAG, "Activity " + resolved.activityInfo.name + " has unknown "
1376 + "category key " + categoryKey);
1379 Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,
1381 DashboardTile tile = addedCache.get(key);
1383 tile = new DashboardTile();
1384 tile.intent = new Intent().setClassName(
1385 activityInfo.packageName, activityInfo.name);
1386 Utils.updateTileToSpecificActivityFromMetaDataOrRemove(this, tile);
1388 if (category.externalIndex == -1
1389 || category.externalIndex > category.getTilesCount()) {
1390 // If no location for external tiles has been specified for this category,
1391 // then just put them at the end.
1392 category.addTile(tile);
1394 category.addTile(category.externalIndex, tile);
1396 addedCache.put(key, tile);
1398 tile.userHandle.add(user);
1402 private DashboardCategory getCategory(List<DashboardCategory> target, String categoryKey) {
1403 for (DashboardCategory category : target) {
1404 if (categoryKey.equals(category.key)) {
1411 private boolean updateHomeSettingTiles(DashboardTile tile) {
1412 // Once we decide to show Home settings, keep showing it forever
1413 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1414 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1419 mHomeActivitiesCount = getHomeActivitiesCount();
1420 if (mHomeActivitiesCount < 2) {
1421 // When there's only one available home app, omit this settings
1422 // category entirely at the top level UI. If the user just
1423 // uninstalled the penultimate home app candidiate, we also
1424 // now tell them about why they aren't seeing 'Home' in the list.
1425 if (sShowNoHomeNotice) {
1426 sShowNoHomeNotice = false;
1427 NoHomeDialogFragment.show(this);
1431 // Okay, we're allowing the Home settings category. Tell it, when
1432 // invoked via this front door, that we'll need to be told about the
1433 // case when the user uninstalls all but one home app.
1434 if (tile.fragmentArguments == null) {
1435 tile.fragmentArguments = new Bundle();
1437 tile.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1439 } catch (Exception e) {
1440 // Can't look up the home activity; bail on configuring the icon
1441 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1444 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1448 private void getMetaData() {
1450 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1451 PackageManager.GET_META_DATA);
1452 if (ai == null || ai.metaData == null) return;
1453 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1454 } catch (NameNotFoundException nnfe) {
1456 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1460 // give subclasses access to the Next button
1461 public boolean hasNextButton() {
1462 return mNextButton != null;
1465 public Button getNextButton() {
1470 public boolean shouldUpRecreateTask(Intent targetIntent) {
1471 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1474 public static void requestHomeNotice() {
1475 sShowNoHomeNotice = true;
1479 public boolean onQueryTextSubmit(String query) {
1480 switchToSearchResultsFragmentIfNeeded();
1481 mSearchQuery = query;
1482 return mSearchResultsFragment.onQueryTextSubmit(query);
1486 public boolean onQueryTextChange(String newText) {
1487 mSearchQuery = newText;
1488 if (mSearchResultsFragment == null) {
1491 return mSearchResultsFragment.onQueryTextChange(newText);
1495 public boolean onClose() {
1500 public boolean onMenuItemActionExpand(MenuItem item) {
1501 if (item.getItemId() == mSearchMenuItem.getItemId()) {
1502 switchToSearchResultsFragmentIfNeeded();
1508 public boolean onMenuItemActionCollapse(MenuItem item) {
1509 if (item.getItemId() == mSearchMenuItem.getItemId()) {
1510 if (mSearchMenuItemExpanded) {
1511 revertToInitialFragment();
1517 private void switchToSearchResultsFragmentIfNeeded() {
1518 if (mSearchResultsFragment != null) {
1521 Fragment current = getFragmentManager().findFragmentById(R.id.main_content);
1522 if (current != null && current instanceof SearchResultsSummary) {
1523 mSearchResultsFragment = (SearchResultsSummary) current;
1525 mSearchResultsFragment = (SearchResultsSummary) switchToFragment(
1526 SearchResultsSummary.class.getName(), null, false, true,
1527 R.string.search_results_title, null, true);
1529 mSearchResultsFragment.setSearchView(mSearchView);
1530 mSearchMenuItemExpanded = true;
1533 public void needToRevertToInitialFragment() {
1534 mNeedToRevertToInitialFragment = true;
1537 private void revertToInitialFragment() {
1538 mNeedToRevertToInitialFragment = false;
1539 mSearchResultsFragment = null;
1540 mSearchMenuItemExpanded = false;
1541 getFragmentManager().popBackStackImmediate(SettingsActivity.BACK_STACK_PREFS,
1542 FragmentManager.POP_BACK_STACK_INCLUSIVE);
1543 if (mSearchMenuItem != null) {
1544 mSearchMenuItem.collapseActionView();
1548 public Intent getResultIntentData() {
1549 return mResultIntentData;
1552 public void setResultIntentData(Intent resultIntentData) {
1553 mResultIntentData = resultIntentData;
1556 public void setNfcProfileCallback(NFCProfileTagCallback callback) {
1557 mNfcProfileCallback = callback;
1561 protected void onNewIntent(Intent intent) {
1562 if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
1563 Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
1564 if (mNfcProfileCallback != null) {
1565 mNfcProfileCallback.onTagRead(detectedTag);
1569 super.onNewIntent(intent);