blob: 28ff8836db316a50b8c8c9bfdd33d42ce1c3e725 [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
Fabrice Di Meglioc95be4f2014-03-07 12:57:38 -0800706 public void onBackPressed() {
707 if (mDrawerLayout.isDrawerOpen(mDrawer)) {
708 mDrawerLayout.closeDrawer(mDrawer);
709 return;
710 }
711 super.onBackPressed();
712 }
713
714 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800715 public void onBackStackChanged() {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800716 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800717 }
718
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800719 private void setTitleFromBackStack() {
720 final int count = getFragmentManager().getBackStackEntryCount();
721 if (count == 0) {
722 setTitle(mInitialTitle);
723 return;
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800724 }
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800725 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1);
726 setTitleFromBackStackEntry(bse);
727 }
728
729 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
730 final CharSequence title;
731 final int titleRes = bse.getBreadCrumbTitleRes();
732 if (titleRes > 0) {
733 title = getText(titleRes);
734 } else {
735 title = bse.getBreadCrumbTitle();
736 }
737 if (title != null) {
738 setTitle(title);
739 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800740 }
741
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800742 @Override
743 protected void onSaveInstanceState(Bundle outState) {
744 super.onSaveInstanceState(outState);
745
746 if (mHeaders.size() > 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800747 outState.putParcelableArrayList(SAVE_KEY_HEADERS_TAG, mHeaders);
748 if (mCurrentHeader != null) {
749 int index = mHeaders.indexOf(mCurrentHeader);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800750 if (index >= 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800751 outState.putInt(SAVE_KEY_CURRENT_HEADER_TAG, index);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800752 }
753 }
754 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800755 }
756
757 @Override
758 public void onResume() {
759 super.onResume();
760
761 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
762 @Override
763 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
764 invalidateHeaders();
765 }
766 };
767 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
768 mDevelopmentPreferencesListener);
769
Matthew Xiea504c4d2014-02-14 16:32:32 -0800770 mHeaderAdapter.resume(this);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800771 invalidateHeaders();
772
773 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800774
775 mDrawerLayout.setDrawerListener(new DrawerListener());
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800776 }
777
778 @Override
779 public void onPause() {
780 super.onPause();
781
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800782 mDrawerLayout.setDrawerListener(null);
783
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800784 unregisterReceiver(mBatteryInfoReceiver);
785
786 mHeaderAdapter.pause();
787
788 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
789 mDevelopmentPreferencesListener);
790
791 mDevelopmentPreferencesListener = null;
792 }
793
794 @Override
795 public void onDestroy() {
796 super.onDestroy();
797 if (mListeningToAccountUpdates) {
798 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
799 }
800 }
801
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800802 protected boolean isValidFragment(String fragmentName) {
803 // Almost all fragments are wrapped in this,
804 // except for a few that have their own activities.
805 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
806 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
807 }
808 return false;
809 }
810
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800811 private CharSequence getHeaderTitle(Header header) {
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800812 if (header == null || header.fragment == null) return getTitle();
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800813 final CharSequence title;
814 if (header.fragment.equals(DashboardSummary.class.getName())) {
815 title = getResources().getString(R.string.settings_label);
816 } else {
817 title = header.getTitle(getResources());
818 }
819 return title;
820 }
821
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800822 private void setSelectedHeaderByTopLevelId(int topLevelId) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800823 final int count = mHeaders.size();
824 for (int n = 0; n < count; n++) {
825 Header h = mHeaders.get(n);
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800826 if (h.id == topLevelId) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800827 setSelectedHeader(h);
828 return;
829 }
830 }
831 }
832
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800833 private void setSelectedHeader(Header header) {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800834 if (header == null) {
835 mCurrentHeader = null;
836 return;
837 }
838 // Update selected Header into Drawer only if it is not "Add Account"
839 if (header.id == R.id.account_add) {
840 mDrawer.clearChoices();
841 return;
842 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800843 mCurrentHeader = header;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800844 int index = mHeaders.indexOf(header);
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800845 if (index >= 0) {
846 mDrawer.setItemChecked(index, true);
847 } else {
848 mDrawer.clearChoices();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800849 }
850 }
851
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800852 /**
853 * When in two-pane mode, switch to the fragment pane to show the given
854 * preference fragment.
855 *
856 * @param header The new header to display.
857 * @param validate true means that the fragment's Header needs to be validated.
858 * @param initial true means that it is the initial Header.
859 */
860 private void switchToHeader(Header header, boolean validate, boolean initial) {
861 if (header == null) {
862 return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800863 }
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800864 // For switching to another Header it should be a different one
865 if (mCurrentHeader == null || header.id != mCurrentHeader.id) {
866 if (header.fragment != null) {
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800867 boolean addToBackStack;
868 if (initial) {
869 addToBackStack = false;
870 } else {
871 if (header.id != mInitialHeader.id) {
872 addToBackStack = true;
873 } else {
874 addToBackStack = (mTopLevelHeaderId > 0);
875 }
876 }
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800877 switchToHeaderInner(header.fragment, header.fragmentArguments, validate,
878 addToBackStack, getHeaderTitle(header));
879 setSelectedHeader(header);
880 } else if (header.intent != null) {
881 setSelectedHeader(header);
882 startActivity(header.intent);
883 } else {
884 throw new IllegalStateException(
885 "Can't switch to header that has no Fragment nor Intent");
886 }
887 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800888 }
889
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000890 /**
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800891 * Switch the fragment pane to show the given preference fragment.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000892 *
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800893 * (used for initial fragment)
894 *
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000895 * @param fragmentName The name of the fragment to display.
896 * @param args Optional arguments to supply to the fragment.
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800897 * @param validate true means that the fragment's Header needs to be validated.
898 * @param title The title of the fragment to display.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000899 */
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800900 private void switchToHeader(String fragmentName, Bundle args, boolean validate,
901 CharSequence title) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000902 setSelectedHeader(null);
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800903 switchToHeaderInner(fragmentName, args, validate, false, title);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000904 }
905
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800906 /**
907 * Switch to a specific Header with taking care of validation, Title and BackStack
908 */
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800909 private void switchToHeaderInner(String fragmentName, Bundle args, boolean validate,
910 boolean addToBackStack, CharSequence title) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000911 getFragmentManager().popBackStack(BACK_STACK_PREFS,
912 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800913 if (validate && !isValidFragment(fragmentName)) {
914 throw new IllegalArgumentException("Invalid fragment for this activity: "
915 + fragmentName);
916 }
917 Fragment f = Fragment.instantiate(this, fragmentName, args);
918 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800919 transaction.replace(R.id.prefs, f);
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800920 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
921 if (addToBackStack) {
922 transaction.addToBackStack(BACK_STACK_PREFS);
923 }
924 if (title != null) {
925 transaction.setBreadCrumbTitle(title);
926 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800927 transaction.commitAllowingStateLoss();
928 }
929
930 @Override
931 public void onNewIntent(Intent intent) {
932 super.onNewIntent(intent);
933
934 // If it is not launched from history, then reset to top-level
935 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
936 if (mDrawer != null) {
937 mDrawer.setSelectionFromTop(0, 0);
938 }
939 }
940 }
941
942 /**
943 * Called to determine whether the header list should be hidden.
944 * The default implementation returns the
945 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
946 * This is set to false, for example, when the activity is being re-launched
947 * to show a particular preference activity.
948 */
949 public boolean onIsHidingHeaders() {
950 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
951 }
952
953 private void highlightHeader(int id) {
954 if (id != 0) {
955 Integer index = mHeaderIndexMap.get(id);
956 if (index != null && mDrawer != null) {
957 mDrawer.setItemChecked(index, true);
958 if (mDrawer.getVisibility() == View.VISIBLE) {
959 mDrawer.smoothScrollToPosition(index);
960 }
961 }
962 }
963 }
964
965 @Override
966 public Intent getIntent() {
967 Intent superIntent = super.getIntent();
968 String startingFragment = getStartingFragmentClass(superIntent);
969 // This is called from super.onCreate, isMultiPane() is not yet reliable
970 // Do not use onIsHidingHeaders either, which relies itself on this method
971 if (startingFragment != null) {
972 Intent modIntent = new Intent(superIntent);
973 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
974 Bundle args = superIntent.getExtras();
975 if (args != null) {
976 args = new Bundle(args);
977 } else {
978 args = new Bundle();
979 }
980 args.putParcelable("intent", superIntent);
981 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
982 return modIntent;
983 }
984 return superIntent;
985 }
986
987 /**
988 * Checks if the component name in the intent is different from the Settings class and
989 * returns the class name to load as a fragment.
990 */
991 private String getStartingFragmentClass(Intent intent) {
992 if (mFragmentClass != null) return mFragmentClass;
993
994 String intentClass = intent.getComponent().getClassName();
995 if (intentClass.equals(getClass().getName())) return null;
996
997 if ("com.android.settings.ManageApplications".equals(intentClass)
998 || "com.android.settings.RunningServices".equals(intentClass)
999 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
1000 // Old names of manage apps.
1001 intentClass = com.android.settings.applications.ManageApplications.class.getName();
1002 }
1003
1004 return intentClass;
1005 }
1006
1007 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001008 * Start a new fragment containing a preference panel. If the preferences
1009 * are being displayed in multi-pane mode, the given fragment class will
1010 * be instantiated and placed in the appropriate pane. If running in
1011 * single-pane mode, a new activity will be launched in which to show the
1012 * fragment.
1013 *
1014 * @param fragmentClass Full name of the class implementing the fragment.
1015 * @param args Any desired arguments to supply to the fragment.
1016 * @param titleRes Optional resource identifier of the title of this
1017 * fragment.
1018 * @param titleText Optional text of the title of this fragment.
1019 * @param resultTo Optional fragment that result data should be sent to.
1020 * If non-null, resultTo.onActivityResult() will be called when this
1021 * preference panel is done. The launched panel must use
1022 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
1023 * @param resultRequestCode If resultTo is non-null, this is the caller's
1024 * request code to be received with the resut.
1025 */
1026 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
1027 CharSequence titleText, Fragment resultTo,
1028 int resultRequestCode) {
1029 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
1030 }
1031
1032 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001033 * Called by a preference panel fragment to finish itself.
1034 *
1035 * @param caller The fragment that is asking to be finished.
1036 * @param resultCode Optional result code to send back to the original
1037 * launching fragment.
1038 * @param resultData Optional result data to send back to the original
1039 * launching fragment.
1040 */
1041 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
1042 setResult(resultCode, resultData);
1043 }
1044
1045 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001046 * Start a new fragment.
1047 *
1048 * @param fragment The fragment to start
1049 * @param push If true, the current fragment will be pushed onto the back stack. If false,
1050 * the current fragment will be replaced.
1051 */
1052 public void startPreferenceFragment(Fragment fragment, boolean push) {
1053 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1054 transaction.replace(R.id.prefs, fragment);
1055 if (push) {
1056 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1057 transaction.addToBackStack(BACK_STACK_PREFS);
1058 } else {
1059 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
1060 }
1061 transaction.commitAllowingStateLoss();
1062 }
1063
1064 /**
1065 * Start a new fragment.
1066 *
1067 * @param fragmentName The name of the fragment to display.
1068 * @param args Optional arguments to supply to the fragment.
1069 * @param resultTo Option fragment that should receive the result of
1070 * the activity launch.
1071 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
1072 * report the result.
1073 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
1074 * valid one then it will be used to get the title. Otherwise the titleText
1075 * argument will be used as the title.
1076 * @param titleText string to display for the title of.
1077 */
1078 private void startWithFragment(String fragmentName, Bundle args, Fragment resultTo,
1079 int resultRequestCode, int titleRes, CharSequence titleText) {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -08001080 final CharSequence cs;
1081 if (titleRes != 0) {
1082 cs = getText(titleRes);
1083 } else {
1084 cs = titleText;
1085 }
1086
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001087 Fragment f = Fragment.instantiate(this, fragmentName, args);
1088 if (resultTo != null) {
1089 f.setTargetFragment(resultTo, resultRequestCode);
1090 }
1091 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1092 transaction.replace(R.id.prefs, f);
1093 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1094 transaction.addToBackStack(BACK_STACK_PREFS);
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -08001095 transaction.setBreadCrumbTitle(cs);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001096 transaction.commitAllowingStateLoss();
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001097 }
1098
1099 /**
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -08001100 * Called when the activity needs its list of headers build.
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001101 *
1102 * @param headers The list in which to place the headers.
1103 */
1104 private void onBuildHeaders(List<Header> headers) {
1105 loadHeadersFromResource(R.xml.settings_headers, headers);
1106 updateHeaderList(headers);
1107 }
1108
1109 /**
1110 * Parse the given XML file as a header description, adding each
1111 * parsed Header into the target list.
1112 *
1113 * @param resid The XML resource to load and parse.
1114 * @param target The list in which the parsed headers should be placed.
1115 */
1116 private void loadHeadersFromResource(int resid, List<Header> target) {
1117 XmlResourceParser parser = null;
1118 try {
1119 parser = getResources().getXml(resid);
1120 AttributeSet attrs = Xml.asAttributeSet(parser);
1121
1122 int type;
1123 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1124 && type != XmlPullParser.START_TAG) {
1125 // Parse next until start tag is found
1126 }
1127
1128 String nodeName = parser.getName();
1129 if (!"preference-headers".equals(nodeName)) {
1130 throw new RuntimeException(
1131 "XML document must start with <preference-headers> tag; found"
1132 + nodeName + " at " + parser.getPositionDescription());
1133 }
1134
1135 Bundle curBundle = null;
1136
1137 final int outerDepth = parser.getDepth();
1138 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1139 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1140 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1141 continue;
1142 }
1143
1144 nodeName = parser.getName();
1145 if ("header".equals(nodeName)) {
1146 Header header = new Header();
1147
1148 TypedArray sa = obtainStyledAttributes(
1149 attrs, com.android.internal.R.styleable.PreferenceHeader);
1150 header.id = sa.getResourceId(
1151 com.android.internal.R.styleable.PreferenceHeader_id,
1152 (int)HEADER_ID_UNDEFINED);
1153 TypedValue tv = sa.peekValue(
1154 com.android.internal.R.styleable.PreferenceHeader_title);
1155 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1156 if (tv.resourceId != 0) {
1157 header.titleRes = tv.resourceId;
1158 } else {
1159 header.title = tv.string;
1160 }
1161 }
1162 tv = sa.peekValue(
1163 com.android.internal.R.styleable.PreferenceHeader_summary);
1164 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1165 if (tv.resourceId != 0) {
1166 header.summaryRes = tv.resourceId;
1167 } else {
1168 header.summary = tv.string;
1169 }
1170 }
1171 header.iconRes = sa.getResourceId(
1172 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
1173 header.fragment = sa.getString(
1174 com.android.internal.R.styleable.PreferenceHeader_fragment);
1175 sa.recycle();
1176
1177 if (curBundle == null) {
1178 curBundle = new Bundle();
1179 }
1180
1181 final int innerDepth = parser.getDepth();
1182 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1183 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
1184 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1185 continue;
1186 }
1187
1188 String innerNodeName = parser.getName();
1189 if (innerNodeName.equals("extra")) {
1190 getResources().parseBundleExtra("extra", attrs, curBundle);
1191 XmlUtils.skipCurrentTag(parser);
1192
1193 } else if (innerNodeName.equals("intent")) {
1194 header.intent = Intent.parseIntent(getResources(), parser, attrs);
1195
1196 } else {
1197 XmlUtils.skipCurrentTag(parser);
1198 }
1199 }
1200
1201 if (curBundle.size() > 0) {
1202 header.fragmentArguments = curBundle;
1203 curBundle = null;
1204 }
1205
1206 target.add(header);
1207 } else {
1208 XmlUtils.skipCurrentTag(parser);
1209 }
1210 }
1211
1212 } catch (XmlPullParserException e) {
1213 throw new RuntimeException("Error parsing headers", e);
1214 } catch (IOException e) {
1215 throw new RuntimeException("Error parsing headers", e);
1216 } finally {
1217 if (parser != null) parser.close();
1218 }
1219 }
1220
1221 private void updateHeaderList(List<Header> target) {
1222 final boolean showDev = mDevelopmentPreferences.getBoolean(
1223 DevelopmentSettings.PREF_SHOW,
1224 android.os.Build.TYPE.equals("eng"));
1225 int i = 0;
1226
1227 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1228 mHeaderIndexMap.clear();
1229 while (i < target.size()) {
1230 Header header = target.get(i);
1231 // Ids are integers, so downcasting
1232 int id = (int) header.id;
1233 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1234 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1235 } else if (id == R.id.wifi_settings) {
1236 // Remove WiFi Settings if WiFi service is not available.
1237 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1238 target.remove(i);
1239 }
1240 } else if (id == R.id.bluetooth_settings) {
1241 // Remove Bluetooth Settings if Bluetooth service is not available.
1242 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1243 target.remove(i);
1244 }
1245 } else if (id == R.id.data_usage_settings) {
1246 // Remove data usage when kernel module not enabled
1247 final INetworkManagementService netManager = INetworkManagementService.Stub
1248 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1249 try {
1250 if (!netManager.isBandwidthControlEnabled()) {
1251 target.remove(i);
1252 }
1253 } catch (RemoteException e) {
1254 // ignored
1255 }
1256 } else if (id == R.id.battery_settings) {
1257 // Remove battery settings when battery is not available. (e.g. TV)
1258
1259 if (!mBatteryPresent) {
1260 target.remove(i);
1261 }
1262 } else if (id == R.id.account_settings) {
1263 int headerIndex = i + 1;
1264 i = insertAccountsHeaders(target, headerIndex);
1265 } else if (id == R.id.home_settings) {
1266 if (!updateHomeSettingHeaders(header)) {
1267 target.remove(i);
1268 }
1269 } else if (id == R.id.user_settings) {
1270 if (!UserHandle.MU_ENABLED
1271 || !UserManager.supportsMultipleUsers()
1272 || Utils.isMonkeyRunning()) {
1273 target.remove(i);
1274 }
1275 } else if (id == R.id.nfc_payment_settings) {
1276 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1277 target.remove(i);
1278 } else {
1279 // Only show if NFC is on and we have the HCE feature
1280 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1281 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1282 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1283 target.remove(i);
1284 }
1285 }
1286 } else if (id == R.id.development_settings) {
1287 if (!showDev) {
1288 target.remove(i);
1289 }
1290 } else if (id == R.id.account_add) {
1291 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1292 target.remove(i);
1293 }
1294 }
1295
1296 if (i < target.size() && target.get(i) == header
1297 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1298 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1299 target.remove(i);
1300 }
1301
1302 // Increment if the current one wasn't removed by the Utils code.
1303 if (i < target.size() && target.get(i) == header) {
1304 // Hold on to the first header, when we need to reset to the top-level
1305 if (mFirstHeader == null &&
1306 HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
1307 mFirstHeader = header;
1308 }
1309 mHeaderIndexMap.put(id, i);
1310 i++;
1311 }
1312 }
1313 }
1314
1315 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1316 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1317 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1318 for (String accountType : accountTypes) {
1319 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1320 if (label == null) {
1321 continue;
1322 }
1323
1324 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1325 boolean skipToAccount = accounts.length == 1
1326 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1327 Header accHeader = new Header();
1328 accHeader.title = label;
1329 if (accHeader.extras == null) {
1330 accHeader.extras = new Bundle();
1331 }
1332 if (skipToAccount) {
1333 accHeader.fragment = AccountSyncSettings.class.getName();
1334 accHeader.fragmentArguments = new Bundle();
1335 // Need this for the icon
1336 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1337 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1338 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1339 accounts[0]);
1340 } else {
1341 accHeader.fragment = ManageAccountsSettings.class.getName();
1342 accHeader.fragmentArguments = new Bundle();
1343 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1344 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1345 accountType);
1346 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1347 label.toString());
1348 }
1349 accountHeaders.add(accHeader);
1350 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1351 }
1352
1353 // Sort by label
1354 Collections.sort(accountHeaders, new Comparator<Header>() {
1355 @Override
1356 public int compare(Header h1, Header h2) {
1357 return h1.title.toString().compareTo(h2.title.toString());
1358 }
1359 });
1360
1361 for (Header header : accountHeaders) {
1362 target.add(headerIndex++, header);
1363 }
1364 if (!mListeningToAccountUpdates) {
1365 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1366 mListeningToAccountUpdates = true;
1367 }
1368 return headerIndex;
1369 }
1370
1371 private boolean updateHomeSettingHeaders(Header header) {
1372 // Once we decide to show Home settings, keep showing it forever
1373 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1374 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1375 return true;
1376 }
1377
1378 try {
1379 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1380 getPackageManager().getHomeActivities(homeApps);
1381 if (homeApps.size() < 2) {
1382 // When there's only one available home app, omit this settings
1383 // category entirely at the top level UI. If the user just
1384 // uninstalled the penultimate home app candidiate, we also
1385 // now tell them about why they aren't seeing 'Home' in the list.
1386 if (sShowNoHomeNotice) {
1387 sShowNoHomeNotice = false;
1388 NoHomeDialogFragment.show(this);
1389 }
1390 return false;
1391 } else {
1392 // Okay, we're allowing the Home settings category. Tell it, when
1393 // invoked via this front door, that we'll need to be told about the
1394 // case when the user uninstalls all but one home app.
1395 if (header.fragmentArguments == null) {
1396 header.fragmentArguments = new Bundle();
1397 }
1398 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1399 }
1400 } catch (Exception e) {
1401 // Can't look up the home activity; bail on configuring the icon
1402 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1403 }
1404
1405 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1406 return true;
1407 }
1408
1409 private void getMetaData() {
1410 try {
1411 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1412 PackageManager.GET_META_DATA);
1413 if (ai == null || ai.metaData == null) return;
1414 mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
1415 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1416 } catch (NameNotFoundException nnfe) {
1417 // No recovery
1418 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1419 }
1420 }
1421
1422 // give subclasses access to the Next button
1423 public boolean hasNextButton() {
1424 return mNextButton != null;
1425 }
1426
1427 public Button getNextButton() {
1428 return mNextButton;
1429 }
1430
1431 public static class NoHomeDialogFragment extends DialogFragment {
1432 public static void show(Activity parent) {
1433 final NoHomeDialogFragment dialog = new NoHomeDialogFragment();
1434 dialog.show(parent.getFragmentManager(), null);
1435 }
1436
1437 @Override
1438 public Dialog onCreateDialog(Bundle savedInstanceState) {
1439 return new AlertDialog.Builder(getActivity())
1440 .setMessage(R.string.only_one_home_message)
1441 .setPositiveButton(android.R.string.ok, null)
1442 .create();
1443 }
1444 }
1445
1446 private static class HeaderAdapter extends ArrayAdapter<Header> {
1447 static final int HEADER_TYPE_CATEGORY = 0;
1448 static final int HEADER_TYPE_NORMAL = 1;
1449 static final int HEADER_TYPE_SWITCH = 2;
1450 static final int HEADER_TYPE_BUTTON = 3;
1451 private static final int HEADER_TYPE_COUNT = HEADER_TYPE_BUTTON + 1;
1452
1453 private final WifiEnabler mWifiEnabler;
1454 private final BluetoothEnabler mBluetoothEnabler;
1455 private AuthenticatorHelper mAuthHelper;
1456 private DevicePolicyManager mDevicePolicyManager;
1457
1458 private static class HeaderViewHolder {
1459 ImageView mIcon;
1460 TextView mTitle;
1461 TextView mSummary;
1462 Switch mSwitch;
1463 ImageButton mButton;
1464 View mDivider;
1465 }
1466
1467 private LayoutInflater mInflater;
1468
1469 static int getHeaderType(Header header) {
1470 if (header.fragment == null && header.intent == null) {
1471 return HEADER_TYPE_CATEGORY;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001472 } else if (header.id == R.id.security_settings) {
1473 return HEADER_TYPE_BUTTON;
1474 } else {
1475 return HEADER_TYPE_NORMAL;
1476 }
1477 }
1478
1479 @Override
1480 public int getItemViewType(int position) {
1481 Header header = getItem(position);
1482 return getHeaderType(header);
1483 }
1484
1485 @Override
1486 public boolean areAllItemsEnabled() {
1487 return false; // because of categories
1488 }
1489
1490 @Override
1491 public boolean isEnabled(int position) {
1492 return getItemViewType(position) != HEADER_TYPE_CATEGORY;
1493 }
1494
1495 @Override
1496 public int getViewTypeCount() {
1497 return HEADER_TYPE_COUNT;
1498 }
1499
1500 @Override
1501 public boolean hasStableIds() {
1502 return true;
1503 }
1504
1505 public HeaderAdapter(Context context, List<Header> objects,
1506 AuthenticatorHelper authenticatorHelper, DevicePolicyManager dpm) {
1507 super(context, 0, objects);
1508
1509 mAuthHelper = authenticatorHelper;
1510 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1511
1512 // Temp Switches provided as placeholder until the adapter replaces these with actual
1513 // Switches inflated from their layouts. Must be done before adapter is set in super
1514 mWifiEnabler = new WifiEnabler(context, new Switch(context));
1515 mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
1516 mDevicePolicyManager = dpm;
1517 }
1518
1519 @Override
1520 public View getView(int position, View convertView, ViewGroup parent) {
1521 HeaderViewHolder holder;
1522 Header header = getItem(position);
1523 int headerType = getHeaderType(header);
1524 View view = null;
1525
1526 if (convertView == null) {
1527 holder = new HeaderViewHolder();
1528 switch (headerType) {
1529 case HEADER_TYPE_CATEGORY:
1530 view = new TextView(getContext(), null,
1531 android.R.attr.listSeparatorTextViewStyle);
1532 holder.mTitle = (TextView) view;
1533 break;
1534
1535 case HEADER_TYPE_SWITCH:
1536 view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
1537 false);
1538 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1539 holder.mTitle = (TextView)
1540 view.findViewById(com.android.internal.R.id.title);
1541 holder.mSummary = (TextView)
1542 view.findViewById(com.android.internal.R.id.summary);
1543 holder.mSwitch = (Switch) view.findViewById(R.id.switchWidget);
1544 break;
1545
1546 case HEADER_TYPE_BUTTON:
1547 view = mInflater.inflate(R.layout.preference_header_button_item, parent,
1548 false);
1549 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1550 holder.mTitle = (TextView)
1551 view.findViewById(com.android.internal.R.id.title);
1552 holder.mSummary = (TextView)
1553 view.findViewById(com.android.internal.R.id.summary);
1554 holder.mButton = (ImageButton) view.findViewById(R.id.buttonWidget);
1555 holder.mDivider = view.findViewById(R.id.divider);
1556 break;
1557
1558 case HEADER_TYPE_NORMAL:
1559 view = mInflater.inflate(
1560 R.layout.preference_header_item, parent,
1561 false);
1562 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1563 holder.mTitle = (TextView)
1564 view.findViewById(com.android.internal.R.id.title);
1565 holder.mSummary = (TextView)
1566 view.findViewById(com.android.internal.R.id.summary);
1567 break;
1568 }
Fabrice Di Meglio0e2f9492014-02-25 14:26:27 -08001569 if (holder.mIcon != null) {
1570 holder.mIcon.setBackgroundResource(R.color.background_drawer_icon);
1571 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001572 view.setTag(holder);
1573 } else {
1574 view = convertView;
1575 holder = (HeaderViewHolder) view.getTag();
1576 }
1577
1578 // All view fields must be updated every time, because the view may be recycled
1579 switch (headerType) {
1580 case HEADER_TYPE_CATEGORY:
1581 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1582 break;
1583
1584 case HEADER_TYPE_SWITCH:
1585 // Would need a different treatment if the main menu had more switches
1586 if (header.id == R.id.wifi_settings) {
1587 mWifiEnabler.setSwitch(holder.mSwitch);
1588 } else {
1589 mBluetoothEnabler.setSwitch(holder.mSwitch);
1590 }
1591 updateCommonHeaderView(header, holder);
1592 break;
1593
1594 case HEADER_TYPE_BUTTON:
1595 if (header.id == R.id.security_settings) {
1596 boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled();
1597 if (hasCert) {
1598 holder.mButton.setVisibility(View.VISIBLE);
1599 holder.mDivider.setVisibility(View.VISIBLE);
1600 boolean isManaged = mDevicePolicyManager.getDeviceOwner() != null;
1601 if (isManaged) {
1602 holder.mButton.setImageResource(R.drawable.ic_settings_about);
1603 } else {
1604 holder.mButton.setImageResource(
1605 android.R.drawable.stat_notify_error);
1606 }
1607 holder.mButton.setOnClickListener(new OnClickListener() {
1608 @Override
1609 public void onClick(View v) {
1610 Intent intent = new Intent(
1611 android.provider.Settings.ACTION_MONITORING_CERT_INFO);
1612 getContext().startActivity(intent);
1613 }
1614 });
1615 } else {
1616 holder.mButton.setVisibility(View.GONE);
1617 holder.mDivider.setVisibility(View.GONE);
1618 }
1619 }
1620 updateCommonHeaderView(header, holder);
1621 break;
1622
1623 case HEADER_TYPE_NORMAL:
1624 updateCommonHeaderView(header, holder);
1625 break;
1626 }
1627
1628 return view;
1629 }
1630
1631 private void updateCommonHeaderView(Header header, HeaderViewHolder holder) {
1632 if (header.extras != null
1633 && header.extras.containsKey(ManageAccountsSettings.KEY_ACCOUNT_TYPE)) {
1634 String accType = header.extras.getString(
1635 ManageAccountsSettings.KEY_ACCOUNT_TYPE);
1636 Drawable icon = mAuthHelper.getDrawableForType(getContext(), accType);
1637 setHeaderIcon(holder, icon);
1638 } else {
1639 holder.mIcon.setImageResource(header.iconRes);
1640 }
1641 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1642 CharSequence summary = header.getSummary(getContext().getResources());
1643 if (!TextUtils.isEmpty(summary)) {
1644 holder.mSummary.setVisibility(View.VISIBLE);
1645 holder.mSummary.setText(summary);
1646 } else {
1647 holder.mSummary.setVisibility(View.GONE);
1648 }
1649 }
1650
1651 private void setHeaderIcon(HeaderViewHolder holder, Drawable icon) {
1652 ViewGroup.LayoutParams lp = holder.mIcon.getLayoutParams();
1653 lp.width = getContext().getResources().getDimensionPixelSize(
1654 R.dimen.header_icon_width);
1655 lp.height = lp.width;
1656 holder.mIcon.setLayoutParams(lp);
1657 holder.mIcon.setImageDrawable(icon);
1658 }
1659
Matthew Xiea504c4d2014-02-14 16:32:32 -08001660 public void resume(Context context) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001661 mWifiEnabler.resume();
Matthew Xiea504c4d2014-02-14 16:32:32 -08001662 mBluetoothEnabler.resume(context);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001663 }
1664
1665 public void pause() {
1666 mWifiEnabler.pause();
1667 mBluetoothEnabler.pause();
1668 }
1669 }
1670
1671 private void onListItemClick(ListView l, View v, int position, long id) {
1672 if (!isResumed()) {
1673 return;
1674 }
1675 Object item = mHeaderAdapter.getItem(position);
1676 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001677 mSelectedHeader = (Header) item;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001678 }
1679 }
1680
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001681 @Override
1682 public boolean shouldUpRecreateTask(Intent targetIntent) {
1683 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1684 }
1685
1686 @Override
1687 public void onAccountsUpdated(Account[] accounts) {
1688 // TODO: watch for package upgrades to invalidate cache; see 7206643
1689 mAuthenticatorHelper.updateAuthDescriptions(this);
1690 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1691 invalidateHeaders();
1692 }
1693
1694 public static void requestHomeNotice() {
1695 sShowNoHomeNotice = true;
1696 }
1697
1698 /**
1699 * Default value for {@link Header#id Header.id} indicating that no
1700 * identifier value is set. All other values (including those below -1)
1701 * are valid.
1702 */
1703 private static final long HEADER_ID_UNDEFINED = -1;
1704
1705 /**
1706 * Description of a single Header item that the user can select.
1707 */
1708 static final class Header implements Parcelable {
1709 /**
1710 * Identifier for this header, to correlate with a new list when
1711 * it is updated. The default value is
1712 * {@link SettingsActivity#HEADER_ID_UNDEFINED}, meaning no id.
1713 * @attr ref android.R.styleable#PreferenceHeader_id
1714 */
1715 public long id = HEADER_ID_UNDEFINED;
1716
1717 /**
1718 * Resource ID of title of the header that is shown to the user.
1719 * @attr ref android.R.styleable#PreferenceHeader_title
1720 */
1721 public int titleRes;
1722
1723 /**
1724 * Title of the header that is shown to the user.
1725 * @attr ref android.R.styleable#PreferenceHeader_title
1726 */
1727 public CharSequence title;
1728
1729 /**
1730 * Resource ID of optional summary describing what this header controls.
1731 * @attr ref android.R.styleable#PreferenceHeader_summary
1732 */
1733 public int summaryRes;
1734
1735 /**
1736 * Optional summary describing what this header controls.
1737 * @attr ref android.R.styleable#PreferenceHeader_summary
1738 */
1739 public CharSequence summary;
1740
1741 /**
1742 * Optional icon resource to show for this header.
1743 * @attr ref android.R.styleable#PreferenceHeader_icon
1744 */
1745 public int iconRes;
1746
1747 /**
1748 * Full class name of the fragment to display when this header is
1749 * selected.
1750 * @attr ref android.R.styleable#PreferenceHeader_fragment
1751 */
1752 public String fragment;
1753
1754 /**
1755 * Optional arguments to supply to the fragment when it is
1756 * instantiated.
1757 */
1758 public Bundle fragmentArguments;
1759
1760 /**
1761 * Intent to launch when the preference is selected.
1762 */
1763 public Intent intent;
1764
1765 /**
1766 * Optional additional data for use by subclasses of the activity
1767 */
1768 public Bundle extras;
1769
1770 public Header() {
1771 // Empty
1772 }
1773
1774 /**
1775 * Return the currently set title. If {@link #titleRes} is set,
1776 * this resource is loaded from <var>res</var> and returned. Otherwise
1777 * {@link #title} is returned.
1778 */
1779 public CharSequence getTitle(Resources res) {
1780 if (titleRes != 0) {
1781 return res.getText(titleRes);
1782 }
1783 return title;
1784 }
1785
1786 /**
1787 * Return the currently set summary. If {@link #summaryRes} is set,
1788 * this resource is loaded from <var>res</var> and returned. Otherwise
1789 * {@link #summary} is returned.
1790 */
1791 public CharSequence getSummary(Resources res) {
1792 if (summaryRes != 0) {
1793 return res.getText(summaryRes);
1794 }
1795 return summary;
1796 }
1797
1798 @Override
1799 public int describeContents() {
1800 return 0;
1801 }
1802
1803 @Override
1804 public void writeToParcel(Parcel dest, int flags) {
1805 dest.writeLong(id);
1806 dest.writeInt(titleRes);
1807 TextUtils.writeToParcel(title, dest, flags);
1808 dest.writeInt(summaryRes);
1809 TextUtils.writeToParcel(summary, dest, flags);
1810 dest.writeInt(iconRes);
1811 dest.writeString(fragment);
1812 dest.writeBundle(fragmentArguments);
1813 if (intent != null) {
1814 dest.writeInt(1);
1815 intent.writeToParcel(dest, flags);
1816 } else {
1817 dest.writeInt(0);
1818 }
1819 dest.writeBundle(extras);
1820 }
1821
1822 public void readFromParcel(Parcel in) {
1823 id = in.readLong();
1824 titleRes = in.readInt();
1825 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1826 summaryRes = in.readInt();
1827 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1828 iconRes = in.readInt();
1829 fragment = in.readString();
1830 fragmentArguments = in.readBundle();
1831 if (in.readInt() != 0) {
1832 intent = Intent.CREATOR.createFromParcel(in);
1833 }
1834 extras = in.readBundle();
1835 }
1836
1837 Header(Parcel in) {
1838 readFromParcel(in);
1839 }
1840
1841 public static final Creator<Header> CREATOR = new Creator<Header>() {
1842 public Header createFromParcel(Parcel source) {
1843 return new Header(source);
1844 }
1845 public Header[] newArray(int size) {
1846 return new Header[size];
1847 }
1848 };
1849 }
1850}