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,
300 private static final String[] ENTRY_FRAGMENTS = {
301 WirelessSettings.class.getName(),
302 WifiSettings.class.getName(),
303 AdvancedWifiSettings.class.getName(),
304 SavedAccessPointsWifiSettings.class.getName(),
305 BluetoothSettings.class.getName(),
306 SimSettings.class.getName(),
307 TetherSettings.class.getName(),
308 WifiP2pSettings.class.getName(),
309 VpnSettings.class.getName(),
310 DateTimeSettings.class.getName(),
311 LocalePicker.class.getName(),
312 InputMethodAndLanguageSettings.class.getName(),
313 SpellCheckersSettings.class.getName(),
314 UserDictionaryList.class.getName(),
315 UserDictionarySettings.class.getName(),
316 HomeSettings.class.getName(),
317 DisplaySettings.class.getName(),
318 DeviceInfoSettings.class.getName(),
319 ManageApplications.class.getName(),
320 ManageAssist.class.getName(),
321 ProcessStatsUi.class.getName(),
322 NotificationStation.class.getName(),
323 LocationSettings.class.getName(),
324 SecuritySettings.class.getName(),
325 UsageAccessDetails.class.getName(),
326 PrivacySettings.class.getName(),
327 DeviceAdminSettings.class.getName(),
328 AccessibilitySettings.class.getName(),
329 CaptionPropertiesFragment.class.getName(),
330 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
331 TextToSpeechSettings.class.getName(),
332 StorageSettings.class.getName(),
333 PrivateVolumeForget.class.getName(),
334 PrivateVolumeSettings.class.getName(),
335 PublicVolumeSettings.class.getName(),
336 DevelopmentSettings.class.getName(),
337 AndroidBeam.class.getName(),
338 WifiDisplaySettings.class.getName(),
339 PowerUsageSummary.class.getName(),
340 AccountSyncSettings.class.getName(),
341 AccountSettings.class.getName(),
342 CryptKeeperSettings.class.getName(),
343 DataUsageSummary.class.getName(),
344 DreamSettings.class.getName(),
345 UserSettings.class.getName(),
346 NotificationAccessSettings.class.getName(),
347 ZenAccessSettings.class.getName(),
348 PrintSettingsFragment.class.getName(),
349 PrintJobSettingsFragment.class.getName(),
350 TrustedCredentialsSettings.class.getName(),
351 PaymentSettings.class.getName(),
352 KeyboardLayoutPickerFragment.class.getName(),
353 ZenModeSettings.class.getName(),
354 SoundSettings.class.getName(),
355 ChooseLockPassword.ChooseLockPasswordFragment.class.getName(),
356 ChooseLockPattern.ChooseLockPatternFragment.class.getName(),
357 InstalledAppDetails.class.getName(),
358 AppNotificationSettings.class.getName(),
359 OtherSoundSettings.class.getName(),
360 ApnSettings.class.getName(),
361 WifiCallingSettings.class.getName(),
362 ZenModePrioritySettings.class.getName(),
363 ZenModeAutomationSettings.class.getName(),
364 ZenModeScheduleRuleSettings.class.getName(),
365 ZenModeEventRuleSettings.class.getName(),
366 ZenModeExternalRuleSettings.class.getName(),
367 ProcessStatsUi.class.getName(),
368 PowerUsageDetail.class.getName(),
369 ProcessStatsSummary.class.getName(),
370 DrawOverlayDetails.class.getName(),
371 WriteSettingsDetails.class.getName(),
372 LiveDisplay.class.getName(),
373 com.android.settings.cyanogenmod.DisplayRotation.class.getName(),
374 com.android.settings.cyanogenmod.PrivacySettings.class.getName(),
375 BlacklistSettings.class.getName(),
376 ProfilesSettings.class.getName(),
377 ContributorsCloudFragment.class.getName(),
378 NotificationManagerSettings.class.getName()
382 private static final String[] LIKE_SHORTCUT_INTENT_ACTION_ARRAY = {
383 "android.settings.APPLICATION_DETAILS_SETTINGS"
386 private SharedPreferences mDevelopmentPreferences;
387 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
389 private boolean mBatteryPresent = true;
390 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
392 public void onReceive(Context context, Intent intent) {
393 String action = intent.getAction();
394 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
395 boolean batteryPresent = Utils.isBatteryPresent(intent);
397 if (mBatteryPresent != batteryPresent) {
398 mBatteryPresent = batteryPresent;
399 invalidateCategories(true);
405 private final DynamicIndexableContentMonitor mDynamicIndexableContentMonitor =
406 new DynamicIndexableContentMonitor();
408 private ActionBar mActionBar;
409 private SwitchBar mSwitchBar;
411 private Button mNextButton;
413 private boolean mDisplayHomeAsUpEnabled;
414 private boolean mDisplaySearch;
416 private boolean mIsShowingDashboard;
417 private boolean mIsShortcut;
419 private ViewGroup mContent;
421 private SearchView mSearchView;
422 private MenuItem mSearchMenuItem;
423 private boolean mSearchMenuItemExpanded = false;
424 private SearchResultsSummary mSearchResultsFragment;
425 private String mSearchQuery;
428 private ArrayList<DashboardCategory> mCategories = new ArrayList<DashboardCategory>();
430 private static final String MSG_DATA_FORCE_REFRESH = "msg_data_force_refresh";
431 private static final int MSG_BUILD_CATEGORIES = 1;
432 private Handler mHandler = new Handler() {
434 public void handleMessage(Message msg) {
436 case MSG_BUILD_CATEGORIES: {
437 final boolean forceRefresh = msg.getData().getBoolean(MSG_DATA_FORCE_REFRESH);
439 buildDashboardCategories(mCategories);
446 private boolean mNeedToRevertToInitialFragment = false;
447 private int mHomeActivitiesCount = 1;
449 private Intent mResultIntentData;
451 public SwitchBar getSwitchBar() {
455 public List<DashboardCategory> getDashboardCategories(boolean forceRefresh) {
456 if (forceRefresh || mCategories.size() == 0) {
457 buildDashboardCategories(mCategories);
463 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
464 // Override the fragment title for Wallpaper settings
465 int titleRes = pref.getTitleRes();
466 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
467 titleRes = R.string.wallpaper_settings_fragment_title;
468 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
469 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
470 if (UserManager.get(this).isLinkedUser()) {
471 titleRes = R.string.profile_info_settings_title;
473 titleRes = R.string.user_info_settings_title;
476 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
482 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
486 private void invalidateCategories(boolean forceRefresh) {
487 if (!mHandler.hasMessages(MSG_BUILD_CATEGORIES)) {
488 Message msg = new Message();
489 msg.what = MSG_BUILD_CATEGORIES;
490 msg.getData().putBoolean(MSG_DATA_FORCE_REFRESH, forceRefresh);
495 public void onConfigurationChanged(Configuration newConfig) {
496 super.onConfigurationChanged(newConfig);
497 Index.getInstance(this).update();
501 protected void onStart() {
504 if (mNeedToRevertToInitialFragment) {
505 revertToInitialFragment();
510 public boolean onCreateOptionsMenu(Menu menu) {
511 if (!mDisplaySearch) {
515 MenuInflater inflater = getMenuInflater();
516 inflater.inflate(R.menu.options_menu, menu);
518 // Cache the search query (can be overriden by the OnQueryTextListener)
519 final String query = mSearchQuery;
521 mSearchMenuItem = menu.findItem(R.id.search);
522 mSearchView = (SearchView) mSearchMenuItem.getActionView();
524 if (mSearchMenuItem == null || mSearchView == null) {
528 if (mSearchResultsFragment != null) {
529 mSearchResultsFragment.setSearchView(mSearchView);
532 mSearchMenuItem.setOnActionExpandListener(this);
533 mSearchView.setOnQueryTextListener(this);
534 mSearchView.setOnCloseListener(this);
536 if (mSearchMenuItemExpanded) {
537 mSearchMenuItem.expandActionView();
539 mSearchView.setQuery(query, true /* submit */);
544 private static boolean isShortCutIntent(final Intent intent) {
545 Set<String> categories = intent.getCategories();
546 return (categories != null) && categories.contains("com.android.settings.SHORTCUT");
549 private static boolean isLikeShortCutIntent(final Intent intent) {
550 String action = intent.getAction();
551 if (action == null) {
554 for (int i = 0; i < LIKE_SHORTCUT_INTENT_ACTION_ARRAY.length; i++) {
555 if (LIKE_SHORTCUT_INTENT_ACTION_ARRAY[i].equals(action)) return true;
561 protected void onCreate(Bundle savedState) {
562 super.onCreate(savedState);
564 // Should happen before any call to getIntent()
567 final Intent intent = getIntent();
568 if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
569 getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
572 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
573 Context.MODE_PRIVATE);
575 // Getting Intent properties can only be done after the super.onCreate(...)
576 final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
578 mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) ||
579 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false);
581 final ComponentName cn = intent.getComponent();
582 final String className = cn.getClassName();
584 mIsShowingDashboard = className.equals(Settings.class.getName());
586 // This is a "Sub Settings" when:
587 // - this is a real SubSettings
588 // - or :settings:show_fragment_as_subsetting is passed to the Intent
589 final boolean isSubSettings = this instanceof SubSettings ||
590 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
592 // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content insets
594 // Check also that we are not a Theme Dialog as we don't want to override them
595 final int themeResId = getThemeResId();
596 if (themeResId != R.style.Theme_DialogWhenLarge &&
597 themeResId != R.style.Theme_SubSettingsDialogWhenLarge) {
598 setTheme(R.style.Theme_SubSettings);
602 setContentView(mIsShowingDashboard ?
603 R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
605 mContent = (ViewGroup) findViewById(R.id.main_content);
607 getFragmentManager().addOnBackStackChangedListener(this);
609 if (mIsShowingDashboard) {
610 // Run the Index update only if we have some space
611 if (!Utils.isLowStorage(this)) {
612 Index.getInstance(getApplicationContext()).update();
614 Log.w(LOG_TAG, "Cannot update the Indexer as we are running low on storage space!");
618 if (savedState != null) {
619 // We are restarting from a previous saved state; used that to initialize, instead
620 // of starting fresh.
621 mSearchMenuItemExpanded = savedState.getBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED);
622 mSearchQuery = savedState.getString(SAVE_KEY_SEARCH_QUERY);
624 setTitleFromIntent(intent);
626 ArrayList<DashboardCategory> categories =
627 savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);
628 if (categories != null) {
630 mCategories.addAll(categories);
631 setTitleFromBackStack();
634 mDisplayHomeAsUpEnabled = savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP);
635 mDisplaySearch = savedState.getBoolean(SAVE_KEY_SHOW_SEARCH);
636 mHomeActivitiesCount = savedState.getInt(SAVE_KEY_HOME_ACTIVITIES_COUNT,
637 1 /* one home activity by default */);
639 if (!mIsShowingDashboard) {
640 mDisplaySearch = true;
641 // UP will be shown only if it is a sub settings
643 mDisplayHomeAsUpEnabled = isSubSettings;
644 } else if (isSubSettings) {
645 mDisplayHomeAsUpEnabled = true;
647 mDisplayHomeAsUpEnabled = false;
649 setTitleFromIntent(intent);
651 Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
652 switchToFragment(initialFragmentName, initialArguments, true, false,
653 mInitialTitleResId, mInitialTitle, false);
655 // No UP affordance if we are displaying the main Dashboard
656 mDisplayHomeAsUpEnabled = false;
657 // Show Search affordance
658 mDisplaySearch = true;
659 mInitialTitleResId = R.string.dashboard_title;
660 switchToFragment(DashboardSummary.class.getName(), null, false, false,
661 mInitialTitleResId, mInitialTitle, false);
665 mActionBar = getActionBar();
666 if (mActionBar != null) {
667 mActionBar.setDisplayHomeAsUpEnabled(mDisplayHomeAsUpEnabled);
668 mActionBar.setHomeButtonEnabled(mDisplayHomeAsUpEnabled);
670 mSwitchBar = (SwitchBar) findViewById(R.id.switch_bar);
672 // see if we should show Back/Next buttons
673 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
675 View buttonBar = findViewById(R.id.button_bar);
676 if (buttonBar != null) {
677 buttonBar.setVisibility(View.VISIBLE);
679 Button backButton = (Button)findViewById(R.id.back_button);
680 backButton.setOnClickListener(new OnClickListener() {
681 public void onClick(View v) {
682 setResult(RESULT_CANCELED, getResultIntentData());
686 Button skipButton = (Button)findViewById(R.id.skip_button);
687 skipButton.setOnClickListener(new OnClickListener() {
688 public void onClick(View v) {
689 setResult(RESULT_OK, getResultIntentData());
693 mNextButton = (Button)findViewById(R.id.next_button);
694 mNextButton.setOnClickListener(new OnClickListener() {
695 public void onClick(View v) {
696 setResult(RESULT_OK, getResultIntentData());
701 // set our various button parameters
702 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
703 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
704 if (TextUtils.isEmpty(buttonText)) {
705 mNextButton.setVisibility(View.GONE);
708 mNextButton.setText(buttonText);
711 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
712 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
713 if (TextUtils.isEmpty(buttonText)) {
714 backButton.setVisibility(View.GONE);
717 backButton.setText(buttonText);
720 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
721 skipButton.setVisibility(View.VISIBLE);
726 mHomeActivitiesCount = getHomeActivitiesCount();
729 private int getHomeActivitiesCount() {
730 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
731 getPackageManager().getHomeActivities(homeApps);
732 return homeApps.size();
735 private void setTitleFromIntent(Intent intent) {
736 final int initialTitleResId = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, -1);
737 if (initialTitleResId > 0) {
738 mInitialTitle = null;
739 mInitialTitleResId = initialTitleResId;
741 final String initialTitleResPackageName = intent.getStringExtra(
742 EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME);
743 if (initialTitleResPackageName != null) {
745 Context authContext = createPackageContextAsUser(initialTitleResPackageName,
746 0 /* flags */, new UserHandle(UserHandle.myUserId()));
747 mInitialTitle = authContext.getResources().getText(mInitialTitleResId);
748 setTitle(mInitialTitle);
749 mInitialTitleResId = -1;
751 } catch (NameNotFoundException e) {
752 Log.w(LOG_TAG, "Could not find package" + initialTitleResPackageName);
755 setTitle(mInitialTitleResId);
758 mInitialTitleResId = -1;
759 final String initialTitle = intent.getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
760 mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
761 setTitle(mInitialTitle);
766 public void onBackStackChanged() {
767 setTitleFromBackStack();
770 private int setTitleFromBackStack() {
771 final int count = getFragmentManager().getBackStackEntryCount();
774 if (mInitialTitleResId > 0) {
775 setTitle(mInitialTitleResId);
777 setTitle(mInitialTitle);
782 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1);
783 setTitleFromBackStackEntry(bse);
788 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
789 final CharSequence title;
790 final int titleRes = bse.getBreadCrumbTitleRes();
792 title = getText(titleRes);
794 title = bse.getBreadCrumbTitle();
802 protected void onSaveInstanceState(Bundle outState) {
803 super.onSaveInstanceState(outState);
805 if (mCategories.size() > 0) {
806 outState.putParcelableArrayList(SAVE_KEY_CATEGORIES, mCategories);
809 outState.putBoolean(SAVE_KEY_SHOW_HOME_AS_UP, mDisplayHomeAsUpEnabled);
810 outState.putBoolean(SAVE_KEY_SHOW_SEARCH, mDisplaySearch);
812 if (mDisplaySearch) {
813 // The option menus are created if the ActionBar is visible and they are also created
814 // asynchronously. If you launch Settings with an Intent action like
815 // android.intent.action.POWER_USAGE_SUMMARY and at the same time your device is locked
816 // thru a LockScreen, onCreateOptionsMenu() is not yet called and references to the search
817 // menu item and search view are null.
818 boolean isExpanded = (mSearchMenuItem != null) && mSearchMenuItem.isActionViewExpanded();
819 outState.putBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED, isExpanded);
821 String query = (mSearchView != null) ? mSearchView.getQuery().toString() : EMPTY_QUERY;
822 outState.putString(SAVE_KEY_SEARCH_QUERY, query);
825 outState.putInt(SAVE_KEY_HOME_ACTIVITIES_COUNT, mHomeActivitiesCount);
829 public void onResume() {
831 if (mIsShowingDashboard) {
832 MetricsLogger.visible(this, MetricsLogger.MAIN_SETTINGS);
835 final int newHomeActivityCount = getHomeActivitiesCount();
836 if (newHomeActivityCount != mHomeActivitiesCount) {
837 mHomeActivitiesCount = newHomeActivityCount;
838 invalidateCategories(true);
841 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
843 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
844 invalidateCategories(true);
847 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
848 mDevelopmentPreferencesListener);
850 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
852 mDynamicIndexableContentMonitor.register(this);
854 if(mDisplaySearch && !TextUtils.isEmpty(mSearchQuery)) {
855 onQueryTextSubmit(mSearchQuery);
860 public void onPause() {
862 if (mIsShowingDashboard) {
863 MetricsLogger.hidden(this, MetricsLogger.MAIN_SETTINGS);
865 unregisterReceiver(mBatteryInfoReceiver);
866 mDynamicIndexableContentMonitor.unregister();
870 public void onDestroy() {
873 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
874 mDevelopmentPreferencesListener);
875 mDevelopmentPreferencesListener = null;
878 protected boolean isValidFragment(String fragmentName) {
879 // Almost all fragments are wrapped in this,
880 // except for a few that have their own activities.
881 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
882 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
888 public Intent getIntent() {
889 Intent superIntent = super.getIntent();
890 String startingFragment = getStartingFragmentClass(superIntent);
891 // This is called from super.onCreate, isMultiPane() is not yet reliable
892 // Do not use onIsHidingHeaders either, which relies itself on this method
893 if (startingFragment != null) {
894 Intent modIntent = new Intent(superIntent);
895 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
896 Bundle args = superIntent.getExtras();
898 args = new Bundle(args);
902 args.putParcelable("intent", superIntent);
903 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
910 * Checks if the component name in the intent is different from the Settings class and
911 * returns the class name to load as a fragment.
913 private String getStartingFragmentClass(Intent intent) {
914 if (mFragmentClass != null) return mFragmentClass;
916 String intentClass = intent.getComponent().getClassName();
917 if (intentClass.equals(getClass().getName())) return null;
919 if ("com.android.settings.ManageApplications".equals(intentClass)
920 || "com.android.settings.RunningServices".equals(intentClass)
921 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
922 // Old names of manage apps.
923 intentClass = com.android.settings.applications.ManageApplications.class.getName();
930 * Start a new fragment containing a preference panel. If the preferences
931 * are being displayed in multi-pane mode, the given fragment class will
932 * be instantiated and placed in the appropriate pane. If running in
933 * single-pane mode, a new activity will be launched in which to show the
936 * @param fragmentClass Full name of the class implementing the fragment.
937 * @param args Any desired arguments to supply to the fragment.
938 * @param titleRes Optional resource identifier of the title of this
940 * @param titleText Optional text of the title of this fragment.
941 * @param resultTo Optional fragment that result data should be sent to.
942 * If non-null, resultTo.onActivityResult() will be called when this
943 * preference panel is done. The launched panel must use
944 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
945 * @param resultRequestCode If resultTo is non-null, this is the caller's
946 * request code to be received with the result.
948 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
949 CharSequence titleText, Fragment resultTo, int resultRequestCode) {
952 if (titleText != null) {
953 title = titleText.toString();
955 // There not much we can do in that case
959 Utils.startWithFragment(this, fragmentClass, args, resultTo, resultRequestCode,
960 titleRes, title, mIsShortcut);
964 * Start a new fragment in a new activity containing a preference panel for a given user. If the
965 * preferences are being displayed in multi-pane mode, the given fragment class will be
966 * instantiated and placed in the appropriate pane. If running in single-pane mode, a new
967 * activity will be launched in which to show the fragment.
969 * @param fragmentClass Full name of the class implementing the fragment.
970 * @param args Any desired arguments to supply to the fragment.
971 * @param titleRes Optional resource identifier of the title of this fragment.
972 * @param titleText Optional text of the title of this fragment.
973 * @param userHandle The user for which the panel has to be started.
975 public void startPreferencePanelAsUser(String fragmentClass, Bundle args, int titleRes,
976 CharSequence titleText, UserHandle userHandle) {
977 // This is a workaround.
979 // Calling startWithFragmentAsUser() without specifying FLAG_ACTIVITY_NEW_TASK to the intent
980 // starting the fragment could cause a native stack corruption. See b/17523189. However,
981 // adding that flag and start the preference panel with the same UserHandler will make it
982 // impossible to use back button to return to the previous screen. See b/20042570.
984 // We work around this issue by adding FLAG_ACTIVITY_NEW_TASK to the intent, while doing
985 // another check here to call startPreferencePanel() instead of startWithFragmentAsUser()
986 // when we're calling it as the same user.
987 if (userHandle.getIdentifier() == UserHandle.myUserId()) {
988 startPreferencePanel(fragmentClass, args, titleRes, titleText, null, 0);
992 if (titleText != null) {
993 title = titleText.toString();
995 // There not much we can do in that case
999 Utils.startWithFragmentAsUser(this, fragmentClass, args,
1000 titleRes, title, mIsShortcut, userHandle);
1005 * Called by a preference panel fragment to finish itself.
1007 * @param caller The fragment that is asking to be finished.
1008 * @param resultCode Optional result code to send back to the original
1009 * launching fragment.
1010 * @param resultData Optional result data to send back to the original
1011 * launching fragment.
1013 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
1014 setResult(resultCode, resultData);
1019 * Start a new fragment.
1021 * @param fragment The fragment to start
1022 * @param push If true, the current fragment will be pushed onto the back stack. If false,
1023 * the current fragment will be replaced.
1025 public void startPreferenceFragment(Fragment fragment, boolean push) {
1026 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1027 transaction.replace(R.id.main_content, fragment);
1029 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1030 transaction.addToBackStack(BACK_STACK_PREFS);
1032 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
1034 transaction.commitAllowingStateLoss();
1038 * Switch to a specific Fragment with taking care of validation, Title and BackStack
1040 private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
1041 boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition) {
1042 if (validate && !isValidFragment(fragmentName)) {
1043 throw new IllegalArgumentException("Invalid fragment for this activity: "
1046 Fragment f = Fragment.instantiate(this, fragmentName, args);
1047 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1048 transaction.replace(R.id.main_content, f);
1049 if (withTransition) {
1050 TransitionManager.beginDelayedTransition(mContent);
1052 if (addToBackStack) {
1053 transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
1055 if (titleResId > 0) {
1056 transaction.setBreadCrumbTitle(titleResId);
1057 } else if (title != null) {
1058 transaction.setBreadCrumbTitle(title);
1060 transaction.commitAllowingStateLoss();
1061 getFragmentManager().executePendingTransactions();
1066 * Called when the activity needs its list of categories/tiles built.
1068 * @param categories The list in which to place the tiles categories.
1070 private void buildDashboardCategories(List<DashboardCategory> categories) {
1072 loadCategoriesFromResource(R.xml.dashboard_categories, categories, this);
1073 updateTilesList(categories);
1077 * Parse the given XML file as a categories description, adding each
1078 * parsed categories and tiles into the target list.
1080 * @param resid The XML resource to load and parse.
1081 * @param target The list in which the parsed categories and tiles should be placed.
1083 public static void loadCategoriesFromResource(int resid, List<DashboardCategory> target,
1085 XmlResourceParser parser = null;
1087 parser = context.getResources().getXml(resid);
1088 AttributeSet attrs = Xml.asAttributeSet(parser);
1091 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1092 && type != XmlPullParser.START_TAG) {
1093 // Parse next until start tag is found
1096 String nodeName = parser.getName();
1097 if (!"dashboard-categories".equals(nodeName)) {
1098 throw new RuntimeException(
1099 "XML document must start with <preference-categories> tag; found"
1100 + nodeName + " at " + parser.getPositionDescription());
1103 Bundle curBundle = null;
1105 final int outerDepth = parser.getDepth();
1106 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1107 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1108 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1112 nodeName = parser.getName();
1113 if ("dashboard-category".equals(nodeName)) {
1114 DashboardCategory category = new DashboardCategory();
1116 TypedArray sa = context.obtainStyledAttributes(
1117 attrs, com.android.internal.R.styleable.PreferenceHeader);
1118 category.id = sa.getResourceId(
1119 com.android.internal.R.styleable.PreferenceHeader_id,
1120 (int)DashboardCategory.CAT_ID_UNDEFINED);
1122 TypedValue tv = sa.peekValue(
1123 com.android.internal.R.styleable.PreferenceHeader_title);
1124 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1125 if (tv.resourceId != 0) {
1126 category.titleRes = tv.resourceId;
1128 category.title = tv.string;
1132 sa = context.obtainStyledAttributes(attrs,
1133 com.android.internal.R.styleable.Preference);
1135 com.android.internal.R.styleable.Preference_key);
1136 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1137 if (tv.resourceId != 0) {
1138 category.key = context.getString(tv.resourceId);
1140 category.key = tv.string.toString();
1145 final int innerDepth = parser.getDepth();
1146 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1147 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
1148 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1152 String innerNodeName = parser.getName();
1153 if (innerNodeName.equals("dashboard-tile")) {
1154 DashboardTile tile = new DashboardTile();
1156 sa = context.obtainStyledAttributes(
1157 attrs, com.android.internal.R.styleable.PreferenceHeader);
1158 tile.id = sa.getResourceId(
1159 com.android.internal.R.styleable.PreferenceHeader_id,
1160 (int)TILE_ID_UNDEFINED);
1162 com.android.internal.R.styleable.PreferenceHeader_title);
1163 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1164 if (tv.resourceId != 0) {
1165 tile.titleRes = tv.resourceId;
1167 tile.title = tv.string;
1171 com.android.internal.R.styleable.PreferenceHeader_summary);
1172 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1173 if (tv.resourceId != 0) {
1174 tile.summaryRes = tv.resourceId;
1176 tile.summary = tv.string;
1179 tile.iconRes = sa.getResourceId(
1180 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
1181 tile.fragment = sa.getString(
1182 com.android.internal.R.styleable.PreferenceHeader_fragment);
1185 sa = context.obtainStyledAttributes(attrs, R.styleable.DashboardTile);
1186 tile.switchControl = sa.getString(
1187 R.styleable.DashboardTile_switchClass);
1190 if (curBundle == null) {
1191 curBundle = new Bundle();
1194 final int innerDepth2 = parser.getDepth();
1195 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1196 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth2)) {
1197 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1201 String innerNodeName2 = parser.getName();
1202 if (innerNodeName2.equals("extra")) {
1203 context.getResources().parseBundleExtra("extra", attrs,
1205 XmlUtils.skipCurrentTag(parser);
1207 } else if (innerNodeName2.equals("intent")) {
1208 tile.intent = Intent.parseIntent(context.getResources(), parser,
1212 XmlUtils.skipCurrentTag(parser);
1216 if (curBundle.size() > 0) {
1217 tile.fragmentArguments = curBundle;
1221 category.addTile(tile);
1223 } else if (innerNodeName.equals("external-tiles")) {
1224 category.externalIndex = category.getTilesCount();
1226 XmlUtils.skipCurrentTag(parser);
1230 target.add(category);
1232 XmlUtils.skipCurrentTag(parser);
1236 } catch (XmlPullParserException e) {
1237 throw new RuntimeException("Error parsing categories", e);
1238 } catch (IOException e) {
1239 throw new RuntimeException("Error parsing categories", e);
1241 if (parser != null) parser.close();
1245 private void updateTilesList(List<DashboardCategory> target) {
1246 final boolean showDev = mDevelopmentPreferences.getBoolean(
1247 DevelopmentSettings.PREF_SHOW,
1248 android.os.Build.TYPE.equals("eng"));
1250 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1252 final int size = target.size();
1253 for (int i = 0; i < size; i++) {
1255 DashboardCategory category = target.get(i);
1257 // Ids are integers, so downcasting is ok
1258 int id = (int) category.id;
1259 int n = category.getTilesCount() - 1;
1262 DashboardTile tile = category.getTile(n);
1263 boolean removeTile = false;
1265 if (id == R.id.operator_settings || id == R.id.manufacturer_settings
1266 || id == R.id.device_specific_gesture_settings
1267 || id == R.id.oclick) {
1268 if (!Utils.updateTileToSpecificActivityFromMetaDataOrRemove(this, tile)) {
1271 } else if (id == R.id.wifi_settings) {
1272 // Remove WiFi Settings if WiFi service is not available.
1273 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1276 } else if (id == R.id.bluetooth_settings) {
1277 // Remove Bluetooth Settings if Bluetooth service is not available.
1278 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1281 } else if (id == R.id.mobile_networks) {
1282 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
1283 || Utils.showSimCardTile(this)) {
1286 } else if (id == R.id.sim_settings) {
1287 if (!Utils.showSimCardTile(this)) {
1290 } else if (id == R.id.data_usage_settings) {
1291 // Remove data usage when kernel module not enabled
1292 if (!Utils.isBandwidthControlEnabled()) {
1295 } else if (id == R.id.battery_settings) {
1296 // Remove battery settings when battery is not available. (e.g. TV)
1298 if (!mBatteryPresent) {
1301 } else if (id == R.id.home_settings) {
1302 if (!updateHomeSettingTiles(tile)) {
1305 } else if (id == R.id.user_settings) {
1306 boolean hasMultipleUsers =
1307 ((UserManager) getSystemService(Context.USER_SERVICE))
1308 .getUserCount() > 1;
1309 if (!UserHandle.MU_ENABLED
1310 || !UserManager.supportsMultipleUsers()
1311 || Utils.isMonkeyRunning()) {
1314 } else if (id == R.id.print_settings) {
1315 boolean hasPrintingSupport = getPackageManager().hasSystemFeature(
1316 PackageManager.FEATURE_PRINTING);
1317 if (!hasPrintingSupport) {
1320 } else if (id == R.id.development_settings) {
1321 if (!showDev || um.hasUserRestriction(
1322 UserManager.DISALLOW_DEBUGGING_FEATURES)) {
1325 } else if (id == R.id.button_settings) {
1326 boolean hasDeviceKeys = getResources().getInteger(
1327 com.android.internal.R.integer.config_deviceHardwareKeys) != 0;
1328 if (!hasDeviceKeys) {
1333 if (UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1334 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1338 if (removeTile && n < category.getTilesCount()) {
1339 category.removeTile(n);
1344 addExternalTiles(target);
1347 private void addExternalTiles(List<DashboardCategory> target) {
1348 Map<Pair<String, String>, DashboardTile> addedCache =
1349 new ArrayMap<Pair<String, String>, DashboardTile>();
1350 UserManager userManager = UserManager.get(this);
1351 for (UserHandle user : userManager.getUserProfiles()) {
1352 addExternalTiles(target, user, addedCache);
1356 private void addExternalTiles(List<DashboardCategory> target, UserHandle user,
1357 Map<Pair<String, String>, DashboardTile> addedCache) {
1358 PackageManager pm = getPackageManager();
1359 Intent intent = new Intent(EXTRA_SETTINGS_ACTION);
1360 List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
1361 PackageManager.GET_META_DATA, user.getIdentifier());
1362 for (ResolveInfo resolved : results) {
1363 if (!resolved.system) {
1364 // Do not allow any app to add to settings, only system ones.
1367 ActivityInfo activityInfo = resolved.activityInfo;
1368 Bundle metaData = activityInfo.metaData;
1369 if ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY)) {
1370 Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for action "
1371 + EXTRA_SETTINGS_ACTION + " missing metadata " +
1372 (metaData == null ? "" : EXTRA_CATEGORY_KEY));
1375 String categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
1376 DashboardCategory category = getCategory(target, categoryKey);
1377 if (category == null) {
1378 Log.w(LOG_TAG, "Activity " + resolved.activityInfo.name + " has unknown "
1379 + "category key " + categoryKey);
1382 Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,
1384 DashboardTile tile = addedCache.get(key);
1386 tile = new DashboardTile();
1387 tile.intent = new Intent().setClassName(
1388 activityInfo.packageName, activityInfo.name);
1389 Utils.updateTileToSpecificActivityFromMetaDataOrRemove(this, tile);
1391 if (category.externalIndex == -1
1392 || category.externalIndex > category.getTilesCount()) {
1393 // If no location for external tiles has been specified for this category,
1394 // then just put them at the end.
1395 category.addTile(tile);
1397 category.addTile(category.externalIndex, tile);
1399 addedCache.put(key, tile);
1401 tile.userHandle.add(user);
1405 private DashboardCategory getCategory(List<DashboardCategory> target, String categoryKey) {
1406 for (DashboardCategory category : target) {
1407 if (categoryKey.equals(category.key)) {
1414 private boolean updateHomeSettingTiles(DashboardTile tile) {
1415 // Once we decide to show Home settings, keep showing it forever
1416 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1417 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1422 mHomeActivitiesCount = getHomeActivitiesCount();
1423 if (mHomeActivitiesCount < 2) {
1424 // When there's only one available home app, omit this settings
1425 // category entirely at the top level UI. If the user just
1426 // uninstalled the penultimate home app candidiate, we also
1427 // now tell them about why they aren't seeing 'Home' in the list.
1428 if (sShowNoHomeNotice) {
1429 sShowNoHomeNotice = false;
1430 NoHomeDialogFragment.show(this);
1434 // Okay, we're allowing the Home settings category. Tell it, when
1435 // invoked via this front door, that we'll need to be told about the
1436 // case when the user uninstalls all but one home app.
1437 if (tile.fragmentArguments == null) {
1438 tile.fragmentArguments = new Bundle();
1440 tile.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1442 } catch (Exception e) {
1443 // Can't look up the home activity; bail on configuring the icon
1444 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1447 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1451 private void getMetaData() {
1453 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1454 PackageManager.GET_META_DATA);
1455 if (ai == null || ai.metaData == null) return;
1456 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1457 } catch (NameNotFoundException nnfe) {
1459 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1463 // give subclasses access to the Next button
1464 public boolean hasNextButton() {
1465 return mNextButton != null;
1468 public Button getNextButton() {
1473 public boolean shouldUpRecreateTask(Intent targetIntent) {
1474 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1477 public static void requestHomeNotice() {
1478 sShowNoHomeNotice = true;
1482 public boolean onQueryTextSubmit(String query) {
1483 switchToSearchResultsFragmentIfNeeded();
1484 mSearchQuery = query;
1485 return mSearchResultsFragment.onQueryTextSubmit(query);
1489 public boolean onQueryTextChange(String newText) {
1490 mSearchQuery = newText;
1491 if (mSearchResultsFragment == null) {
1494 return mSearchResultsFragment.onQueryTextChange(newText);
1498 public boolean onClose() {
1503 public boolean onMenuItemActionExpand(MenuItem item) {
1504 if (item.getItemId() == mSearchMenuItem.getItemId()) {
1505 switchToSearchResultsFragmentIfNeeded();
1511 public boolean onMenuItemActionCollapse(MenuItem item) {
1512 if (item.getItemId() == mSearchMenuItem.getItemId()) {
1513 if (mSearchMenuItemExpanded) {
1514 revertToInitialFragment();
1520 private void switchToSearchResultsFragmentIfNeeded() {
1521 if (mSearchResultsFragment != null) {
1524 Fragment current = getFragmentManager().findFragmentById(R.id.main_content);
1525 if (current != null && current instanceof SearchResultsSummary) {
1526 mSearchResultsFragment = (SearchResultsSummary) current;
1528 mSearchResultsFragment = (SearchResultsSummary) switchToFragment(
1529 SearchResultsSummary.class.getName(), null, false, true,
1530 R.string.search_results_title, null, true);
1532 mSearchResultsFragment.setSearchView(mSearchView);
1533 mSearchMenuItemExpanded = true;
1536 public void needToRevertToInitialFragment() {
1537 mNeedToRevertToInitialFragment = true;
1540 private void revertToInitialFragment() {
1541 mNeedToRevertToInitialFragment = false;
1542 mSearchResultsFragment = null;
1543 mSearchMenuItemExpanded = false;
1544 getFragmentManager().popBackStackImmediate(SettingsActivity.BACK_STACK_PREFS,
1545 FragmentManager.POP_BACK_STACK_INCLUSIVE);
1546 if (mSearchMenuItem != null) {
1547 mSearchMenuItem.collapseActionView();
1551 public Intent getResultIntentData() {
1552 return mResultIntentData;
1555 public void setResultIntentData(Intent resultIntentData) {
1556 mResultIntentData = resultIntentData;
1559 public void setNfcProfileCallback(NFCProfileTagCallback callback) {
1560 mNfcProfileCallback = callback;
1564 protected void onNewIntent(Intent intent) {
1565 if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
1566 Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
1567 if (mNfcProfileCallback != null) {
1568 mNfcProfileCallback.onTagRead(detectedTag);
1572 super.onNewIntent(intent);