blob: 8582bc67ab870229eb1cebc00e83d2df1b6444f0 [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 Meglio263bcc82014-01-17 19:17:58 -0800136
137 /**
138 * When starting this activity, the invoking Intent can contain this extra
139 * string to specify which fragment should be initially displayed.
140 * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
141 * will call isValidFragment() to confirm that the fragment class name is valid for this
142 * activity.
143 */
144 public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
145
146 /**
147 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
148 * this extra can also be specified to supply a Bundle of arguments to pass
149 * to that fragment when it is instantiated during the initial creation
150 * of the activity.
151 */
152 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
153
154 /**
155 * When starting this activity, the invoking Intent can contain this extra
156 * boolean that the header list should not be displayed. This is most often
157 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
158 * the activity to display a specific fragment that the user has navigated
159 * to.
160 */
161 public static final String EXTRA_NO_HEADERS = ":settings:no_headers";
162
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800163 public static final String BACK_STACK_PREFS = ":settings:prefs";
164
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800165 // extras that allow any preference activity to be launched as part of a wizard
166
167 // show Back and Next buttons? takes boolean parameter
168 // Back will then return RESULT_CANCELED and Next RESULT_OK
169 protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
170
171 // add a Skip button?
172 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
173
174 // specify custom text for the Back or Next buttons, or cause a button to not appear
175 // at all by setting it to null
176 protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
177 protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
178
179 /**
180 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
181 * this extra can also be specify to supply the title to be shown for
182 * that fragment.
183 */
184 protected static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
185
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800186 private static final String META_DATA_KEY_HEADER_ID =
187 "com.android.settings.TOP_LEVEL_HEADER_ID";
188
189 private static final String META_DATA_KEY_FRAGMENT_CLASS =
190 "com.android.settings.FRAGMENT_CLASS";
191
192 private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
193
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800194 private static boolean sShowNoHomeNotice = false;
195
196 private String mFragmentClass;
197 private int mTopLevelHeaderId;
198 private Header mFirstHeader;
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800199 private Header mSelectedHeader;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800200 private Header mCurrentHeader;
201
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800202 private CharSequence mInitialTitle;
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800203 private Header mInitialHeader;
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800204
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800205 // Show only these settings for restricted users
206 private int[] SETTINGS_FOR_RESTRICTED = {
207 R.id.wireless_section,
208 R.id.wifi_settings,
209 R.id.bluetooth_settings,
210 R.id.data_usage_settings,
211 R.id.wireless_settings,
212 R.id.device_section,
213 R.id.sound_settings,
214 R.id.display_settings,
215 R.id.storage_settings,
216 R.id.application_settings,
217 R.id.battery_settings,
218 R.id.personal_section,
219 R.id.location_settings,
220 R.id.security_settings,
221 R.id.language_settings,
222 R.id.user_settings,
223 R.id.account_settings,
224 R.id.account_add,
225 R.id.system_section,
226 R.id.date_time_settings,
227 R.id.about_settings,
228 R.id.accessibility_settings,
229 R.id.print_settings,
230 R.id.nfc_payment_settings,
Fabrice Di Meglio2858b792014-02-18 19:35:08 -0800231 R.id.home_settings,
232 R.id.dashboard
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800233 };
234
235 private static final String[] ENTRY_FRAGMENTS = {
236 WirelessSettings.class.getName(),
237 WifiSettings.class.getName(),
238 AdvancedWifiSettings.class.getName(),
239 BluetoothSettings.class.getName(),
240 TetherSettings.class.getName(),
241 WifiP2pSettings.class.getName(),
242 VpnSettings.class.getName(),
243 DateTimeSettings.class.getName(),
244 LocalePicker.class.getName(),
245 InputMethodAndLanguageSettings.class.getName(),
246 SpellCheckersSettings.class.getName(),
247 UserDictionaryList.class.getName(),
248 UserDictionarySettings.class.getName(),
249 SoundSettings.class.getName(),
250 DisplaySettings.class.getName(),
251 DeviceInfoSettings.class.getName(),
252 ManageApplications.class.getName(),
253 ProcessStatsUi.class.getName(),
254 NotificationStation.class.getName(),
255 LocationSettings.class.getName(),
256 SecuritySettings.class.getName(),
257 PrivacySettings.class.getName(),
258 DeviceAdminSettings.class.getName(),
259 AccessibilitySettings.class.getName(),
260 CaptionPropertiesFragment.class.getName(),
261 com.android.settings.accessibility.ToggleInversionPreferenceFragment.class.getName(),
262 com.android.settings.accessibility.ToggleContrastPreferenceFragment.class.getName(),
263 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
264 TextToSpeechSettings.class.getName(),
265 Memory.class.getName(),
266 DevelopmentSettings.class.getName(),
267 UsbSettings.class.getName(),
268 AndroidBeam.class.getName(),
269 WifiDisplaySettings.class.getName(),
270 PowerUsageSummary.class.getName(),
271 AccountSyncSettings.class.getName(),
272 CryptKeeperSettings.class.getName(),
273 DataUsageSummary.class.getName(),
274 DreamSettings.class.getName(),
275 UserSettings.class.getName(),
276 NotificationAccessSettings.class.getName(),
277 ManageAccountsSettings.class.getName(),
278 PrintSettingsFragment.class.getName(),
279 PrintJobSettingsFragment.class.getName(),
280 TrustedCredentialsSettings.class.getName(),
281 PaymentSettings.class.getName(),
282 KeyboardLayoutPickerFragment.class.getName(),
John Spurlock72438062014-02-27 18:01:20 -0500283 DashboardSummary.class.getName(),
284 ZenModeSettings.class.getName()
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800285 };
286
287 private SharedPreferences mDevelopmentPreferences;
288 private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
289
290 // TODO: Update Call Settings based on airplane mode state.
291
292 protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
293
294 private AuthenticatorHelper mAuthenticatorHelper;
295 private boolean mListeningToAccountUpdates;
296
297 private Button mNextButton;
298
299 private boolean mBatteryPresent = true;
300 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
301
302 @Override
303 public void onReceive(Context context, Intent intent) {
304 String action = intent.getAction();
305 if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
306 boolean batteryPresent = Utils.isBatteryPresent(intent);
307
308 if (mBatteryPresent != batteryPresent) {
309 mBatteryPresent = batteryPresent;
310 invalidateHeaders();
311 }
312 }
313 }
314 };
315
316 private final ArrayList<Header> mHeaders = new ArrayList<Header>();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800317 private HeaderAdapter mHeaderAdapter;
318
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800319 private DrawerLayout mDrawerLayout;
320 private ListView mDrawer;
321 private ActionBarDrawerToggle mDrawerToggle;
322 private ActionBar mActionBar;
323
324 private static final int MSG_BUILD_HEADERS = 1;
325 private Handler mHandler = new Handler() {
326 @Override
327 public void handleMessage(Message msg) {
328 switch (msg.what) {
329 case MSG_BUILD_HEADERS: {
330 mHeaders.clear();
331 onBuildHeaders(mHeaders);
332 mHeaderAdapter.notifyDataSetChanged();
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800333 if (mCurrentHeader != null) {
334 Header mappedHeader = findBestMatchingHeader(mCurrentHeader, mHeaders);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800335 if (mappedHeader != null) {
336 setSelectedHeader(mappedHeader);
337 }
338 }
339 } break;
340 }
341 }
342 };
343
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800344 /**
345 * Searchable data description.
346 *
347 * Known restriction: we are only searching (for now) the first level of Settings.
348 */
349 private static IndexableData[] INDEXABLE_DATA = new IndexableData[] {
350 new IndexableData(1, R.xml.wifi_settings,
351 "com.android.settings.wifi.WifiSettings",
352 R.drawable.ic_settings_wireless),
353 new IndexableData(2, R.xml.bluetooth_settings,
354 "com.android.settings.bluetooth.BluetoothSettings",
355 R.drawable.ic_settings_bluetooth2),
356 new IndexableData(3, R.xml.data_usage_metered_prefs,
357 "com.android.settings.net.DataUsageMeteredSettings",
358 R.drawable.ic_settings_data_usage),
359 new IndexableData(4, R.xml.wireless_settings,
360 "com.android.settings.WirelessSettings",
361 R.drawable.empty_icon),
362 new IndexableData(5, R.xml.home_selection,
363 "com.android.settings.HomeSettings",
364 R.drawable.ic_settings_home),
365 new IndexableData(6, R.xml.sound_settings,
366 "com.android.settings.SoundSettings",
367 R.drawable.ic_settings_sound),
368 new IndexableData(7, R.xml.display_settings,
369 "com.android.settings.DisplaySettings",
370 R.drawable.ic_settings_display),
371 new IndexableData(8, R.xml.device_info_memory,
372 "com.android.settings.deviceinfo.Memory",
373 R.drawable.ic_settings_storage),
374 new IndexableData(9, R.xml.power_usage_summary,
375 "com.android.settings.fuelgauge.PowerUsageSummary",
376 R.drawable.ic_settings_battery),
377 new IndexableData(10, R.xml.user_settings,
378 "com.android.settings.users.UserSettings",
379 R.drawable.ic_settings_multiuser),
380 new IndexableData(11, R.xml.location_settings,
381 "com.android.settings.location.LocationSettings",
382 R.drawable.ic_settings_location),
383 new IndexableData(12, R.xml.security_settings,
384 "com.android.settings.SecuritySettings",
385 R.drawable.ic_settings_security),
386 new IndexableData(13, R.xml.language_settings,
387 "com.android.settings.inputmethod.InputMethodAndLanguageSettings",
388 R.drawable.ic_settings_language),
389 new IndexableData(14, R.xml.privacy_settings,
390 "com.android.settings.PrivacySettings",
391 R.drawable.ic_settings_backup),
392 new IndexableData(15, R.xml.date_time_prefs,
393 "com.android.settings.DateTimeSettings",
394 R.drawable.ic_settings_date_time),
395 new IndexableData(16, R.xml.accessibility_settings,
396 "com.android.settings.accessibility.AccessibilitySettings",
397 R.drawable.ic_settings_accessibility),
398 new IndexableData(17, R.xml.print_settings,
399 "com.android.settings.print.PrintSettingsFragment",
400 com.android.internal.R.drawable.ic_print),
401 new IndexableData(18, R.xml.development_prefs,
402 "com.android.settings.DevelopmentSettings",
403 R.drawable.ic_settings_development),
404 new IndexableData(19, R.xml.device_info_settings,
405 "com.android.settings.DeviceInfoSettings",
406 R.drawable.ic_settings_about),
407 };
408
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800409 @Override
410 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
411 // Override the fragment title for Wallpaper settings
412 int titleRes = pref.getTitleRes();
413 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
414 titleRes = R.string.wallpaper_settings_fragment_title;
415 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
416 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
417 if (UserManager.get(this).isLinkedUser()) {
418 titleRes = R.string.profile_info_settings_title;
419 } else {
420 titleRes = R.string.user_info_settings_title;
421 }
422 }
423 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
424 null, 0);
425 return true;
426 }
427
428 @Override
429 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
430 return false;
431 }
432
433 private class DrawerListener implements DrawerLayout.DrawerListener {
434 @Override
435 public void onDrawerOpened(View drawerView) {
436 mDrawerToggle.onDrawerOpened(drawerView);
437 }
438
439 @Override
440 public void onDrawerClosed(View drawerView) {
441 mDrawerToggle.onDrawerClosed(drawerView);
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800442 // Cannot process clicks when the App is finishing
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800443 if (isFinishing() || mSelectedHeader == null) {
444 return;
445 }
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800446 switchToHeader(mSelectedHeader, false, false);
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800447 mSelectedHeader = null;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800448 }
449
450 @Override
451 public void onDrawerSlide(View drawerView, float slideOffset) {
452 mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
453 }
454
455 @Override
456 public void onDrawerStateChanged(int newState) {
457 mDrawerToggle.onDrawerStateChanged(newState);
458 }
459 }
460
461 private class DrawerItemClickListener implements ListView.OnItemClickListener {
462 @Override
463 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
464 mDrawerLayout.closeDrawer(mDrawer);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000465 onListItemClick((ListView)parent, view, position, id);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800466 }
467 }
468
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800469 private Header findBestMatchingHeader(Header current, ArrayList<Header> from) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800470 ArrayList<Header> matches = new ArrayList<Header>();
471 for (int j=0; j<from.size(); j++) {
472 Header oh = from.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800473 if (current == oh || (current.id != HEADER_ID_UNDEFINED && current.id == oh.id)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800474 // Must be this one.
475 matches.clear();
476 matches.add(oh);
477 break;
478 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800479 if (current.fragment != null) {
480 if (current.fragment.equals(oh.fragment)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800481 matches.add(oh);
482 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800483 } else if (current.intent != null) {
484 if (current.intent.equals(oh.intent)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800485 matches.add(oh);
486 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800487 } else if (current.title != null) {
488 if (current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800489 matches.add(oh);
490 }
491 }
492 }
493 final int NM = matches.size();
494 if (NM == 1) {
495 return matches.get(0);
496 } else if (NM > 1) {
497 for (int j=0; j<NM; j++) {
498 Header oh = matches.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800499 if (current.fragmentArguments != null &&
500 current.fragmentArguments.equals(oh.fragmentArguments)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800501 return oh;
502 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800503 if (current.extras != null && current.extras.equals(oh.extras)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800504 return oh;
505 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800506 if (current.title != null && current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800507 return oh;
508 }
509 }
510 }
511 return null;
512 }
513
514 private void invalidateHeaders() {
515 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
516 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
517 }
518 }
519
520 @Override
521 protected void onPostCreate(Bundle savedInstanceState) {
522 super.onPostCreate(savedInstanceState);
523
524 // Sync the toggle state after onRestoreInstanceState has occurred.
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800525 mDrawerToggle.syncState();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800526 }
527
528 @Override
529 public void onConfigurationChanged(Configuration newConfig) {
530 super.onConfigurationChanged(newConfig);
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800531 mDrawerToggle.onConfigurationChanged(newConfig);
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800532 Index.getInstance(this).update();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800533 }
534
535 @Override
536 public boolean onOptionsItemSelected(MenuItem item) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800537 if (mDrawerToggle.onOptionsItemSelected(item)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800538 return true;
539 }
540 return super.onOptionsItemSelected(item);
541 }
542
543 @Override
544 protected void onCreate(Bundle savedInstanceState) {
545 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
546 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
547 }
548
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800549 Index.getInstance(this).addIndexableData(INDEXABLE_DATA);
550 Index.getInstance(this).update();
551
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800552 mAuthenticatorHelper = new AuthenticatorHelper();
553 mAuthenticatorHelper.updateAuthDescriptions(this);
554 mAuthenticatorHelper.onAccountsUpdated(this, null);
555
556 DevicePolicyManager dpm =
557 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
Fabrice Di Megliodca28062014-02-21 17:42:56 -0800558
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800559 mHeaderAdapter= new HeaderAdapter(this, mHeaders, mAuthenticatorHelper, dpm);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800560
561 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
562 Context.MODE_PRIVATE);
563
564 getMetaData();
565
566 super.onCreate(savedInstanceState);
567
568 setContentView(R.layout.settings_main);
569
570 getFragmentManager().addOnBackStackChangedListener(this);
571
572 mActionBar = getActionBar();
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800573 mActionBar.setDisplayHomeAsUpEnabled(true);
574 mActionBar.setHomeButtonEnabled(true);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800575
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800576 mDrawer = (ListView) findViewById(R.id.headers_drawer);
577 mDrawer.setAdapter(mHeaderAdapter);
578 mDrawer.setOnItemClickListener(new DrawerItemClickListener());
579 mDrawer.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800580
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800581 mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
582 mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
583 R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800584
585 if (savedInstanceState != null) {
586 // We are restarting from a previous saved state; used that to
587 // initialize, instead of starting fresh.
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800588 mInitialTitle = getTitle();
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800589
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800590 ArrayList<Header> headers =
591 savedInstanceState.getParcelableArrayList(SAVE_KEY_HEADERS_TAG);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800592 if (headers != null) {
593 mHeaders.addAll(headers);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800594 int curHeader = savedInstanceState.getInt(SAVE_KEY_CURRENT_HEADER_TAG,
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800595 (int) HEADER_ID_UNDEFINED);
596 if (curHeader >= 0 && curHeader < mHeaders.size()) {
597 setSelectedHeader(mHeaders.get(curHeader));
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800598 mInitialHeader = mCurrentHeader;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800599 }
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700600 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800601 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800602 } else {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800603 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
604 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
605
606 // We need to build the Headers in all cases
607 onBuildHeaders(mHeaders);
608
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800609 if (initialFragment != null) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000610 // If we are just showing a fragment, we want to run in
611 // new fragment mode, but don't need to compute and show
612 // the headers.
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800613 final int initialTitleResId = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
614 mInitialTitle = (initialTitleResId > 0) ? getText(initialTitleResId) : getTitle();
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800615 setTitle(mInitialTitle);
616 switchToHeaderInner(initialFragment, initialArguments, true, false, mInitialTitle);
Fabrice Di Meglio75eabc22014-03-07 18:17:03 -0800617 setSelectedHeaderById(mTopLevelHeaderId);
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800618 mInitialHeader = mCurrentHeader;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000619 } else {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000620 // If there are headers, then at this point we need to show
621 // them and, depending on the screen, we may also show in-line
622 // the currently selected preference fragment.
623 if (mHeaders.size() > 0) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800624 mInitialHeader = onGetInitialHeader();
625 mInitialTitle = getHeaderTitle(mInitialHeader);
626 switchToHeader(mInitialHeader, false, true);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000627 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800628 }
629 }
630
631 // see if we should show Back/Next buttons
632 Intent intent = getIntent();
633 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
634
635 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
636 if (buttonBar != null) {
637 buttonBar.setVisibility(View.VISIBLE);
638
639 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
640 backButton.setOnClickListener(new OnClickListener() {
641 public void onClick(View v) {
642 setResult(RESULT_CANCELED);
643 finish();
644 }
645 });
646 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
647 skipButton.setOnClickListener(new OnClickListener() {
648 public void onClick(View v) {
649 setResult(RESULT_OK);
650 finish();
651 }
652 });
653 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
654 mNextButton.setOnClickListener(new OnClickListener() {
655 public void onClick(View v) {
656 setResult(RESULT_OK);
657 finish();
658 }
659 });
660
661 // set our various button parameters
662 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
663 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
664 if (TextUtils.isEmpty(buttonText)) {
665 mNextButton.setVisibility(View.GONE);
666 }
667 else {
668 mNextButton.setText(buttonText);
669 }
670 }
671 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
672 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
673 if (TextUtils.isEmpty(buttonText)) {
674 backButton.setVisibility(View.GONE);
675 }
676 else {
677 backButton.setText(buttonText);
678 }
679 }
680 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
681 skipButton.setVisibility(View.VISIBLE);
682 }
683 }
684 }
685
686 if (!onIsHidingHeaders()) {
687 highlightHeader(mTopLevelHeaderId);
688 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800689 }
690
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800691 public Header onGetInitialHeader() {
692 String fragmentClass = getStartingFragmentClass(super.getIntent());
693 if (fragmentClass != null) {
694 Header header = new Header();
695 header.fragment = fragmentClass;
696 header.title = getTitle();
697 header.fragmentArguments = getIntent().getExtras();
698 return header;
699 }
700
701 return mFirstHeader;
702 }
703
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800704 @Override
Fabrice Di Meglioc95be4f2014-03-07 12:57:38 -0800705 public void onBackPressed() {
706 if (mDrawerLayout.isDrawerOpen(mDrawer)) {
707 mDrawerLayout.closeDrawer(mDrawer);
708 return;
709 }
710 super.onBackPressed();
711 }
712
713 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800714 public void onBackStackChanged() {
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700715 if (setTitleFromBackStack() == 0) {
716 setSelectedHeaderById(mInitialHeader.id);
717 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800718 }
719
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700720 private int setTitleFromBackStack() {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800721 final int count = getFragmentManager().getBackStackEntryCount();
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700722
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800723 if (count == 0) {
724 setTitle(mInitialTitle);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700725 return 0;
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800726 }
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700727
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800728 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1);
729 setTitleFromBackStackEntry(bse);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700730
731 return count;
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800732 }
733
734 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
735 final CharSequence title;
736 final int titleRes = bse.getBreadCrumbTitleRes();
737 if (titleRes > 0) {
738 title = getText(titleRes);
739 } else {
740 title = bse.getBreadCrumbTitle();
741 }
742 if (title != null) {
743 setTitle(title);
744 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800745 }
746
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800747 @Override
748 protected void onSaveInstanceState(Bundle outState) {
749 super.onSaveInstanceState(outState);
750
751 if (mHeaders.size() > 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800752 outState.putParcelableArrayList(SAVE_KEY_HEADERS_TAG, mHeaders);
753 if (mCurrentHeader != null) {
754 int index = mHeaders.indexOf(mCurrentHeader);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800755 if (index >= 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800756 outState.putInt(SAVE_KEY_CURRENT_HEADER_TAG, index);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800757 }
758 }
759 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800760 }
761
762 @Override
763 public void onResume() {
764 super.onResume();
765
766 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
767 @Override
768 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
769 invalidateHeaders();
770 }
771 };
772 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
773 mDevelopmentPreferencesListener);
774
Matthew Xiea504c4d2014-02-14 16:32:32 -0800775 mHeaderAdapter.resume(this);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800776 invalidateHeaders();
777
778 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800779
780 mDrawerLayout.setDrawerListener(new DrawerListener());
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800781 }
782
783 @Override
784 public void onPause() {
785 super.onPause();
786
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800787 mDrawerLayout.setDrawerListener(null);
788
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800789 unregisterReceiver(mBatteryInfoReceiver);
790
791 mHeaderAdapter.pause();
792
793 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
794 mDevelopmentPreferencesListener);
795
796 mDevelopmentPreferencesListener = null;
797 }
798
799 @Override
800 public void onDestroy() {
801 super.onDestroy();
802 if (mListeningToAccountUpdates) {
803 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
804 }
805 }
806
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800807 protected boolean isValidFragment(String fragmentName) {
808 // Almost all fragments are wrapped in this,
809 // except for a few that have their own activities.
810 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
811 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
812 }
813 return false;
814 }
815
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800816 private CharSequence getHeaderTitle(Header header) {
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800817 if (header == null || header.fragment == null) return getTitle();
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800818 final CharSequence title;
819 if (header.fragment.equals(DashboardSummary.class.getName())) {
820 title = getResources().getString(R.string.settings_label);
821 } else {
822 title = header.getTitle(getResources());
823 }
824 return title;
825 }
826
Fabrice Di Meglio75eabc22014-03-07 18:17:03 -0800827 private void setSelectedHeaderById(long headerId) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800828 final int count = mHeaders.size();
829 for (int n = 0; n < count; n++) {
830 Header h = mHeaders.get(n);
Fabrice Di Meglio75eabc22014-03-07 18:17:03 -0800831 if (h.id == headerId) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800832 setSelectedHeader(h);
833 return;
834 }
835 }
836 }
837
Fabrice Di Meglio75eabc22014-03-07 18:17:03 -0800838 /**
839 * As the Headers can be rebuilt, their references can change, so use this method with caution!
840 */
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800841 private void setSelectedHeader(Header header) {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800842 if (header == null) {
843 mCurrentHeader = null;
844 return;
845 }
846 // Update selected Header into Drawer only if it is not "Add Account"
847 if (header.id == R.id.account_add) {
848 mDrawer.clearChoices();
849 return;
850 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800851 mCurrentHeader = header;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800852 int index = mHeaders.indexOf(header);
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800853 if (index >= 0) {
854 mDrawer.setItemChecked(index, true);
855 } else {
856 mDrawer.clearChoices();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800857 }
858 }
859
Fabrice Di Meglio75eabc22014-03-07 18:17:03 -0800860 private void highlightHeader(int id) {
861 if (id != 0) {
862 Integer index = mHeaderIndexMap.get(id);
863 if (index != null) {
864 mDrawer.setItemChecked(index, true);
865 if (mDrawer.getVisibility() == View.VISIBLE) {
866 mDrawer.smoothScrollToPosition(index);
867 }
868 }
869 }
870 }
871
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800872 /**
873 * When in two-pane mode, switch to the fragment pane to show the given
874 * preference fragment.
875 *
876 * @param header The new header to display.
877 * @param validate true means that the fragment's Header needs to be validated.
878 * @param initial true means that it is the initial Header.
879 */
880 private void switchToHeader(Header header, boolean validate, boolean initial) {
881 if (header == null) {
882 return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800883 }
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800884 // For switching to another Header it should be a different one
885 if (mCurrentHeader == null || header.id != mCurrentHeader.id) {
886 if (header.fragment != null) {
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800887 boolean addToBackStack;
888 if (initial) {
889 addToBackStack = false;
890 } else {
891 if (header.id != mInitialHeader.id) {
892 addToBackStack = true;
893 } else {
894 addToBackStack = (mTopLevelHeaderId > 0);
895 }
896 }
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800897 switchToHeaderInner(header.fragment, header.fragmentArguments, validate,
898 addToBackStack, getHeaderTitle(header));
899 setSelectedHeader(header);
900 } else if (header.intent != null) {
901 setSelectedHeader(header);
902 startActivity(header.intent);
903 } else {
904 throw new IllegalStateException(
905 "Can't switch to header that has no Fragment nor Intent");
906 }
907 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800908 }
909
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000910 /**
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800911 * Switch the fragment pane to show the given preference fragment.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000912 *
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800913 * (used for initial fragment)
914 *
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000915 * @param fragmentName The name of the fragment to display.
916 * @param args Optional arguments to supply to the fragment.
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800917 * @param validate true means that the fragment's Header needs to be validated.
918 * @param title The title of the fragment to display.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000919 */
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800920 private void switchToHeader(String fragmentName, Bundle args, boolean validate,
921 CharSequence title) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000922 setSelectedHeader(null);
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800923 switchToHeaderInner(fragmentName, args, validate, false, title);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000924 }
925
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800926 /**
927 * Switch to a specific Header with taking care of validation, Title and BackStack
928 */
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800929 private void switchToHeaderInner(String fragmentName, Bundle args, boolean validate,
930 boolean addToBackStack, CharSequence title) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000931 getFragmentManager().popBackStack(BACK_STACK_PREFS,
932 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800933 if (validate && !isValidFragment(fragmentName)) {
934 throw new IllegalArgumentException("Invalid fragment for this activity: "
935 + fragmentName);
936 }
937 Fragment f = Fragment.instantiate(this, fragmentName, args);
938 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800939 transaction.replace(R.id.prefs, f);
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800940 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
941 if (addToBackStack) {
942 transaction.addToBackStack(BACK_STACK_PREFS);
943 }
944 if (title != null) {
945 transaction.setBreadCrumbTitle(title);
946 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800947 transaction.commitAllowingStateLoss();
948 }
949
950 @Override
951 public void onNewIntent(Intent intent) {
952 super.onNewIntent(intent);
953
954 // If it is not launched from history, then reset to top-level
955 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
956 if (mDrawer != null) {
957 mDrawer.setSelectionFromTop(0, 0);
958 }
959 }
960 }
961
962 /**
963 * Called to determine whether the header list should be hidden.
964 * The default implementation returns the
965 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
966 * This is set to false, for example, when the activity is being re-launched
967 * to show a particular preference activity.
968 */
969 public boolean onIsHidingHeaders() {
970 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
971 }
972
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800973 @Override
974 public Intent getIntent() {
975 Intent superIntent = super.getIntent();
976 String startingFragment = getStartingFragmentClass(superIntent);
977 // This is called from super.onCreate, isMultiPane() is not yet reliable
978 // Do not use onIsHidingHeaders either, which relies itself on this method
979 if (startingFragment != null) {
980 Intent modIntent = new Intent(superIntent);
981 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
982 Bundle args = superIntent.getExtras();
983 if (args != null) {
984 args = new Bundle(args);
985 } else {
986 args = new Bundle();
987 }
988 args.putParcelable("intent", superIntent);
989 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
990 return modIntent;
991 }
992 return superIntent;
993 }
994
995 /**
996 * Checks if the component name in the intent is different from the Settings class and
997 * returns the class name to load as a fragment.
998 */
999 private String getStartingFragmentClass(Intent intent) {
1000 if (mFragmentClass != null) return mFragmentClass;
1001
1002 String intentClass = intent.getComponent().getClassName();
1003 if (intentClass.equals(getClass().getName())) return null;
1004
1005 if ("com.android.settings.ManageApplications".equals(intentClass)
1006 || "com.android.settings.RunningServices".equals(intentClass)
1007 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
1008 // Old names of manage apps.
1009 intentClass = com.android.settings.applications.ManageApplications.class.getName();
1010 }
1011
1012 return intentClass;
1013 }
1014
1015 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001016 * Start a new fragment containing a preference panel. If the preferences
1017 * are being displayed in multi-pane mode, the given fragment class will
1018 * be instantiated and placed in the appropriate pane. If running in
1019 * single-pane mode, a new activity will be launched in which to show the
1020 * fragment.
1021 *
1022 * @param fragmentClass Full name of the class implementing the fragment.
1023 * @param args Any desired arguments to supply to the fragment.
1024 * @param titleRes Optional resource identifier of the title of this
1025 * fragment.
1026 * @param titleText Optional text of the title of this fragment.
1027 * @param resultTo Optional fragment that result data should be sent to.
1028 * If non-null, resultTo.onActivityResult() will be called when this
1029 * preference panel is done. The launched panel must use
1030 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
1031 * @param resultRequestCode If resultTo is non-null, this is the caller's
1032 * request code to be received with the resut.
1033 */
1034 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
1035 CharSequence titleText, Fragment resultTo,
1036 int resultRequestCode) {
1037 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
1038 }
1039
1040 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001041 * Called by a preference panel fragment to finish itself.
1042 *
1043 * @param caller The fragment that is asking to be finished.
1044 * @param resultCode Optional result code to send back to the original
1045 * launching fragment.
1046 * @param resultData Optional result data to send back to the original
1047 * launching fragment.
1048 */
1049 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
1050 setResult(resultCode, resultData);
1051 }
1052
1053 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001054 * Start a new fragment.
1055 *
1056 * @param fragment The fragment to start
1057 * @param push If true, the current fragment will be pushed onto the back stack. If false,
1058 * the current fragment will be replaced.
1059 */
1060 public void startPreferenceFragment(Fragment fragment, boolean push) {
1061 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1062 transaction.replace(R.id.prefs, fragment);
1063 if (push) {
1064 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1065 transaction.addToBackStack(BACK_STACK_PREFS);
1066 } else {
1067 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
1068 }
1069 transaction.commitAllowingStateLoss();
1070 }
1071
1072 /**
1073 * Start a new fragment.
1074 *
1075 * @param fragmentName The name of the fragment to display.
1076 * @param args Optional arguments to supply to the fragment.
1077 * @param resultTo Option fragment that should receive the result of
1078 * the activity launch.
1079 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
1080 * report the result.
1081 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
1082 * valid one then it will be used to get the title. Otherwise the titleText
1083 * argument will be used as the title.
1084 * @param titleText string to display for the title of.
1085 */
1086 private void startWithFragment(String fragmentName, Bundle args, Fragment resultTo,
1087 int resultRequestCode, int titleRes, CharSequence titleText) {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -08001088 final CharSequence cs;
1089 if (titleRes != 0) {
1090 cs = getText(titleRes);
1091 } else {
1092 cs = titleText;
1093 }
1094
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001095 Fragment f = Fragment.instantiate(this, fragmentName, args);
1096 if (resultTo != null) {
1097 f.setTargetFragment(resultTo, resultRequestCode);
1098 }
1099 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1100 transaction.replace(R.id.prefs, f);
1101 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1102 transaction.addToBackStack(BACK_STACK_PREFS);
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -08001103 transaction.setBreadCrumbTitle(cs);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001104 transaction.commitAllowingStateLoss();
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001105 }
1106
1107 /**
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -08001108 * Called when the activity needs its list of headers build.
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001109 *
1110 * @param headers The list in which to place the headers.
1111 */
1112 private void onBuildHeaders(List<Header> headers) {
1113 loadHeadersFromResource(R.xml.settings_headers, headers);
1114 updateHeaderList(headers);
1115 }
1116
1117 /**
1118 * Parse the given XML file as a header description, adding each
1119 * parsed Header into the target list.
1120 *
1121 * @param resid The XML resource to load and parse.
1122 * @param target The list in which the parsed headers should be placed.
1123 */
1124 private void loadHeadersFromResource(int resid, List<Header> target) {
1125 XmlResourceParser parser = null;
1126 try {
1127 parser = getResources().getXml(resid);
1128 AttributeSet attrs = Xml.asAttributeSet(parser);
1129
1130 int type;
1131 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1132 && type != XmlPullParser.START_TAG) {
1133 // Parse next until start tag is found
1134 }
1135
1136 String nodeName = parser.getName();
1137 if (!"preference-headers".equals(nodeName)) {
1138 throw new RuntimeException(
1139 "XML document must start with <preference-headers> tag; found"
1140 + nodeName + " at " + parser.getPositionDescription());
1141 }
1142
1143 Bundle curBundle = null;
1144
1145 final int outerDepth = parser.getDepth();
1146 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1147 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1148 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1149 continue;
1150 }
1151
1152 nodeName = parser.getName();
1153 if ("header".equals(nodeName)) {
1154 Header header = new Header();
1155
1156 TypedArray sa = obtainStyledAttributes(
1157 attrs, com.android.internal.R.styleable.PreferenceHeader);
1158 header.id = sa.getResourceId(
1159 com.android.internal.R.styleable.PreferenceHeader_id,
1160 (int)HEADER_ID_UNDEFINED);
1161 TypedValue tv = sa.peekValue(
1162 com.android.internal.R.styleable.PreferenceHeader_title);
1163 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1164 if (tv.resourceId != 0) {
1165 header.titleRes = tv.resourceId;
1166 } else {
1167 header.title = tv.string;
1168 }
1169 }
1170 tv = sa.peekValue(
1171 com.android.internal.R.styleable.PreferenceHeader_summary);
1172 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1173 if (tv.resourceId != 0) {
1174 header.summaryRes = tv.resourceId;
1175 } else {
1176 header.summary = tv.string;
1177 }
1178 }
1179 header.iconRes = sa.getResourceId(
1180 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
1181 header.fragment = sa.getString(
1182 com.android.internal.R.styleable.PreferenceHeader_fragment);
1183 sa.recycle();
1184
1185 if (curBundle == null) {
1186 curBundle = new Bundle();
1187 }
1188
1189 final int innerDepth = parser.getDepth();
1190 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1191 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
1192 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1193 continue;
1194 }
1195
1196 String innerNodeName = parser.getName();
1197 if (innerNodeName.equals("extra")) {
1198 getResources().parseBundleExtra("extra", attrs, curBundle);
1199 XmlUtils.skipCurrentTag(parser);
1200
1201 } else if (innerNodeName.equals("intent")) {
1202 header.intent = Intent.parseIntent(getResources(), parser, attrs);
1203
1204 } else {
1205 XmlUtils.skipCurrentTag(parser);
1206 }
1207 }
1208
1209 if (curBundle.size() > 0) {
1210 header.fragmentArguments = curBundle;
1211 curBundle = null;
1212 }
1213
1214 target.add(header);
1215 } else {
1216 XmlUtils.skipCurrentTag(parser);
1217 }
1218 }
1219
1220 } catch (XmlPullParserException e) {
1221 throw new RuntimeException("Error parsing headers", e);
1222 } catch (IOException e) {
1223 throw new RuntimeException("Error parsing headers", e);
1224 } finally {
1225 if (parser != null) parser.close();
1226 }
1227 }
1228
1229 private void updateHeaderList(List<Header> target) {
1230 final boolean showDev = mDevelopmentPreferences.getBoolean(
1231 DevelopmentSettings.PREF_SHOW,
1232 android.os.Build.TYPE.equals("eng"));
1233 int i = 0;
1234
1235 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1236 mHeaderIndexMap.clear();
1237 while (i < target.size()) {
1238 Header header = target.get(i);
1239 // Ids are integers, so downcasting
1240 int id = (int) header.id;
1241 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1242 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1243 } else if (id == R.id.wifi_settings) {
1244 // Remove WiFi Settings if WiFi service is not available.
1245 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1246 target.remove(i);
1247 }
1248 } else if (id == R.id.bluetooth_settings) {
1249 // Remove Bluetooth Settings if Bluetooth service is not available.
1250 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1251 target.remove(i);
1252 }
1253 } else if (id == R.id.data_usage_settings) {
1254 // Remove data usage when kernel module not enabled
1255 final INetworkManagementService netManager = INetworkManagementService.Stub
1256 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1257 try {
1258 if (!netManager.isBandwidthControlEnabled()) {
1259 target.remove(i);
1260 }
1261 } catch (RemoteException e) {
1262 // ignored
1263 }
1264 } else if (id == R.id.battery_settings) {
1265 // Remove battery settings when battery is not available. (e.g. TV)
1266
1267 if (!mBatteryPresent) {
1268 target.remove(i);
1269 }
1270 } else if (id == R.id.account_settings) {
1271 int headerIndex = i + 1;
1272 i = insertAccountsHeaders(target, headerIndex);
1273 } else if (id == R.id.home_settings) {
1274 if (!updateHomeSettingHeaders(header)) {
1275 target.remove(i);
1276 }
1277 } else if (id == R.id.user_settings) {
1278 if (!UserHandle.MU_ENABLED
1279 || !UserManager.supportsMultipleUsers()
1280 || Utils.isMonkeyRunning()) {
1281 target.remove(i);
1282 }
1283 } else if (id == R.id.nfc_payment_settings) {
1284 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1285 target.remove(i);
1286 } else {
1287 // Only show if NFC is on and we have the HCE feature
1288 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1289 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1290 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1291 target.remove(i);
1292 }
1293 }
1294 } else if (id == R.id.development_settings) {
1295 if (!showDev) {
1296 target.remove(i);
1297 }
1298 } else if (id == R.id.account_add) {
1299 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1300 target.remove(i);
1301 }
1302 }
1303
1304 if (i < target.size() && target.get(i) == header
1305 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1306 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1307 target.remove(i);
1308 }
1309
1310 // Increment if the current one wasn't removed by the Utils code.
1311 if (i < target.size() && target.get(i) == header) {
1312 // Hold on to the first header, when we need to reset to the top-level
1313 if (mFirstHeader == null &&
1314 HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
1315 mFirstHeader = header;
1316 }
1317 mHeaderIndexMap.put(id, i);
1318 i++;
1319 }
1320 }
1321 }
1322
1323 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1324 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1325 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1326 for (String accountType : accountTypes) {
1327 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1328 if (label == null) {
1329 continue;
1330 }
1331
1332 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1333 boolean skipToAccount = accounts.length == 1
1334 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1335 Header accHeader = new Header();
1336 accHeader.title = label;
1337 if (accHeader.extras == null) {
1338 accHeader.extras = new Bundle();
1339 }
1340 if (skipToAccount) {
1341 accHeader.fragment = AccountSyncSettings.class.getName();
1342 accHeader.fragmentArguments = new Bundle();
1343 // Need this for the icon
1344 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1345 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1346 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1347 accounts[0]);
1348 } else {
1349 accHeader.fragment = ManageAccountsSettings.class.getName();
1350 accHeader.fragmentArguments = new Bundle();
1351 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1352 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1353 accountType);
1354 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1355 label.toString());
1356 }
1357 accountHeaders.add(accHeader);
1358 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1359 }
1360
1361 // Sort by label
1362 Collections.sort(accountHeaders, new Comparator<Header>() {
1363 @Override
1364 public int compare(Header h1, Header h2) {
1365 return h1.title.toString().compareTo(h2.title.toString());
1366 }
1367 });
1368
1369 for (Header header : accountHeaders) {
1370 target.add(headerIndex++, header);
1371 }
1372 if (!mListeningToAccountUpdates) {
1373 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1374 mListeningToAccountUpdates = true;
1375 }
1376 return headerIndex;
1377 }
1378
1379 private boolean updateHomeSettingHeaders(Header header) {
1380 // Once we decide to show Home settings, keep showing it forever
1381 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1382 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1383 return true;
1384 }
1385
1386 try {
1387 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1388 getPackageManager().getHomeActivities(homeApps);
1389 if (homeApps.size() < 2) {
1390 // When there's only one available home app, omit this settings
1391 // category entirely at the top level UI. If the user just
1392 // uninstalled the penultimate home app candidiate, we also
1393 // now tell them about why they aren't seeing 'Home' in the list.
1394 if (sShowNoHomeNotice) {
1395 sShowNoHomeNotice = false;
1396 NoHomeDialogFragment.show(this);
1397 }
1398 return false;
1399 } else {
1400 // Okay, we're allowing the Home settings category. Tell it, when
1401 // invoked via this front door, that we'll need to be told about the
1402 // case when the user uninstalls all but one home app.
1403 if (header.fragmentArguments == null) {
1404 header.fragmentArguments = new Bundle();
1405 }
1406 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1407 }
1408 } catch (Exception e) {
1409 // Can't look up the home activity; bail on configuring the icon
1410 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1411 }
1412
1413 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1414 return true;
1415 }
1416
1417 private void getMetaData() {
1418 try {
1419 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1420 PackageManager.GET_META_DATA);
1421 if (ai == null || ai.metaData == null) return;
1422 mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
1423 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1424 } catch (NameNotFoundException nnfe) {
1425 // No recovery
1426 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1427 }
1428 }
1429
1430 // give subclasses access to the Next button
1431 public boolean hasNextButton() {
1432 return mNextButton != null;
1433 }
1434
1435 public Button getNextButton() {
1436 return mNextButton;
1437 }
1438
1439 public static class NoHomeDialogFragment extends DialogFragment {
1440 public static void show(Activity parent) {
1441 final NoHomeDialogFragment dialog = new NoHomeDialogFragment();
1442 dialog.show(parent.getFragmentManager(), null);
1443 }
1444
1445 @Override
1446 public Dialog onCreateDialog(Bundle savedInstanceState) {
1447 return new AlertDialog.Builder(getActivity())
1448 .setMessage(R.string.only_one_home_message)
1449 .setPositiveButton(android.R.string.ok, null)
1450 .create();
1451 }
1452 }
1453
1454 private static class HeaderAdapter extends ArrayAdapter<Header> {
1455 static final int HEADER_TYPE_CATEGORY = 0;
1456 static final int HEADER_TYPE_NORMAL = 1;
1457 static final int HEADER_TYPE_SWITCH = 2;
1458 static final int HEADER_TYPE_BUTTON = 3;
1459 private static final int HEADER_TYPE_COUNT = HEADER_TYPE_BUTTON + 1;
1460
1461 private final WifiEnabler mWifiEnabler;
1462 private final BluetoothEnabler mBluetoothEnabler;
1463 private AuthenticatorHelper mAuthHelper;
1464 private DevicePolicyManager mDevicePolicyManager;
1465
1466 private static class HeaderViewHolder {
1467 ImageView mIcon;
1468 TextView mTitle;
1469 TextView mSummary;
1470 Switch mSwitch;
1471 ImageButton mButton;
1472 View mDivider;
1473 }
1474
1475 private LayoutInflater mInflater;
1476
1477 static int getHeaderType(Header header) {
1478 if (header.fragment == null && header.intent == null) {
1479 return HEADER_TYPE_CATEGORY;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001480 } else if (header.id == R.id.security_settings) {
1481 return HEADER_TYPE_BUTTON;
1482 } else {
1483 return HEADER_TYPE_NORMAL;
1484 }
1485 }
1486
1487 @Override
1488 public int getItemViewType(int position) {
1489 Header header = getItem(position);
1490 return getHeaderType(header);
1491 }
1492
1493 @Override
1494 public boolean areAllItemsEnabled() {
1495 return false; // because of categories
1496 }
1497
1498 @Override
1499 public boolean isEnabled(int position) {
1500 return getItemViewType(position) != HEADER_TYPE_CATEGORY;
1501 }
1502
1503 @Override
1504 public int getViewTypeCount() {
1505 return HEADER_TYPE_COUNT;
1506 }
1507
1508 @Override
1509 public boolean hasStableIds() {
1510 return true;
1511 }
1512
1513 public HeaderAdapter(Context context, List<Header> objects,
1514 AuthenticatorHelper authenticatorHelper, DevicePolicyManager dpm) {
1515 super(context, 0, objects);
1516
1517 mAuthHelper = authenticatorHelper;
1518 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1519
1520 // Temp Switches provided as placeholder until the adapter replaces these with actual
1521 // Switches inflated from their layouts. Must be done before adapter is set in super
1522 mWifiEnabler = new WifiEnabler(context, new Switch(context));
1523 mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
1524 mDevicePolicyManager = dpm;
1525 }
1526
1527 @Override
1528 public View getView(int position, View convertView, ViewGroup parent) {
1529 HeaderViewHolder holder;
1530 Header header = getItem(position);
1531 int headerType = getHeaderType(header);
1532 View view = null;
1533
1534 if (convertView == null) {
1535 holder = new HeaderViewHolder();
1536 switch (headerType) {
1537 case HEADER_TYPE_CATEGORY:
1538 view = new TextView(getContext(), null,
1539 android.R.attr.listSeparatorTextViewStyle);
1540 holder.mTitle = (TextView) view;
1541 break;
1542
1543 case HEADER_TYPE_SWITCH:
1544 view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
1545 false);
1546 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1547 holder.mTitle = (TextView)
1548 view.findViewById(com.android.internal.R.id.title);
1549 holder.mSummary = (TextView)
1550 view.findViewById(com.android.internal.R.id.summary);
1551 holder.mSwitch = (Switch) view.findViewById(R.id.switchWidget);
1552 break;
1553
1554 case HEADER_TYPE_BUTTON:
1555 view = mInflater.inflate(R.layout.preference_header_button_item, parent,
1556 false);
1557 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1558 holder.mTitle = (TextView)
1559 view.findViewById(com.android.internal.R.id.title);
1560 holder.mSummary = (TextView)
1561 view.findViewById(com.android.internal.R.id.summary);
1562 holder.mButton = (ImageButton) view.findViewById(R.id.buttonWidget);
1563 holder.mDivider = view.findViewById(R.id.divider);
1564 break;
1565
1566 case HEADER_TYPE_NORMAL:
1567 view = mInflater.inflate(
1568 R.layout.preference_header_item, parent,
1569 false);
1570 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1571 holder.mTitle = (TextView)
1572 view.findViewById(com.android.internal.R.id.title);
1573 holder.mSummary = (TextView)
1574 view.findViewById(com.android.internal.R.id.summary);
1575 break;
1576 }
Fabrice Di Meglio0e2f9492014-02-25 14:26:27 -08001577 if (holder.mIcon != null) {
1578 holder.mIcon.setBackgroundResource(R.color.background_drawer_icon);
1579 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001580 view.setTag(holder);
1581 } else {
1582 view = convertView;
1583 holder = (HeaderViewHolder) view.getTag();
1584 }
1585
1586 // All view fields must be updated every time, because the view may be recycled
1587 switch (headerType) {
1588 case HEADER_TYPE_CATEGORY:
1589 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1590 break;
1591
1592 case HEADER_TYPE_SWITCH:
1593 // Would need a different treatment if the main menu had more switches
1594 if (header.id == R.id.wifi_settings) {
1595 mWifiEnabler.setSwitch(holder.mSwitch);
1596 } else {
1597 mBluetoothEnabler.setSwitch(holder.mSwitch);
1598 }
1599 updateCommonHeaderView(header, holder);
1600 break;
1601
1602 case HEADER_TYPE_BUTTON:
1603 if (header.id == R.id.security_settings) {
1604 boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled();
1605 if (hasCert) {
1606 holder.mButton.setVisibility(View.VISIBLE);
1607 holder.mDivider.setVisibility(View.VISIBLE);
1608 boolean isManaged = mDevicePolicyManager.getDeviceOwner() != null;
1609 if (isManaged) {
1610 holder.mButton.setImageResource(R.drawable.ic_settings_about);
1611 } else {
1612 holder.mButton.setImageResource(
1613 android.R.drawable.stat_notify_error);
1614 }
1615 holder.mButton.setOnClickListener(new OnClickListener() {
1616 @Override
1617 public void onClick(View v) {
1618 Intent intent = new Intent(
1619 android.provider.Settings.ACTION_MONITORING_CERT_INFO);
1620 getContext().startActivity(intent);
1621 }
1622 });
1623 } else {
1624 holder.mButton.setVisibility(View.GONE);
1625 holder.mDivider.setVisibility(View.GONE);
1626 }
1627 }
1628 updateCommonHeaderView(header, holder);
1629 break;
1630
1631 case HEADER_TYPE_NORMAL:
1632 updateCommonHeaderView(header, holder);
1633 break;
1634 }
1635
1636 return view;
1637 }
1638
1639 private void updateCommonHeaderView(Header header, HeaderViewHolder holder) {
1640 if (header.extras != null
1641 && header.extras.containsKey(ManageAccountsSettings.KEY_ACCOUNT_TYPE)) {
1642 String accType = header.extras.getString(
1643 ManageAccountsSettings.KEY_ACCOUNT_TYPE);
1644 Drawable icon = mAuthHelper.getDrawableForType(getContext(), accType);
1645 setHeaderIcon(holder, icon);
1646 } else {
1647 holder.mIcon.setImageResource(header.iconRes);
1648 }
1649 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1650 CharSequence summary = header.getSummary(getContext().getResources());
1651 if (!TextUtils.isEmpty(summary)) {
1652 holder.mSummary.setVisibility(View.VISIBLE);
1653 holder.mSummary.setText(summary);
1654 } else {
1655 holder.mSummary.setVisibility(View.GONE);
1656 }
1657 }
1658
1659 private void setHeaderIcon(HeaderViewHolder holder, Drawable icon) {
1660 ViewGroup.LayoutParams lp = holder.mIcon.getLayoutParams();
1661 lp.width = getContext().getResources().getDimensionPixelSize(
1662 R.dimen.header_icon_width);
1663 lp.height = lp.width;
1664 holder.mIcon.setLayoutParams(lp);
1665 holder.mIcon.setImageDrawable(icon);
1666 }
1667
Matthew Xiea504c4d2014-02-14 16:32:32 -08001668 public void resume(Context context) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001669 mWifiEnabler.resume();
Matthew Xiea504c4d2014-02-14 16:32:32 -08001670 mBluetoothEnabler.resume(context);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001671 }
1672
1673 public void pause() {
1674 mWifiEnabler.pause();
1675 mBluetoothEnabler.pause();
1676 }
1677 }
1678
1679 private void onListItemClick(ListView l, View v, int position, long id) {
1680 if (!isResumed()) {
1681 return;
1682 }
1683 Object item = mHeaderAdapter.getItem(position);
1684 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001685 mSelectedHeader = (Header) item;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001686 }
1687 }
1688
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001689 @Override
1690 public boolean shouldUpRecreateTask(Intent targetIntent) {
1691 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1692 }
1693
1694 @Override
1695 public void onAccountsUpdated(Account[] accounts) {
1696 // TODO: watch for package upgrades to invalidate cache; see 7206643
1697 mAuthenticatorHelper.updateAuthDescriptions(this);
1698 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1699 invalidateHeaders();
1700 }
1701
1702 public static void requestHomeNotice() {
1703 sShowNoHomeNotice = true;
1704 }
1705
1706 /**
1707 * Default value for {@link Header#id Header.id} indicating that no
1708 * identifier value is set. All other values (including those below -1)
1709 * are valid.
1710 */
1711 private static final long HEADER_ID_UNDEFINED = -1;
1712
1713 /**
1714 * Description of a single Header item that the user can select.
1715 */
1716 static final class Header implements Parcelable {
1717 /**
1718 * Identifier for this header, to correlate with a new list when
1719 * it is updated. The default value is
1720 * {@link SettingsActivity#HEADER_ID_UNDEFINED}, meaning no id.
1721 * @attr ref android.R.styleable#PreferenceHeader_id
1722 */
1723 public long id = HEADER_ID_UNDEFINED;
1724
1725 /**
1726 * Resource ID of title of the header that is shown to the user.
1727 * @attr ref android.R.styleable#PreferenceHeader_title
1728 */
1729 public int titleRes;
1730
1731 /**
1732 * Title of the header that is shown to the user.
1733 * @attr ref android.R.styleable#PreferenceHeader_title
1734 */
1735 public CharSequence title;
1736
1737 /**
1738 * Resource ID of optional summary describing what this header controls.
1739 * @attr ref android.R.styleable#PreferenceHeader_summary
1740 */
1741 public int summaryRes;
1742
1743 /**
1744 * Optional summary describing what this header controls.
1745 * @attr ref android.R.styleable#PreferenceHeader_summary
1746 */
1747 public CharSequence summary;
1748
1749 /**
1750 * Optional icon resource to show for this header.
1751 * @attr ref android.R.styleable#PreferenceHeader_icon
1752 */
1753 public int iconRes;
1754
1755 /**
1756 * Full class name of the fragment to display when this header is
1757 * selected.
1758 * @attr ref android.R.styleable#PreferenceHeader_fragment
1759 */
1760 public String fragment;
1761
1762 /**
1763 * Optional arguments to supply to the fragment when it is
1764 * instantiated.
1765 */
1766 public Bundle fragmentArguments;
1767
1768 /**
1769 * Intent to launch when the preference is selected.
1770 */
1771 public Intent intent;
1772
1773 /**
1774 * Optional additional data for use by subclasses of the activity
1775 */
1776 public Bundle extras;
1777
1778 public Header() {
1779 // Empty
1780 }
1781
1782 /**
1783 * Return the currently set title. If {@link #titleRes} is set,
1784 * this resource is loaded from <var>res</var> and returned. Otherwise
1785 * {@link #title} is returned.
1786 */
1787 public CharSequence getTitle(Resources res) {
1788 if (titleRes != 0) {
1789 return res.getText(titleRes);
1790 }
1791 return title;
1792 }
1793
1794 /**
1795 * Return the currently set summary. If {@link #summaryRes} is set,
1796 * this resource is loaded from <var>res</var> and returned. Otherwise
1797 * {@link #summary} is returned.
1798 */
1799 public CharSequence getSummary(Resources res) {
1800 if (summaryRes != 0) {
1801 return res.getText(summaryRes);
1802 }
1803 return summary;
1804 }
1805
1806 @Override
1807 public int describeContents() {
1808 return 0;
1809 }
1810
1811 @Override
1812 public void writeToParcel(Parcel dest, int flags) {
1813 dest.writeLong(id);
1814 dest.writeInt(titleRes);
1815 TextUtils.writeToParcel(title, dest, flags);
1816 dest.writeInt(summaryRes);
1817 TextUtils.writeToParcel(summary, dest, flags);
1818 dest.writeInt(iconRes);
1819 dest.writeString(fragment);
1820 dest.writeBundle(fragmentArguments);
1821 if (intent != null) {
1822 dest.writeInt(1);
1823 intent.writeToParcel(dest, flags);
1824 } else {
1825 dest.writeInt(0);
1826 }
1827 dest.writeBundle(extras);
1828 }
1829
1830 public void readFromParcel(Parcel in) {
1831 id = in.readLong();
1832 titleRes = in.readInt();
1833 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1834 summaryRes = in.readInt();
1835 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1836 iconRes = in.readInt();
1837 fragment = in.readString();
1838 fragmentArguments = in.readBundle();
1839 if (in.readInt() != 0) {
1840 intent = Intent.CREATOR.createFromParcel(in);
1841 }
1842 extras = in.readBundle();
1843 }
1844
1845 Header(Parcel in) {
1846 readFromParcel(in);
1847 }
1848
1849 public static final Creator<Header> CREATOR = new Creator<Header>() {
1850 public Header createFromParcel(Parcel source) {
1851 return new Header(source);
1852 }
1853 public Header[] newArray(int size) {
1854 return new Header[size];
1855 }
1856 };
1857 }
1858}