blob: 00e65f243b2689beea074b4647887e1aa0b1b205 [file] [log] [blame]
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings;
18
19import android.accounts.Account;
20import android.accounts.AccountManager;
21import android.accounts.OnAccountsUpdateListener;
22import android.app.ActionBar;
23import android.app.Activity;
24import android.app.Fragment;
25import android.app.FragmentManager;
26import android.app.FragmentTransaction;
27import android.app.AlertDialog;
28import android.app.Dialog;
29import android.app.DialogFragment;
30import android.app.admin.DevicePolicyManager;
31import android.content.BroadcastReceiver;
32import android.content.Context;
33import android.content.Intent;
34import android.content.IntentFilter;
35import android.content.SharedPreferences;
36import android.content.pm.ActivityInfo;
37import android.content.pm.PackageManager;
38import android.content.pm.PackageManager.NameNotFoundException;
39import android.content.pm.ResolveInfo;
40import android.content.res.Configuration;
41import android.content.res.Resources;
42import android.content.res.TypedArray;
43import android.content.res.XmlResourceParser;
44import android.graphics.drawable.Drawable;
45import android.nfc.NfcAdapter;
46import android.os.Bundle;
47import android.os.Handler;
48import android.os.INetworkManagementService;
49import android.os.Message;
50import android.os.Parcel;
51import android.os.Parcelable;
52import android.os.RemoteException;
53import android.os.ServiceManager;
54import android.os.UserHandle;
55import android.os.UserManager;
56import android.preference.Preference;
57import android.preference.PreferenceFragment;
58import android.preference.PreferenceManager;
59import android.preference.PreferenceScreen;
60import android.support.v4.app.ActionBarDrawerToggle;
61import android.support.v4.widget.DrawerLayout;
62import android.text.TextUtils;
63import android.util.AttributeSet;
64import android.util.Log;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080065import android.util.TypedValue;
66import android.util.Xml;
67import android.view.LayoutInflater;
68import android.view.MenuItem;
69import android.view.View;
70import android.view.View.OnClickListener;
71import android.view.ViewGroup;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +000072import android.widget.AbsListView;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080073import android.widget.AdapterView;
74import android.widget.ArrayAdapter;
75import android.widget.Button;
76import android.widget.ImageButton;
77import android.widget.ImageView;
78import android.widget.ListView;
79import android.widget.Switch;
80import android.widget.TextView;
81
82import com.android.internal.util.ArrayUtils;
83import com.android.internal.util.XmlUtils;
84import com.android.settings.accessibility.AccessibilitySettings;
85import com.android.settings.accessibility.CaptionPropertiesFragment;
86import com.android.settings.accounts.AccountSyncSettings;
87import com.android.settings.accounts.AuthenticatorHelper;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080088import com.android.settings.accounts.ManageAccountsSettings;
89import com.android.settings.applications.ManageApplications;
90import com.android.settings.applications.ProcessStatsUi;
91import com.android.settings.bluetooth.BluetoothEnabler;
92import com.android.settings.bluetooth.BluetoothSettings;
93import com.android.settings.dashboard.DashboardSummary;
94import com.android.settings.deviceinfo.Memory;
95import com.android.settings.deviceinfo.UsbSettings;
96import com.android.settings.fuelgauge.PowerUsageSummary;
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -080097import com.android.settings.indexer.Index;
98import com.android.settings.indexer.IndexableData;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -080099import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
100import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
101import com.android.settings.inputmethod.SpellCheckersSettings;
102import com.android.settings.inputmethod.UserDictionaryList;
103import com.android.settings.location.LocationSettings;
104import com.android.settings.nfc.AndroidBeam;
105import com.android.settings.nfc.PaymentSettings;
106import com.android.settings.print.PrintJobSettingsFragment;
107import com.android.settings.print.PrintSettingsFragment;
108import com.android.settings.tts.TextToSpeechSettings;
109import com.android.settings.users.UserSettings;
110import com.android.settings.vpn2.VpnSettings;
111import com.android.settings.wfd.WifiDisplaySettings;
112import com.android.settings.wifi.AdvancedWifiSettings;
113import com.android.settings.wifi.WifiEnabler;
114import com.android.settings.wifi.WifiSettings;
115import com.android.settings.wifi.p2p.WifiP2pSettings;
116import org.xmlpull.v1.XmlPullParser;
117import org.xmlpull.v1.XmlPullParserException;
118
119import java.io.IOException;
120import java.util.ArrayList;
121import java.util.Collections;
122import java.util.Comparator;
123import java.util.HashMap;
124import java.util.List;
125
126public class SettingsActivity extends Activity
127 implements PreferenceManager.OnPreferenceTreeClickListener,
128 PreferenceFragment.OnPreferenceStartFragmentCallback,
129 ButtonBarHandler, OnAccountsUpdateListener, FragmentManager.OnBackStackChangedListener {
130
131 private static final String LOG_TAG = "Settings";
132
133 // Constants for state save/restore
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800134 private static final String SAVE_KEY_HEADERS_TAG = ":settings:headers";
135 private static final String SAVE_KEY_CURRENT_HEADER_TAG = ":settings:cur_header";
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800136 private static final String SAVE_KEY_TITLES_TAG = ":settings:titles";
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800137
138 /**
139 * When starting this activity, the invoking Intent can contain this extra
140 * string to specify which fragment should be initially displayed.
141 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
142 * will call isValidFragment() to confirm that the fragment class name is valid for this
143 * activity.
144 */
145 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
146
147 /**
148 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
149 * this extra can also be specified to supply a Bundle of arguments to pass
150 * to that fragment when it is instantiated during the initial creation
151 * of the activity.
152 */
153 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
154
155 /**
156 * When starting this activity, the invoking Intent can contain this extra
157 * boolean that the header list should not be displayed. This is most often
158 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
159 * the activity to display a specific fragment that the user has navigated
160 * to.
161 */
162 public static final String EXTRA_NO_HEADERS = ":settings:no_headers";
163
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800164 public static final String BACK_STACK_PREFS = ":settings:prefs";
165
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800166 // extras that allow any preference activity to be launched as part of a wizard
167
168 // show Back and Next buttons? takes boolean parameter
169 // Back will then return RESULT_CANCELED and Next RESULT_OK
170 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
171
172 // add a Skip button?
173 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
174
175 // specify custom text for the Back or Next buttons, or cause a button to not appear
176 // at all by setting it to null
177 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
178 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
179
180 /**
181 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
182 * this extra can also be specify to supply the title to be shown for
183 * that fragment.
184 */
185 protected static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
186
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800187 private static final String META_DATA_KEY_HEADER_ID =
188 "com.android.settings.TOP_LEVEL_HEADER_ID";
189
190 private static final String META_DATA_KEY_FRAGMENT_CLASS =
191 "com.android.settings.FRAGMENT_CLASS";
192
193 private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
194
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800195 private static boolean sShowNoHomeNotice = false;
196
197 private String mFragmentClass;
198 private int mTopLevelHeaderId;
199 private Header mFirstHeader;
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800200 private Header mSelectedHeader;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800201 private Header mCurrentHeader;
202
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800203 private CharSequence mInitialTitle;
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800204 private Header mInitialHeader;
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800205
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800206 // Show only these settings for restricted users
207 private int[] SETTINGS_FOR_RESTRICTED = {
208 R.id.wireless_section,
209 R.id.wifi_settings,
210 R.id.bluetooth_settings,
211 R.id.data_usage_settings,
212 R.id.wireless_settings,
213 R.id.device_section,
214 R.id.sound_settings,
215 R.id.display_settings,
216 R.id.storage_settings,
217 R.id.application_settings,
218 R.id.battery_settings,
219 R.id.personal_section,
220 R.id.location_settings,
221 R.id.security_settings,
222 R.id.language_settings,
223 R.id.user_settings,
224 R.id.account_settings,
225 R.id.account_add,
226 R.id.system_section,
227 R.id.date_time_settings,
228 R.id.about_settings,
229 R.id.accessibility_settings,
230 R.id.print_settings,
231 R.id.nfc_payment_settings,
Fabrice Di Meglio2858b792014-02-18 19:35:08 -0800232 R.id.home_settings,
233 R.id.dashboard
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800234 };
235
236 private static final String[] ENTRY_FRAGMENTS = {
237 WirelessSettings.class.getName(),
238 WifiSettings.class.getName(),
239 AdvancedWifiSettings.class.getName(),
240 BluetoothSettings.class.getName(),
241 TetherSettings.class.getName(),
242 WifiP2pSettings.class.getName(),
243 VpnSettings.class.getName(),
244 DateTimeSettings.class.getName(),
245 LocalePicker.class.getName(),
246 InputMethodAndLanguageSettings.class.getName(),
247 SpellCheckersSettings.class.getName(),
248 UserDictionaryList.class.getName(),
249 UserDictionarySettings.class.getName(),
250 SoundSettings.class.getName(),
251 DisplaySettings.class.getName(),
252 DeviceInfoSettings.class.getName(),
253 ManageApplications.class.getName(),
254 ProcessStatsUi.class.getName(),
255 NotificationStation.class.getName(),
256 LocationSettings.class.getName(),
257 SecuritySettings.class.getName(),
258 PrivacySettings.class.getName(),
259 DeviceAdminSettings.class.getName(),
260 AccessibilitySettings.class.getName(),
261 CaptionPropertiesFragment.class.getName(),
262 com.android.settings.accessibility.ToggleInversionPreferenceFragment.class.getName(),
263 com.android.settings.accessibility.ToggleContrastPreferenceFragment.class.getName(),
264 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
265 TextToSpeechSettings.class.getName(),
266 Memory.class.getName(),
267 DevelopmentSettings.class.getName(),
268 UsbSettings.class.getName(),
269 AndroidBeam.class.getName(),
270 WifiDisplaySettings.class.getName(),
271 PowerUsageSummary.class.getName(),
272 AccountSyncSettings.class.getName(),
273 CryptKeeperSettings.class.getName(),
274 DataUsageSummary.class.getName(),
275 DreamSettings.class.getName(),
276 UserSettings.class.getName(),
277 NotificationAccessSettings.class.getName(),
278 ManageAccountsSettings.class.getName(),
279 PrintSettingsFragment.class.getName(),
280 PrintJobSettingsFragment.class.getName(),
281 TrustedCredentialsSettings.class.getName(),
282 PaymentSettings.class.getName(),
283 KeyboardLayoutPickerFragment.class.getName(),
John Spurlock72438062014-02-27 18:01:20 -0500284 DashboardSummary.class.getName(),
285 ZenModeSettings.class.getName()
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800286 };
287
288 private SharedPreferences mDevelopmentPreferences;
289 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
290
291 // TODO: Update Call Settings based on airplane mode state.
292
293 protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
294
295 private AuthenticatorHelper mAuthenticatorHelper;
296 private boolean mListeningToAccountUpdates;
297
298 private Button mNextButton;
299
300 private boolean mBatteryPresent = true;
301 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
302
303 @Override
304 public void onReceive(Context context, Intent intent) {
305 String action = intent.getAction();
306 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
307 boolean batteryPresent = Utils.isBatteryPresent(intent);
308
309 if (mBatteryPresent != batteryPresent) {
310 mBatteryPresent = batteryPresent;
311 invalidateHeaders();
312 }
313 }
314 }
315 };
316
317 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800318 private HeaderAdapter mHeaderAdapter;
319
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800320 private DrawerLayout mDrawerLayout;
321 private ListView mDrawer;
322 private ActionBarDrawerToggle mDrawerToggle;
323 private ActionBar mActionBar;
324
325 private static final int MSG_BUILD_HEADERS = 1;
326 private Handler mHandler = new Handler() {
327 @Override
328 public void handleMessage(Message msg) {
329 switch (msg.what) {
330 case MSG_BUILD_HEADERS: {
331 mHeaders.clear();
332 onBuildHeaders(mHeaders);
333 mHeaderAdapter.notifyDataSetChanged();
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800334 if (mCurrentHeader != null) {
335 Header mappedHeader = findBestMatchingHeader(mCurrentHeader, mHeaders);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800336 if (mappedHeader != null) {
337 setSelectedHeader(mappedHeader);
338 }
339 }
340 } break;
341 }
342 }
343 };
344
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800345 /**
346 * Searchable data description.
347 *
348 * Known restriction: we are only searching (for now) the first level of Settings.
349 */
350 private static IndexableData[] INDEXABLE_DATA = new IndexableData[] {
351 new IndexableData(1, R.xml.wifi_settings,
352 "com.android.settings.wifi.WifiSettings",
353 R.drawable.ic_settings_wireless),
354 new IndexableData(2, R.xml.bluetooth_settings,
355 "com.android.settings.bluetooth.BluetoothSettings",
356 R.drawable.ic_settings_bluetooth2),
357 new IndexableData(3, R.xml.data_usage_metered_prefs,
358 "com.android.settings.net.DataUsageMeteredSettings",
359 R.drawable.ic_settings_data_usage),
360 new IndexableData(4, R.xml.wireless_settings,
361 "com.android.settings.WirelessSettings",
362 R.drawable.empty_icon),
363 new IndexableData(5, R.xml.home_selection,
364 "com.android.settings.HomeSettings",
365 R.drawable.ic_settings_home),
366 new IndexableData(6, R.xml.sound_settings,
367 "com.android.settings.SoundSettings",
368 R.drawable.ic_settings_sound),
369 new IndexableData(7, R.xml.display_settings,
370 "com.android.settings.DisplaySettings",
371 R.drawable.ic_settings_display),
372 new IndexableData(8, R.xml.device_info_memory,
373 "com.android.settings.deviceinfo.Memory",
374 R.drawable.ic_settings_storage),
375 new IndexableData(9, R.xml.power_usage_summary,
376 "com.android.settings.fuelgauge.PowerUsageSummary",
377 R.drawable.ic_settings_battery),
378 new IndexableData(10, R.xml.user_settings,
379 "com.android.settings.users.UserSettings",
380 R.drawable.ic_settings_multiuser),
381 new IndexableData(11, R.xml.location_settings,
382 "com.android.settings.location.LocationSettings",
383 R.drawable.ic_settings_location),
384 new IndexableData(12, R.xml.security_settings,
385 "com.android.settings.SecuritySettings",
386 R.drawable.ic_settings_security),
387 new IndexableData(13, R.xml.language_settings,
388 "com.android.settings.inputmethod.InputMethodAndLanguageSettings",
389 R.drawable.ic_settings_language),
390 new IndexableData(14, R.xml.privacy_settings,
391 "com.android.settings.PrivacySettings",
392 R.drawable.ic_settings_backup),
393 new IndexableData(15, R.xml.date_time_prefs,
394 "com.android.settings.DateTimeSettings",
395 R.drawable.ic_settings_date_time),
396 new IndexableData(16, R.xml.accessibility_settings,
397 "com.android.settings.accessibility.AccessibilitySettings",
398 R.drawable.ic_settings_accessibility),
399 new IndexableData(17, R.xml.print_settings,
400 "com.android.settings.print.PrintSettingsFragment",
401 com.android.internal.R.drawable.ic_print),
402 new IndexableData(18, R.xml.development_prefs,
403 "com.android.settings.DevelopmentSettings",
404 R.drawable.ic_settings_development),
405 new IndexableData(19, R.xml.device_info_settings,
406 "com.android.settings.DeviceInfoSettings",
407 R.drawable.ic_settings_about),
408 };
409
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800410 @Override
411 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
412 // Override the fragment title for Wallpaper settings
413 int titleRes = pref.getTitleRes();
414 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
415 titleRes = R.string.wallpaper_settings_fragment_title;
416 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
417 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
418 if (UserManager.get(this).isLinkedUser()) {
419 titleRes = R.string.profile_info_settings_title;
420 } else {
421 titleRes = R.string.user_info_settings_title;
422 }
423 }
424 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
425 null, 0);
426 return true;
427 }
428
429 @Override
430 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
431 return false;
432 }
433
434 private class DrawerListener implements DrawerLayout.DrawerListener {
435 @Override
436 public void onDrawerOpened(View drawerView) {
437 mDrawerToggle.onDrawerOpened(drawerView);
438 }
439
440 @Override
441 public void onDrawerClosed(View drawerView) {
442 mDrawerToggle.onDrawerClosed(drawerView);
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800443 // Cannot process clicks when the App is finishing
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800444 if (isFinishing() || mSelectedHeader == null) {
445 return;
446 }
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800447 switchToHeader(mSelectedHeader, false, false);
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800448 mSelectedHeader = null;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800449 }
450
451 @Override
452 public void onDrawerSlide(View drawerView, float slideOffset) {
453 mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
454 }
455
456 @Override
457 public void onDrawerStateChanged(int newState) {
458 mDrawerToggle.onDrawerStateChanged(newState);
459 }
460 }
461
462 private class DrawerItemClickListener implements ListView.OnItemClickListener {
463 @Override
464 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
465 mDrawerLayout.closeDrawer(mDrawer);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000466 onListItemClick((ListView)parent, view, position, id);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800467 }
468 }
469
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800470 private Header findBestMatchingHeader(Header current, ArrayList<Header> from) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800471 ArrayList<Header> matches = new ArrayList<Header>();
472 for (int j=0; j<from.size(); j++) {
473 Header oh = from.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800474 if (current == oh || (current.id != HEADER_ID_UNDEFINED && current.id == oh.id)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800475 // Must be this one.
476 matches.clear();
477 matches.add(oh);
478 break;
479 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800480 if (current.fragment != null) {
481 if (current.fragment.equals(oh.fragment)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800482 matches.add(oh);
483 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800484 } else if (current.intent != null) {
485 if (current.intent.equals(oh.intent)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800486 matches.add(oh);
487 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800488 } else if (current.title != null) {
489 if (current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800490 matches.add(oh);
491 }
492 }
493 }
494 final int NM = matches.size();
495 if (NM == 1) {
496 return matches.get(0);
497 } else if (NM > 1) {
498 for (int j=0; j<NM; j++) {
499 Header oh = matches.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800500 if (current.fragmentArguments != null &&
501 current.fragmentArguments.equals(oh.fragmentArguments)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800502 return oh;
503 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800504 if (current.extras != null && current.extras.equals(oh.extras)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800505 return oh;
506 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800507 if (current.title != null && current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800508 return oh;
509 }
510 }
511 }
512 return null;
513 }
514
515 private void invalidateHeaders() {
516 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
517 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
518 }
519 }
520
521 @Override
522 protected void onPostCreate(Bundle savedInstanceState) {
523 super.onPostCreate(savedInstanceState);
524
525 // Sync the toggle state after onRestoreInstanceState has occurred.
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800526 mDrawerToggle.syncState();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800527 }
528
529 @Override
530 public void onConfigurationChanged(Configuration newConfig) {
531 super.onConfigurationChanged(newConfig);
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800532 mDrawerToggle.onConfigurationChanged(newConfig);
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800533 Index.getInstance(this).update();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800534 }
535
536 @Override
537 public boolean onOptionsItemSelected(MenuItem item) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800538 if (mDrawerToggle.onOptionsItemSelected(item)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800539 return true;
540 }
541 return super.onOptionsItemSelected(item);
542 }
543
544 @Override
545 protected void onCreate(Bundle savedInstanceState) {
546 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
547 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
548 }
549
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800550 Index.getInstance(this).addIndexableData(INDEXABLE_DATA);
551 Index.getInstance(this).update();
552
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800553 mAuthenticatorHelper = new AuthenticatorHelper();
554 mAuthenticatorHelper.updateAuthDescriptions(this);
555 mAuthenticatorHelper.onAccountsUpdated(this, null);
556
557 DevicePolicyManager dpm =
558 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
Fabrice Di Megliodca28062014-02-21 17:42:56 -0800559
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800560 mHeaderAdapter= new HeaderAdapter(this, mHeaders, mAuthenticatorHelper, dpm);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800561
562 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
563 Context.MODE_PRIVATE);
564
565 getMetaData();
566
567 super.onCreate(savedInstanceState);
568
569 setContentView(R.layout.settings_main);
570
571 getFragmentManager().addOnBackStackChangedListener(this);
572
573 mActionBar = getActionBar();
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800574 mActionBar.setDisplayHomeAsUpEnabled(true);
575 mActionBar.setHomeButtonEnabled(true);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800576
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800577 mDrawer = (ListView) findViewById(R.id.headers_drawer);
578 mDrawer.setAdapter(mHeaderAdapter);
579 mDrawer.setOnItemClickListener(new DrawerItemClickListener());
580 mDrawer.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800581
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800582 mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
583 mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
584 R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800585
586 if (savedInstanceState != null) {
587 // We are restarting from a previous saved state; used that to
588 // initialize, instead of starting fresh.
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800589 mInitialTitle = getTitle();
590 setTitleFromBackStack();
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800591
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800592 ArrayList<Header> headers =
593 savedInstanceState.getParcelableArrayList(SAVE_KEY_HEADERS_TAG);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800594 if (headers != null) {
595 mHeaders.addAll(headers);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800596 int curHeader = savedInstanceState.getInt(SAVE_KEY_CURRENT_HEADER_TAG,
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800597 (int) HEADER_ID_UNDEFINED);
598 if (curHeader >= 0 && curHeader < mHeaders.size()) {
599 setSelectedHeader(mHeaders.get(curHeader));
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800600 mInitialHeader = mCurrentHeader;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800601 }
602 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800603 } else {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800604 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
605 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
606
607 // We need to build the Headers in all cases
608 onBuildHeaders(mHeaders);
609
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800610 if (initialFragment != null) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000611 // If we are just showing a fragment, we want to run in
612 // new fragment mode, but don't need to compute and show
613 // the headers.
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800614 final int initialTitleResId = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
615 mInitialTitle = (initialTitleResId > 0) ? getText(initialTitleResId) : getTitle();
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800616 setTitle(mInitialTitle);
617 switchToHeaderInner(initialFragment, initialArguments, true, false, mInitialTitle);
618 setSelectedHeaderByTopLevelId(mTopLevelHeaderId);
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800619 mInitialHeader = mCurrentHeader;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000620 } else {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000621 // If there are headers, then at this point we need to show
622 // them and, depending on the screen, we may also show in-line
623 // the currently selected preference fragment.
624 if (mHeaders.size() > 0) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800625 mInitialHeader = onGetInitialHeader();
626 mInitialTitle = getHeaderTitle(mInitialHeader);
627 switchToHeader(mInitialHeader, false, true);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000628 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800629 }
630 }
631
632 // see if we should show Back/Next buttons
633 Intent intent = getIntent();
634 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
635
636 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
637 if (buttonBar != null) {
638 buttonBar.setVisibility(View.VISIBLE);
639
640 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
641 backButton.setOnClickListener(new OnClickListener() {
642 public void onClick(View v) {
643 setResult(RESULT_CANCELED);
644 finish();
645 }
646 });
647 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
648 skipButton.setOnClickListener(new OnClickListener() {
649 public void onClick(View v) {
650 setResult(RESULT_OK);
651 finish();
652 }
653 });
654 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
655 mNextButton.setOnClickListener(new OnClickListener() {
656 public void onClick(View v) {
657 setResult(RESULT_OK);
658 finish();
659 }
660 });
661
662 // set our various button parameters
663 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
664 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
665 if (TextUtils.isEmpty(buttonText)) {
666 mNextButton.setVisibility(View.GONE);
667 }
668 else {
669 mNextButton.setText(buttonText);
670 }
671 }
672 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
673 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
674 if (TextUtils.isEmpty(buttonText)) {
675 backButton.setVisibility(View.GONE);
676 }
677 else {
678 backButton.setText(buttonText);
679 }
680 }
681 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
682 skipButton.setVisibility(View.VISIBLE);
683 }
684 }
685 }
686
687 if (!onIsHidingHeaders()) {
688 highlightHeader(mTopLevelHeaderId);
689 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800690 }
691
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800692 public Header onGetInitialHeader() {
693 String fragmentClass = getStartingFragmentClass(super.getIntent());
694 if (fragmentClass != null) {
695 Header header = new Header();
696 header.fragment = fragmentClass;
697 header.title = getTitle();
698 header.fragmentArguments = getIntent().getExtras();
699 return header;
700 }
701
702 return mFirstHeader;
703 }
704
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800705 @Override
706 public void onBackStackChanged() {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800707 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800708 }
709
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800710 private void setTitleFromBackStack() {
711 final int count = getFragmentManager().getBackStackEntryCount();
712 if (count == 0) {
713 setTitle(mInitialTitle);
714 return;
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800715 }
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800716 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1);
717 setTitleFromBackStackEntry(bse);
718 }
719
720 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
721 final CharSequence title;
722 final int titleRes = bse.getBreadCrumbTitleRes();
723 if (titleRes > 0) {
724 title = getText(titleRes);
725 } else {
726 title = bse.getBreadCrumbTitle();
727 }
728 if (title != null) {
729 setTitle(title);
730 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800731 }
732
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800733 @Override
734 protected void onSaveInstanceState(Bundle outState) {
735 super.onSaveInstanceState(outState);
736
737 if (mHeaders.size() > 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800738 outState.putParcelableArrayList(SAVE_KEY_HEADERS_TAG, mHeaders);
739 if (mCurrentHeader != null) {
740 int index = mHeaders.indexOf(mCurrentHeader);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800741 if (index >= 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800742 outState.putInt(SAVE_KEY_CURRENT_HEADER_TAG, index);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800743 }
744 }
745 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800746 }
747
748 @Override
749 public void onResume() {
750 super.onResume();
751
752 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
753 @Override
754 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
755 invalidateHeaders();
756 }
757 };
758 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
759 mDevelopmentPreferencesListener);
760
Matthew Xiea504c4d2014-02-14 16:32:32 -0800761 mHeaderAdapter.resume(this);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800762 invalidateHeaders();
763
764 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800765
766 mDrawerLayout.setDrawerListener(new DrawerListener());
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800767 }
768
769 @Override
770 public void onPause() {
771 super.onPause();
772
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800773 mDrawerLayout.setDrawerListener(null);
774
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800775 unregisterReceiver(mBatteryInfoReceiver);
776
777 mHeaderAdapter.pause();
778
779 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
780 mDevelopmentPreferencesListener);
781
782 mDevelopmentPreferencesListener = null;
783 }
784
785 @Override
786 public void onDestroy() {
787 super.onDestroy();
788 if (mListeningToAccountUpdates) {
789 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
790 }
791 }
792
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800793 protected boolean isValidFragment(String fragmentName) {
794 // Almost all fragments are wrapped in this,
795 // except for a few that have their own activities.
796 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
797 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
798 }
799 return false;
800 }
801
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800802 private CharSequence getHeaderTitle(Header header) {
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800803 if (header == null || header.fragment == null) return getTitle();
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800804 final CharSequence title;
805 if (header.fragment.equals(DashboardSummary.class.getName())) {
806 title = getResources().getString(R.string.settings_label);
807 } else {
808 title = header.getTitle(getResources());
809 }
810 return title;
811 }
812
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800813 private void setSelectedHeaderByTopLevelId(int topLevelId) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800814 final int count = mHeaders.size();
815 for (int n = 0; n < count; n++) {
816 Header h = mHeaders.get(n);
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800817 if (h.id == topLevelId) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800818 setSelectedHeader(h);
819 return;
820 }
821 }
822 }
823
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800824 private void setSelectedHeader(Header header) {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800825 if (header == null) {
826 mCurrentHeader = null;
827 return;
828 }
829 // Update selected Header into Drawer only if it is not "Add Account"
830 if (header.id == R.id.account_add) {
831 mDrawer.clearChoices();
832 return;
833 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800834 mCurrentHeader = header;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800835 int index = mHeaders.indexOf(header);
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800836 if (index >= 0) {
837 mDrawer.setItemChecked(index, true);
838 } else {
839 mDrawer.clearChoices();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800840 }
841 }
842
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800843 /**
844 * When in two-pane mode, switch to the fragment pane to show the given
845 * preference fragment.
846 *
847 * @param header The new header to display.
848 * @param validate true means that the fragment's Header needs to be validated.
849 * @param initial true means that it is the initial Header.
850 */
851 private void switchToHeader(Header header, boolean validate, boolean initial) {
852 if (header == null) {
853 return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800854 }
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800855 // For switching to another Header it should be a different one
856 if (mCurrentHeader == null || header.id != mCurrentHeader.id) {
857 if (header.fragment != null) {
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800858 boolean addToBackStack;
859 if (initial) {
860 addToBackStack = false;
861 } else {
862 if (header.id != mInitialHeader.id) {
863 addToBackStack = true;
864 } else {
865 addToBackStack = (mTopLevelHeaderId > 0);
866 }
867 }
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800868 switchToHeaderInner(header.fragment, header.fragmentArguments, validate,
869 addToBackStack, getHeaderTitle(header));
870 setSelectedHeader(header);
871 } else if (header.intent != null) {
872 setSelectedHeader(header);
873 startActivity(header.intent);
874 } else {
875 throw new IllegalStateException(
876 "Can't switch to header that has no Fragment nor Intent");
877 }
878 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800879 }
880
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000881 /**
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800882 * Switch the fragment pane to show the given preference fragment.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000883 *
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800884 * (used for initial fragment)
885 *
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000886 * @param fragmentName The name of the fragment to display.
887 * @param args Optional arguments to supply to the fragment.
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800888 * @param validate true means that the fragment's Header needs to be validated.
889 * @param title The title of the fragment to display.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000890 */
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800891 private void switchToHeader(String fragmentName, Bundle args, boolean validate,
892 CharSequence title) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000893 setSelectedHeader(null);
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800894 switchToHeaderInner(fragmentName, args, validate, false, title);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000895 }
896
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800897 /**
898 * Switch to a specific Header with taking care of validation, Title and BackStack
899 */
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800900 private void switchToHeaderInner(String fragmentName, Bundle args, boolean validate,
901 boolean addToBackStack, CharSequence title) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000902 getFragmentManager().popBackStack(BACK_STACK_PREFS,
903 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800904 if (validate && !isValidFragment(fragmentName)) {
905 throw new IllegalArgumentException("Invalid fragment for this activity: "
906 + fragmentName);
907 }
908 Fragment f = Fragment.instantiate(this, fragmentName, args);
909 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800910 transaction.replace(R.id.prefs, f);
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800911 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
912 if (addToBackStack) {
913 transaction.addToBackStack(BACK_STACK_PREFS);
914 }
915 if (title != null) {
916 transaction.setBreadCrumbTitle(title);
917 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800918 transaction.commitAllowingStateLoss();
919 }
920
921 @Override
922 public void onNewIntent(Intent intent) {
923 super.onNewIntent(intent);
924
925 // If it is not launched from history, then reset to top-level
926 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
927 if (mDrawer != null) {
928 mDrawer.setSelectionFromTop(0, 0);
929 }
930 }
931 }
932
933 /**
934 * Called to determine whether the header list should be hidden.
935 * The default implementation returns the
936 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
937 * This is set to false, for example, when the activity is being re-launched
938 * to show a particular preference activity.
939 */
940 public boolean onIsHidingHeaders() {
941 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
942 }
943
944 private void highlightHeader(int id) {
945 if (id != 0) {
946 Integer index = mHeaderIndexMap.get(id);
947 if (index != null && mDrawer != null) {
948 mDrawer.setItemChecked(index, true);
949 if (mDrawer.getVisibility() == View.VISIBLE) {
950 mDrawer.smoothScrollToPosition(index);
951 }
952 }
953 }
954 }
955
956 @Override
957 public Intent getIntent() {
958 Intent superIntent = super.getIntent();
959 String startingFragment = getStartingFragmentClass(superIntent);
960 // This is called from super.onCreate, isMultiPane() is not yet reliable
961 // Do not use onIsHidingHeaders either, which relies itself on this method
962 if (startingFragment != null) {
963 Intent modIntent = new Intent(superIntent);
964 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
965 Bundle args = superIntent.getExtras();
966 if (args != null) {
967 args = new Bundle(args);
968 } else {
969 args = new Bundle();
970 }
971 args.putParcelable("intent", superIntent);
972 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
973 return modIntent;
974 }
975 return superIntent;
976 }
977
978 /**
979 * Checks if the component name in the intent is different from the Settings class and
980 * returns the class name to load as a fragment.
981 */
982 private String getStartingFragmentClass(Intent intent) {
983 if (mFragmentClass != null) return mFragmentClass;
984
985 String intentClass = intent.getComponent().getClassName();
986 if (intentClass.equals(getClass().getName())) return null;
987
988 if ("com.android.settings.ManageApplications".equals(intentClass)
989 || "com.android.settings.RunningServices".equals(intentClass)
990 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
991 // Old names of manage apps.
992 intentClass = com.android.settings.applications.ManageApplications.class.getName();
993 }
994
995 return intentClass;
996 }
997
998 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000999 * Start a new fragment containing a preference panel. If the preferences
1000 * are being displayed in multi-pane mode, the given fragment class will
1001 * be instantiated and placed in the appropriate pane. If running in
1002 * single-pane mode, a new activity will be launched in which to show the
1003 * fragment.
1004 *
1005 * @param fragmentClass Full name of the class implementing the fragment.
1006 * @param args Any desired arguments to supply to the fragment.
1007 * @param titleRes Optional resource identifier of the title of this
1008 * fragment.
1009 * @param titleText Optional text of the title of this fragment.
1010 * @param resultTo Optional fragment that result data should be sent to.
1011 * If non-null, resultTo.onActivityResult() will be called when this
1012 * preference panel is done. The launched panel must use
1013 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
1014 * @param resultRequestCode If resultTo is non-null, this is the caller's
1015 * request code to be received with the resut.
1016 */
1017 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
1018 CharSequence titleText, Fragment resultTo,
1019 int resultRequestCode) {
1020 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
1021 }
1022
1023 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001024 * Called by a preference panel fragment to finish itself.
1025 *
1026 * @param caller The fragment that is asking to be finished.
1027 * @param resultCode Optional result code to send back to the original
1028 * launching fragment.
1029 * @param resultData Optional result data to send back to the original
1030 * launching fragment.
1031 */
1032 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
1033 setResult(resultCode, resultData);
1034 }
1035
1036 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001037 * Start a new fragment.
1038 *
1039 * @param fragment The fragment to start
1040 * @param push If true, the current fragment will be pushed onto the back stack. If false,
1041 * the current fragment will be replaced.
1042 */
1043 public void startPreferenceFragment(Fragment fragment, boolean push) {
1044 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1045 transaction.replace(R.id.prefs, fragment);
1046 if (push) {
1047 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1048 transaction.addToBackStack(BACK_STACK_PREFS);
1049 } else {
1050 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
1051 }
1052 transaction.commitAllowingStateLoss();
1053 }
1054
1055 /**
1056 * Start a new fragment.
1057 *
1058 * @param fragmentName The name of the fragment to display.
1059 * @param args Optional arguments to supply to the fragment.
1060 * @param resultTo Option fragment that should receive the result of
1061 * the activity launch.
1062 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
1063 * report the result.
1064 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
1065 * valid one then it will be used to get the title. Otherwise the titleText
1066 * argument will be used as the title.
1067 * @param titleText string to display for the title of.
1068 */
1069 private void startWithFragment(String fragmentName, Bundle args, Fragment resultTo,
1070 int resultRequestCode, int titleRes, CharSequence titleText) {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -08001071 final CharSequence cs;
1072 if (titleRes != 0) {
1073 cs = getText(titleRes);
1074 } else {
1075 cs = titleText;
1076 }
1077
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001078 Fragment f = Fragment.instantiate(this, fragmentName, args);
1079 if (resultTo != null) {
1080 f.setTargetFragment(resultTo, resultRequestCode);
1081 }
1082 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1083 transaction.replace(R.id.prefs, f);
1084 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1085 transaction.addToBackStack(BACK_STACK_PREFS);
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -08001086 transaction.setBreadCrumbTitle(cs);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001087 transaction.commitAllowingStateLoss();
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001088 }
1089
1090 /**
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -08001091 * Called when the activity needs its list of headers build.
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001092 *
1093 * @param headers The list in which to place the headers.
1094 */
1095 private void onBuildHeaders(List<Header> headers) {
1096 loadHeadersFromResource(R.xml.settings_headers, headers);
1097 updateHeaderList(headers);
1098 }
1099
1100 /**
1101 * Parse the given XML file as a header description, adding each
1102 * parsed Header into the target list.
1103 *
1104 * @param resid The XML resource to load and parse.
1105 * @param target The list in which the parsed headers should be placed.
1106 */
1107 private void loadHeadersFromResource(int resid, List<Header> target) {
1108 XmlResourceParser parser = null;
1109 try {
1110 parser = getResources().getXml(resid);
1111 AttributeSet attrs = Xml.asAttributeSet(parser);
1112
1113 int type;
1114 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1115 && type != XmlPullParser.START_TAG) {
1116 // Parse next until start tag is found
1117 }
1118
1119 String nodeName = parser.getName();
1120 if (!"preference-headers".equals(nodeName)) {
1121 throw new RuntimeException(
1122 "XML document must start with <preference-headers> tag; found"
1123 + nodeName + " at " + parser.getPositionDescription());
1124 }
1125
1126 Bundle curBundle = null;
1127
1128 final int outerDepth = parser.getDepth();
1129 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1130 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1131 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1132 continue;
1133 }
1134
1135 nodeName = parser.getName();
1136 if ("header".equals(nodeName)) {
1137 Header header = new Header();
1138
1139 TypedArray sa = obtainStyledAttributes(
1140 attrs, com.android.internal.R.styleable.PreferenceHeader);
1141 header.id = sa.getResourceId(
1142 com.android.internal.R.styleable.PreferenceHeader_id,
1143 (int)HEADER_ID_UNDEFINED);
1144 TypedValue tv = sa.peekValue(
1145 com.android.internal.R.styleable.PreferenceHeader_title);
1146 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1147 if (tv.resourceId != 0) {
1148 header.titleRes = tv.resourceId;
1149 } else {
1150 header.title = tv.string;
1151 }
1152 }
1153 tv = sa.peekValue(
1154 com.android.internal.R.styleable.PreferenceHeader_summary);
1155 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1156 if (tv.resourceId != 0) {
1157 header.summaryRes = tv.resourceId;
1158 } else {
1159 header.summary = tv.string;
1160 }
1161 }
1162 header.iconRes = sa.getResourceId(
1163 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
1164 header.fragment = sa.getString(
1165 com.android.internal.R.styleable.PreferenceHeader_fragment);
1166 sa.recycle();
1167
1168 if (curBundle == null) {
1169 curBundle = new Bundle();
1170 }
1171
1172 final int innerDepth = parser.getDepth();
1173 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1174 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
1175 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1176 continue;
1177 }
1178
1179 String innerNodeName = parser.getName();
1180 if (innerNodeName.equals("extra")) {
1181 getResources().parseBundleExtra("extra", attrs, curBundle);
1182 XmlUtils.skipCurrentTag(parser);
1183
1184 } else if (innerNodeName.equals("intent")) {
1185 header.intent = Intent.parseIntent(getResources(), parser, attrs);
1186
1187 } else {
1188 XmlUtils.skipCurrentTag(parser);
1189 }
1190 }
1191
1192 if (curBundle.size() > 0) {
1193 header.fragmentArguments = curBundle;
1194 curBundle = null;
1195 }
1196
1197 target.add(header);
1198 } else {
1199 XmlUtils.skipCurrentTag(parser);
1200 }
1201 }
1202
1203 } catch (XmlPullParserException e) {
1204 throw new RuntimeException("Error parsing headers", e);
1205 } catch (IOException e) {
1206 throw new RuntimeException("Error parsing headers", e);
1207 } finally {
1208 if (parser != null) parser.close();
1209 }
1210 }
1211
1212 private void updateHeaderList(List<Header> target) {
1213 final boolean showDev = mDevelopmentPreferences.getBoolean(
1214 DevelopmentSettings.PREF_SHOW,
1215 android.os.Build.TYPE.equals("eng"));
1216 int i = 0;
1217
1218 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1219 mHeaderIndexMap.clear();
1220 while (i < target.size()) {
1221 Header header = target.get(i);
1222 // Ids are integers, so downcasting
1223 int id = (int) header.id;
1224 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1225 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1226 } else if (id == R.id.wifi_settings) {
1227 // Remove WiFi Settings if WiFi service is not available.
1228 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1229 target.remove(i);
1230 }
1231 } else if (id == R.id.bluetooth_settings) {
1232 // Remove Bluetooth Settings if Bluetooth service is not available.
1233 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1234 target.remove(i);
1235 }
1236 } else if (id == R.id.data_usage_settings) {
1237 // Remove data usage when kernel module not enabled
1238 final INetworkManagementService netManager = INetworkManagementService.Stub
1239 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1240 try {
1241 if (!netManager.isBandwidthControlEnabled()) {
1242 target.remove(i);
1243 }
1244 } catch (RemoteException e) {
1245 // ignored
1246 }
1247 } else if (id == R.id.battery_settings) {
1248 // Remove battery settings when battery is not available. (e.g. TV)
1249
1250 if (!mBatteryPresent) {
1251 target.remove(i);
1252 }
1253 } else if (id == R.id.account_settings) {
1254 int headerIndex = i + 1;
1255 i = insertAccountsHeaders(target, headerIndex);
1256 } else if (id == R.id.home_settings) {
1257 if (!updateHomeSettingHeaders(header)) {
1258 target.remove(i);
1259 }
1260 } else if (id == R.id.user_settings) {
1261 if (!UserHandle.MU_ENABLED
1262 || !UserManager.supportsMultipleUsers()
1263 || Utils.isMonkeyRunning()) {
1264 target.remove(i);
1265 }
1266 } else if (id == R.id.nfc_payment_settings) {
1267 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1268 target.remove(i);
1269 } else {
1270 // Only show if NFC is on and we have the HCE feature
1271 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1272 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1273 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1274 target.remove(i);
1275 }
1276 }
1277 } else if (id == R.id.development_settings) {
1278 if (!showDev) {
1279 target.remove(i);
1280 }
1281 } else if (id == R.id.account_add) {
1282 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1283 target.remove(i);
1284 }
1285 }
1286
1287 if (i < target.size() && target.get(i) == header
1288 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1289 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1290 target.remove(i);
1291 }
1292
1293 // Increment if the current one wasn't removed by the Utils code.
1294 if (i < target.size() && target.get(i) == header) {
1295 // Hold on to the first header, when we need to reset to the top-level
1296 if (mFirstHeader == null &&
1297 HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
1298 mFirstHeader = header;
1299 }
1300 mHeaderIndexMap.put(id, i);
1301 i++;
1302 }
1303 }
1304 }
1305
1306 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1307 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1308 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1309 for (String accountType : accountTypes) {
1310 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1311 if (label == null) {
1312 continue;
1313 }
1314
1315 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1316 boolean skipToAccount = accounts.length == 1
1317 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1318 Header accHeader = new Header();
1319 accHeader.title = label;
1320 if (accHeader.extras == null) {
1321 accHeader.extras = new Bundle();
1322 }
1323 if (skipToAccount) {
1324 accHeader.fragment = AccountSyncSettings.class.getName();
1325 accHeader.fragmentArguments = new Bundle();
1326 // Need this for the icon
1327 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1328 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1329 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1330 accounts[0]);
1331 } else {
1332 accHeader.fragment = ManageAccountsSettings.class.getName();
1333 accHeader.fragmentArguments = new Bundle();
1334 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1335 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1336 accountType);
1337 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1338 label.toString());
1339 }
1340 accountHeaders.add(accHeader);
1341 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1342 }
1343
1344 // Sort by label
1345 Collections.sort(accountHeaders, new Comparator<Header>() {
1346 @Override
1347 public int compare(Header h1, Header h2) {
1348 return h1.title.toString().compareTo(h2.title.toString());
1349 }
1350 });
1351
1352 for (Header header : accountHeaders) {
1353 target.add(headerIndex++, header);
1354 }
1355 if (!mListeningToAccountUpdates) {
1356 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1357 mListeningToAccountUpdates = true;
1358 }
1359 return headerIndex;
1360 }
1361
1362 private boolean updateHomeSettingHeaders(Header header) {
1363 // Once we decide to show Home settings, keep showing it forever
1364 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1365 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1366 return true;
1367 }
1368
1369 try {
1370 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1371 getPackageManager().getHomeActivities(homeApps);
1372 if (homeApps.size() < 2) {
1373 // When there's only one available home app, omit this settings
1374 // category entirely at the top level UI. If the user just
1375 // uninstalled the penultimate home app candidiate, we also
1376 // now tell them about why they aren't seeing 'Home' in the list.
1377 if (sShowNoHomeNotice) {
1378 sShowNoHomeNotice = false;
1379 NoHomeDialogFragment.show(this);
1380 }
1381 return false;
1382 } else {
1383 // Okay, we're allowing the Home settings category. Tell it, when
1384 // invoked via this front door, that we'll need to be told about the
1385 // case when the user uninstalls all but one home app.
1386 if (header.fragmentArguments == null) {
1387 header.fragmentArguments = new Bundle();
1388 }
1389 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1390 }
1391 } catch (Exception e) {
1392 // Can't look up the home activity; bail on configuring the icon
1393 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1394 }
1395
1396 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1397 return true;
1398 }
1399
1400 private void getMetaData() {
1401 try {
1402 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1403 PackageManager.GET_META_DATA);
1404 if (ai == null || ai.metaData == null) return;
1405 mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
1406 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1407 } catch (NameNotFoundException nnfe) {
1408 // No recovery
1409 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1410 }
1411 }
1412
1413 // give subclasses access to the Next button
1414 public boolean hasNextButton() {
1415 return mNextButton != null;
1416 }
1417
1418 public Button getNextButton() {
1419 return mNextButton;
1420 }
1421
1422 public static class NoHomeDialogFragment extends DialogFragment {
1423 public static void show(Activity parent) {
1424 final NoHomeDialogFragment dialog = new NoHomeDialogFragment();
1425 dialog.show(parent.getFragmentManager(), null);
1426 }
1427
1428 @Override
1429 public Dialog onCreateDialog(Bundle savedInstanceState) {
1430 return new AlertDialog.Builder(getActivity())
1431 .setMessage(R.string.only_one_home_message)
1432 .setPositiveButton(android.R.string.ok, null)
1433 .create();
1434 }
1435 }
1436
1437 private static class HeaderAdapter extends ArrayAdapter<Header> {
1438 static final int HEADER_TYPE_CATEGORY = 0;
1439 static final int HEADER_TYPE_NORMAL = 1;
1440 static final int HEADER_TYPE_SWITCH = 2;
1441 static final int HEADER_TYPE_BUTTON = 3;
1442 private static final int HEADER_TYPE_COUNT = HEADER_TYPE_BUTTON + 1;
1443
1444 private final WifiEnabler mWifiEnabler;
1445 private final BluetoothEnabler mBluetoothEnabler;
1446 private AuthenticatorHelper mAuthHelper;
1447 private DevicePolicyManager mDevicePolicyManager;
1448
1449 private static class HeaderViewHolder {
1450 ImageView mIcon;
1451 TextView mTitle;
1452 TextView mSummary;
1453 Switch mSwitch;
1454 ImageButton mButton;
1455 View mDivider;
1456 }
1457
1458 private LayoutInflater mInflater;
1459
1460 static int getHeaderType(Header header) {
1461 if (header.fragment == null && header.intent == null) {
1462 return HEADER_TYPE_CATEGORY;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001463 } else if (header.id == R.id.security_settings) {
1464 return HEADER_TYPE_BUTTON;
1465 } else {
1466 return HEADER_TYPE_NORMAL;
1467 }
1468 }
1469
1470 @Override
1471 public int getItemViewType(int position) {
1472 Header header = getItem(position);
1473 return getHeaderType(header);
1474 }
1475
1476 @Override
1477 public boolean areAllItemsEnabled() {
1478 return false; // because of categories
1479 }
1480
1481 @Override
1482 public boolean isEnabled(int position) {
1483 return getItemViewType(position) != HEADER_TYPE_CATEGORY;
1484 }
1485
1486 @Override
1487 public int getViewTypeCount() {
1488 return HEADER_TYPE_COUNT;
1489 }
1490
1491 @Override
1492 public boolean hasStableIds() {
1493 return true;
1494 }
1495
1496 public HeaderAdapter(Context context, List<Header> objects,
1497 AuthenticatorHelper authenticatorHelper, DevicePolicyManager dpm) {
1498 super(context, 0, objects);
1499
1500 mAuthHelper = authenticatorHelper;
1501 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1502
1503 // Temp Switches provided as placeholder until the adapter replaces these with actual
1504 // Switches inflated from their layouts. Must be done before adapter is set in super
1505 mWifiEnabler = new WifiEnabler(context, new Switch(context));
1506 mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
1507 mDevicePolicyManager = dpm;
1508 }
1509
1510 @Override
1511 public View getView(int position, View convertView, ViewGroup parent) {
1512 HeaderViewHolder holder;
1513 Header header = getItem(position);
1514 int headerType = getHeaderType(header);
1515 View view = null;
1516
1517 if (convertView == null) {
1518 holder = new HeaderViewHolder();
1519 switch (headerType) {
1520 case HEADER_TYPE_CATEGORY:
1521 view = new TextView(getContext(), null,
1522 android.R.attr.listSeparatorTextViewStyle);
1523 holder.mTitle = (TextView) view;
1524 break;
1525
1526 case HEADER_TYPE_SWITCH:
1527 view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
1528 false);
1529 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1530 holder.mTitle = (TextView)
1531 view.findViewById(com.android.internal.R.id.title);
1532 holder.mSummary = (TextView)
1533 view.findViewById(com.android.internal.R.id.summary);
1534 holder.mSwitch = (Switch) view.findViewById(R.id.switchWidget);
1535 break;
1536
1537 case HEADER_TYPE_BUTTON:
1538 view = mInflater.inflate(R.layout.preference_header_button_item, parent,
1539 false);
1540 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1541 holder.mTitle = (TextView)
1542 view.findViewById(com.android.internal.R.id.title);
1543 holder.mSummary = (TextView)
1544 view.findViewById(com.android.internal.R.id.summary);
1545 holder.mButton = (ImageButton) view.findViewById(R.id.buttonWidget);
1546 holder.mDivider = view.findViewById(R.id.divider);
1547 break;
1548
1549 case HEADER_TYPE_NORMAL:
1550 view = mInflater.inflate(
1551 R.layout.preference_header_item, parent,
1552 false);
1553 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1554 holder.mTitle = (TextView)
1555 view.findViewById(com.android.internal.R.id.title);
1556 holder.mSummary = (TextView)
1557 view.findViewById(com.android.internal.R.id.summary);
1558 break;
1559 }
Fabrice Di Meglio0e2f9492014-02-25 14:26:27 -08001560 if (holder.mIcon != null) {
1561 holder.mIcon.setBackgroundResource(R.color.background_drawer_icon);
1562 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001563 view.setTag(holder);
1564 } else {
1565 view = convertView;
1566 holder = (HeaderViewHolder) view.getTag();
1567 }
1568
1569 // All view fields must be updated every time, because the view may be recycled
1570 switch (headerType) {
1571 case HEADER_TYPE_CATEGORY:
1572 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1573 break;
1574
1575 case HEADER_TYPE_SWITCH:
1576 // Would need a different treatment if the main menu had more switches
1577 if (header.id == R.id.wifi_settings) {
1578 mWifiEnabler.setSwitch(holder.mSwitch);
1579 } else {
1580 mBluetoothEnabler.setSwitch(holder.mSwitch);
1581 }
1582 updateCommonHeaderView(header, holder);
1583 break;
1584
1585 case HEADER_TYPE_BUTTON:
1586 if (header.id == R.id.security_settings) {
1587 boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled();
1588 if (hasCert) {
1589 holder.mButton.setVisibility(View.VISIBLE);
1590 holder.mDivider.setVisibility(View.VISIBLE);
1591 boolean isManaged = mDevicePolicyManager.getDeviceOwner() != null;
1592 if (isManaged) {
1593 holder.mButton.setImageResource(R.drawable.ic_settings_about);
1594 } else {
1595 holder.mButton.setImageResource(
1596 android.R.drawable.stat_notify_error);
1597 }
1598 holder.mButton.setOnClickListener(new OnClickListener() {
1599 @Override
1600 public void onClick(View v) {
1601 Intent intent = new Intent(
1602 android.provider.Settings.ACTION_MONITORING_CERT_INFO);
1603 getContext().startActivity(intent);
1604 }
1605 });
1606 } else {
1607 holder.mButton.setVisibility(View.GONE);
1608 holder.mDivider.setVisibility(View.GONE);
1609 }
1610 }
1611 updateCommonHeaderView(header, holder);
1612 break;
1613
1614 case HEADER_TYPE_NORMAL:
1615 updateCommonHeaderView(header, holder);
1616 break;
1617 }
1618
1619 return view;
1620 }
1621
1622 private void updateCommonHeaderView(Header header, HeaderViewHolder holder) {
1623 if (header.extras != null
1624 && header.extras.containsKey(ManageAccountsSettings.KEY_ACCOUNT_TYPE)) {
1625 String accType = header.extras.getString(
1626 ManageAccountsSettings.KEY_ACCOUNT_TYPE);
1627 Drawable icon = mAuthHelper.getDrawableForType(getContext(), accType);
1628 setHeaderIcon(holder, icon);
1629 } else {
1630 holder.mIcon.setImageResource(header.iconRes);
1631 }
1632 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1633 CharSequence summary = header.getSummary(getContext().getResources());
1634 if (!TextUtils.isEmpty(summary)) {
1635 holder.mSummary.setVisibility(View.VISIBLE);
1636 holder.mSummary.setText(summary);
1637 } else {
1638 holder.mSummary.setVisibility(View.GONE);
1639 }
1640 }
1641
1642 private void setHeaderIcon(HeaderViewHolder holder, Drawable icon) {
1643 ViewGroup.LayoutParams lp = holder.mIcon.getLayoutParams();
1644 lp.width = getContext().getResources().getDimensionPixelSize(
1645 R.dimen.header_icon_width);
1646 lp.height = lp.width;
1647 holder.mIcon.setLayoutParams(lp);
1648 holder.mIcon.setImageDrawable(icon);
1649 }
1650
Matthew Xiea504c4d2014-02-14 16:32:32 -08001651 public void resume(Context context) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001652 mWifiEnabler.resume();
Matthew Xiea504c4d2014-02-14 16:32:32 -08001653 mBluetoothEnabler.resume(context);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001654 }
1655
1656 public void pause() {
1657 mWifiEnabler.pause();
1658 mBluetoothEnabler.pause();
1659 }
1660 }
1661
1662 private void onListItemClick(ListView l, View v, int position, long id) {
1663 if (!isResumed()) {
1664 return;
1665 }
1666 Object item = mHeaderAdapter.getItem(position);
1667 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001668 mSelectedHeader = (Header) item;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001669 }
1670 }
1671
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001672 @Override
1673 public boolean shouldUpRecreateTask(Intent targetIntent) {
1674 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1675 }
1676
1677 @Override
1678 public void onAccountsUpdated(Account[] accounts) {
1679 // TODO: watch for package upgrades to invalidate cache; see 7206643
1680 mAuthenticatorHelper.updateAuthDescriptions(this);
1681 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1682 invalidateHeaders();
1683 }
1684
1685 public static void requestHomeNotice() {
1686 sShowNoHomeNotice = true;
1687 }
1688
1689 /**
1690 * Default value for {@link Header#id Header.id} indicating that no
1691 * identifier value is set. All other values (including those below -1)
1692 * are valid.
1693 */
1694 private static final long HEADER_ID_UNDEFINED = -1;
1695
1696 /**
1697 * Description of a single Header item that the user can select.
1698 */
1699 static final class Header implements Parcelable {
1700 /**
1701 * Identifier for this header, to correlate with a new list when
1702 * it is updated. The default value is
1703 * {@link SettingsActivity#HEADER_ID_UNDEFINED}, meaning no id.
1704 * @attr ref android.R.styleable#PreferenceHeader_id
1705 */
1706 public long id = HEADER_ID_UNDEFINED;
1707
1708 /**
1709 * Resource ID of title of the header that is shown to the user.
1710 * @attr ref android.R.styleable#PreferenceHeader_title
1711 */
1712 public int titleRes;
1713
1714 /**
1715 * Title of the header that is shown to the user.
1716 * @attr ref android.R.styleable#PreferenceHeader_title
1717 */
1718 public CharSequence title;
1719
1720 /**
1721 * Resource ID of optional summary describing what this header controls.
1722 * @attr ref android.R.styleable#PreferenceHeader_summary
1723 */
1724 public int summaryRes;
1725
1726 /**
1727 * Optional summary describing what this header controls.
1728 * @attr ref android.R.styleable#PreferenceHeader_summary
1729 */
1730 public CharSequence summary;
1731
1732 /**
1733 * Optional icon resource to show for this header.
1734 * @attr ref android.R.styleable#PreferenceHeader_icon
1735 */
1736 public int iconRes;
1737
1738 /**
1739 * Full class name of the fragment to display when this header is
1740 * selected.
1741 * @attr ref android.R.styleable#PreferenceHeader_fragment
1742 */
1743 public String fragment;
1744
1745 /**
1746 * Optional arguments to supply to the fragment when it is
1747 * instantiated.
1748 */
1749 public Bundle fragmentArguments;
1750
1751 /**
1752 * Intent to launch when the preference is selected.
1753 */
1754 public Intent intent;
1755
1756 /**
1757 * Optional additional data for use by subclasses of the activity
1758 */
1759 public Bundle extras;
1760
1761 public Header() {
1762 // Empty
1763 }
1764
1765 /**
1766 * Return the currently set title. If {@link #titleRes} is set,
1767 * this resource is loaded from <var>res</var> and returned. Otherwise
1768 * {@link #title} is returned.
1769 */
1770 public CharSequence getTitle(Resources res) {
1771 if (titleRes != 0) {
1772 return res.getText(titleRes);
1773 }
1774 return title;
1775 }
1776
1777 /**
1778 * Return the currently set summary. If {@link #summaryRes} is set,
1779 * this resource is loaded from <var>res</var> and returned. Otherwise
1780 * {@link #summary} is returned.
1781 */
1782 public CharSequence getSummary(Resources res) {
1783 if (summaryRes != 0) {
1784 return res.getText(summaryRes);
1785 }
1786 return summary;
1787 }
1788
1789 @Override
1790 public int describeContents() {
1791 return 0;
1792 }
1793
1794 @Override
1795 public void writeToParcel(Parcel dest, int flags) {
1796 dest.writeLong(id);
1797 dest.writeInt(titleRes);
1798 TextUtils.writeToParcel(title, dest, flags);
1799 dest.writeInt(summaryRes);
1800 TextUtils.writeToParcel(summary, dest, flags);
1801 dest.writeInt(iconRes);
1802 dest.writeString(fragment);
1803 dest.writeBundle(fragmentArguments);
1804 if (intent != null) {
1805 dest.writeInt(1);
1806 intent.writeToParcel(dest, flags);
1807 } else {
1808 dest.writeInt(0);
1809 }
1810 dest.writeBundle(extras);
1811 }
1812
1813 public void readFromParcel(Parcel in) {
1814 id = in.readLong();
1815 titleRes = in.readInt();
1816 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1817 summaryRes = in.readInt();
1818 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1819 iconRes = in.readInt();
1820 fragment = in.readString();
1821 fragmentArguments = in.readBundle();
1822 if (in.readInt() != 0) {
1823 intent = Intent.CREATOR.createFromParcel(in);
1824 }
1825 extras = in.readBundle();
1826 }
1827
1828 Header(Parcel in) {
1829 readFromParcel(in);
1830 }
1831
1832 public static final Creator<Header> CREATOR = new Creator<Header>() {
1833 public Header createFromParcel(Parcel source) {
1834 return new Header(source);
1835 }
1836 public Header[] newArray(int size) {
1837 return new Header[size];
1838 }
1839 };
1840 }
1841}