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