blob: b3ea79ad92d617aab252bc74b83a47c98458b12e [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;
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -070098import com.android.settings.indexer.IndexableRef;
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 Megliob8dfbf12014-03-10 19:24:54 -0700344 private static int NO_DATA_RES_ID = 0;
345
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800346 /**
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700347 * Indexable data description.
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800348 *
349 * Known restriction: we are only searching (for now) the first level of Settings.
350 */
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700351 private static IndexableRef[] INDEXABLE_REFS = new IndexableRef[] {
352 new IndexableRef(1, NO_DATA_RES_ID,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800353 "com.android.settings.wifi.WifiSettings",
354 R.drawable.ic_settings_wireless),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700355 new IndexableRef(2, R.xml.bluetooth_settings,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800356 "com.android.settings.bluetooth.BluetoothSettings",
357 R.drawable.ic_settings_bluetooth2),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700358 new IndexableRef(3, R.xml.data_usage_metered_prefs,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800359 "com.android.settings.net.DataUsageMeteredSettings",
360 R.drawable.ic_settings_data_usage),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700361 new IndexableRef(4, R.xml.wireless_settings,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800362 "com.android.settings.WirelessSettings",
363 R.drawable.empty_icon),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700364 new IndexableRef(5, R.xml.home_selection,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800365 "com.android.settings.HomeSettings",
366 R.drawable.ic_settings_home),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700367 new IndexableRef(6, R.xml.sound_settings,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800368 "com.android.settings.SoundSettings",
369 R.drawable.ic_settings_sound),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700370 new IndexableRef(7, R.xml.display_settings,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800371 "com.android.settings.DisplaySettings",
372 R.drawable.ic_settings_display),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700373 new IndexableRef(7, NO_DATA_RES_ID,
374 "com.android.settings.WallpaperTypeSettings",
375 R.drawable.ic_settings_display),
376 new IndexableRef(8, R.xml.device_info_memory,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800377 "com.android.settings.deviceinfo.Memory",
378 R.drawable.ic_settings_storage),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700379 new IndexableRef(9, R.xml.power_usage_summary,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800380 "com.android.settings.fuelgauge.PowerUsageSummary",
381 R.drawable.ic_settings_battery),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700382 new IndexableRef(10, R.xml.user_settings,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800383 "com.android.settings.users.UserSettings",
384 R.drawable.ic_settings_multiuser),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700385 new IndexableRef(11, R.xml.location_settings,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800386 "com.android.settings.location.LocationSettings",
387 R.drawable.ic_settings_location),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700388 new IndexableRef(12, R.xml.security_settings,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800389 "com.android.settings.SecuritySettings",
390 R.drawable.ic_settings_security),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700391 new IndexableRef(13, R.xml.language_settings,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800392 "com.android.settings.inputmethod.InputMethodAndLanguageSettings",
393 R.drawable.ic_settings_language),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700394 new IndexableRef(14, R.xml.privacy_settings,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800395 "com.android.settings.PrivacySettings",
396 R.drawable.ic_settings_backup),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700397 new IndexableRef(15, R.xml.date_time_prefs,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800398 "com.android.settings.DateTimeSettings",
399 R.drawable.ic_settings_date_time),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700400 new IndexableRef(16, R.xml.accessibility_settings,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800401 "com.android.settings.accessibility.AccessibilitySettings",
402 R.drawable.ic_settings_accessibility),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700403 new IndexableRef(17, R.xml.print_settings,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800404 "com.android.settings.print.PrintSettingsFragment",
405 com.android.internal.R.drawable.ic_print),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700406 new IndexableRef(18, R.xml.development_prefs,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800407 "com.android.settings.DevelopmentSettings",
408 R.drawable.ic_settings_development),
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700409 new IndexableRef(19, R.xml.device_info_settings,
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800410 "com.android.settings.DeviceInfoSettings",
411 R.drawable.ic_settings_about),
412 };
413
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800414 @Override
415 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
416 // Override the fragment title for Wallpaper settings
417 int titleRes = pref.getTitleRes();
418 if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
419 titleRes = R.string.wallpaper_settings_fragment_title;
420 } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
421 && UserHandle.myUserId() != UserHandle.USER_OWNER) {
422 if (UserManager.get(this).isLinkedUser()) {
423 titleRes = R.string.profile_info_settings_title;
424 } else {
425 titleRes = R.string.user_info_settings_title;
426 }
427 }
428 startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
429 null, 0);
430 return true;
431 }
432
433 @Override
434 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
435 return false;
436 }
437
438 private class DrawerListener implements DrawerLayout.DrawerListener {
439 @Override
440 public void onDrawerOpened(View drawerView) {
441 mDrawerToggle.onDrawerOpened(drawerView);
442 }
443
444 @Override
445 public void onDrawerClosed(View drawerView) {
446 mDrawerToggle.onDrawerClosed(drawerView);
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800447 // Cannot process clicks when the App is finishing
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800448 if (isFinishing() || mSelectedHeader == null) {
449 return;
450 }
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800451 switchToHeader(mSelectedHeader, false, false);
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800452 mSelectedHeader = null;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800453 }
454
455 @Override
456 public void onDrawerSlide(View drawerView, float slideOffset) {
457 mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
458 }
459
460 @Override
461 public void onDrawerStateChanged(int newState) {
462 mDrawerToggle.onDrawerStateChanged(newState);
463 }
464 }
465
466 private class DrawerItemClickListener implements ListView.OnItemClickListener {
467 @Override
468 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
469 mDrawerLayout.closeDrawer(mDrawer);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000470 onListItemClick((ListView)parent, view, position, id);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800471 }
472 }
473
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800474 private Header findBestMatchingHeader(Header current, ArrayList<Header> from) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800475 ArrayList<Header> matches = new ArrayList<Header>();
476 for (int j=0; j<from.size(); j++) {
477 Header oh = from.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800478 if (current == oh || (current.id != HEADER_ID_UNDEFINED && current.id == oh.id)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800479 // Must be this one.
480 matches.clear();
481 matches.add(oh);
482 break;
483 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800484 if (current.fragment != null) {
485 if (current.fragment.equals(oh.fragment)) {
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.intent != null) {
489 if (current.intent.equals(oh.intent)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800490 matches.add(oh);
491 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800492 } else if (current.title != null) {
493 if (current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800494 matches.add(oh);
495 }
496 }
497 }
498 final int NM = matches.size();
499 if (NM == 1) {
500 return matches.get(0);
501 } else if (NM > 1) {
502 for (int j=0; j<NM; j++) {
503 Header oh = matches.get(j);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800504 if (current.fragmentArguments != null &&
505 current.fragmentArguments.equals(oh.fragmentArguments)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800506 return oh;
507 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800508 if (current.extras != null && current.extras.equals(oh.extras)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800509 return oh;
510 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800511 if (current.title != null && current.title.equals(oh.title)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800512 return oh;
513 }
514 }
515 }
516 return null;
517 }
518
519 private void invalidateHeaders() {
520 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
521 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
522 }
523 }
524
525 @Override
526 protected void onPostCreate(Bundle savedInstanceState) {
527 super.onPostCreate(savedInstanceState);
528
529 // Sync the toggle state after onRestoreInstanceState has occurred.
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800530 mDrawerToggle.syncState();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800531 }
532
533 @Override
534 public void onConfigurationChanged(Configuration newConfig) {
535 super.onConfigurationChanged(newConfig);
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800536 mDrawerToggle.onConfigurationChanged(newConfig);
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800537 Index.getInstance(this).update();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800538 }
539
540 @Override
541 public boolean onOptionsItemSelected(MenuItem item) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800542 if (mDrawerToggle.onOptionsItemSelected(item)) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800543 return true;
544 }
545 return super.onOptionsItemSelected(item);
546 }
547
548 @Override
549 protected void onCreate(Bundle savedInstanceState) {
550 if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
551 getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
552 }
553
Fabrice Di Megliob8dfbf12014-03-10 19:24:54 -0700554 Index.getInstance(this).addIndexableData(INDEXABLE_REFS);
Fabrice Di Meglio6f0739a2014-02-03 18:12:25 -0800555 Index.getInstance(this).update();
556
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800557 mAuthenticatorHelper = new AuthenticatorHelper();
558 mAuthenticatorHelper.updateAuthDescriptions(this);
559 mAuthenticatorHelper.onAccountsUpdated(this, null);
560
561 DevicePolicyManager dpm =
562 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
Fabrice Di Megliodca28062014-02-21 17:42:56 -0800563
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800564 mHeaderAdapter= new HeaderAdapter(this, mHeaders, mAuthenticatorHelper, dpm);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800565
566 mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
567 Context.MODE_PRIVATE);
568
569 getMetaData();
570
571 super.onCreate(savedInstanceState);
572
573 setContentView(R.layout.settings_main);
574
575 getFragmentManager().addOnBackStackChangedListener(this);
576
577 mActionBar = getActionBar();
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800578 mActionBar.setDisplayHomeAsUpEnabled(true);
579 mActionBar.setHomeButtonEnabled(true);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800580
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800581 mDrawer = (ListView) findViewById(R.id.headers_drawer);
582 mDrawer.setAdapter(mHeaderAdapter);
583 mDrawer.setOnItemClickListener(new DrawerItemClickListener());
584 mDrawer.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800585
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800586 mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
587 mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
588 R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800589
590 if (savedInstanceState != null) {
591 // We are restarting from a previous saved state; used that to
592 // initialize, instead of starting fresh.
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800593 mInitialTitle = getTitle();
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800594
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800595 ArrayList<Header> headers =
596 savedInstanceState.getParcelableArrayList(SAVE_KEY_HEADERS_TAG);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800597 if (headers != null) {
598 mHeaders.addAll(headers);
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800599 int curHeader = savedInstanceState.getInt(SAVE_KEY_CURRENT_HEADER_TAG,
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800600 (int) HEADER_ID_UNDEFINED);
601 if (curHeader >= 0 && curHeader < mHeaders.size()) {
602 setSelectedHeader(mHeaders.get(curHeader));
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800603 mInitialHeader = mCurrentHeader;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800604 }
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700605 setTitleFromBackStack();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800606 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800607 } else {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800608 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
609 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
610
611 // We need to build the Headers in all cases
612 onBuildHeaders(mHeaders);
613
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800614 if (initialFragment != null) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000615 // If we are just showing a fragment, we want to run in
616 // new fragment mode, but don't need to compute and show
617 // the headers.
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800618 final int initialTitleResId = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
619 mInitialTitle = (initialTitleResId > 0) ? getText(initialTitleResId) : getTitle();
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800620 setTitle(mInitialTitle);
621 switchToHeaderInner(initialFragment, initialArguments, true, false, mInitialTitle);
Fabrice Di Meglio75eabc22014-03-07 18:17:03 -0800622 setSelectedHeaderById(mTopLevelHeaderId);
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800623 mInitialHeader = mCurrentHeader;
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000624 } else {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000625 // If there are headers, then at this point we need to show
626 // them and, depending on the screen, we may also show in-line
627 // the currently selected preference fragment.
628 if (mHeaders.size() > 0) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800629 mInitialHeader = onGetInitialHeader();
630 mInitialTitle = getHeaderTitle(mInitialHeader);
631 switchToHeader(mInitialHeader, false, true);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000632 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800633 }
634 }
635
636 // see if we should show Back/Next buttons
637 Intent intent = getIntent();
638 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
639
640 View buttonBar = findViewById(com.android.internal.R.id.button_bar);
641 if (buttonBar != null) {
642 buttonBar.setVisibility(View.VISIBLE);
643
644 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
645 backButton.setOnClickListener(new OnClickListener() {
646 public void onClick(View v) {
647 setResult(RESULT_CANCELED);
648 finish();
649 }
650 });
651 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
652 skipButton.setOnClickListener(new OnClickListener() {
653 public void onClick(View v) {
654 setResult(RESULT_OK);
655 finish();
656 }
657 });
658 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
659 mNextButton.setOnClickListener(new OnClickListener() {
660 public void onClick(View v) {
661 setResult(RESULT_OK);
662 finish();
663 }
664 });
665
666 // set our various button parameters
667 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
668 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
669 if (TextUtils.isEmpty(buttonText)) {
670 mNextButton.setVisibility(View.GONE);
671 }
672 else {
673 mNextButton.setText(buttonText);
674 }
675 }
676 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
677 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
678 if (TextUtils.isEmpty(buttonText)) {
679 backButton.setVisibility(View.GONE);
680 }
681 else {
682 backButton.setText(buttonText);
683 }
684 }
685 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
686 skipButton.setVisibility(View.VISIBLE);
687 }
688 }
689 }
690
691 if (!onIsHidingHeaders()) {
692 highlightHeader(mTopLevelHeaderId);
693 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800694 }
695
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800696 public Header onGetInitialHeader() {
697 String fragmentClass = getStartingFragmentClass(super.getIntent());
698 if (fragmentClass != null) {
699 Header header = new Header();
700 header.fragment = fragmentClass;
701 header.title = getTitle();
702 header.fragmentArguments = getIntent().getExtras();
703 return header;
704 }
705
706 return mFirstHeader;
707 }
708
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800709 @Override
Fabrice Di Meglioc95be4f2014-03-07 12:57:38 -0800710 public void onBackPressed() {
711 if (mDrawerLayout.isDrawerOpen(mDrawer)) {
712 mDrawerLayout.closeDrawer(mDrawer);
713 return;
714 }
715 super.onBackPressed();
716 }
717
718 @Override
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800719 public void onBackStackChanged() {
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700720 if (setTitleFromBackStack() == 0) {
721 setSelectedHeaderById(mInitialHeader.id);
722 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800723 }
724
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700725 private int setTitleFromBackStack() {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800726 final int count = getFragmentManager().getBackStackEntryCount();
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700727
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800728 if (count == 0) {
729 setTitle(mInitialTitle);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700730 return 0;
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800731 }
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700732
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800733 FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1);
734 setTitleFromBackStackEntry(bse);
Fabrice Di Megliob643cbf2014-03-10 12:18:39 -0700735
736 return count;
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800737 }
738
739 private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
740 final CharSequence title;
741 final int titleRes = bse.getBreadCrumbTitleRes();
742 if (titleRes > 0) {
743 title = getText(titleRes);
744 } else {
745 title = bse.getBreadCrumbTitle();
746 }
747 if (title != null) {
748 setTitle(title);
749 }
Fabrice Di Meglio5529d292014-02-11 19:52:28 -0800750 }
751
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800752 @Override
753 protected void onSaveInstanceState(Bundle outState) {
754 super.onSaveInstanceState(outState);
755
756 if (mHeaders.size() > 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800757 outState.putParcelableArrayList(SAVE_KEY_HEADERS_TAG, mHeaders);
758 if (mCurrentHeader != null) {
759 int index = mHeaders.indexOf(mCurrentHeader);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800760 if (index >= 0) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800761 outState.putInt(SAVE_KEY_CURRENT_HEADER_TAG, index);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800762 }
763 }
764 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800765 }
766
767 @Override
768 public void onResume() {
769 super.onResume();
770
771 mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
772 @Override
773 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
774 invalidateHeaders();
775 }
776 };
777 mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
778 mDevelopmentPreferencesListener);
779
Matthew Xiea504c4d2014-02-14 16:32:32 -0800780 mHeaderAdapter.resume(this);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800781 invalidateHeaders();
782
783 registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800784
785 mDrawerLayout.setDrawerListener(new DrawerListener());
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800786 }
787
788 @Override
789 public void onPause() {
790 super.onPause();
791
Fabrice Di Meglio7ce7c402014-02-10 17:11:10 -0800792 mDrawerLayout.setDrawerListener(null);
793
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800794 unregisterReceiver(mBatteryInfoReceiver);
795
796 mHeaderAdapter.pause();
797
798 mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
799 mDevelopmentPreferencesListener);
800
801 mDevelopmentPreferencesListener = null;
802 }
803
804 @Override
805 public void onDestroy() {
806 super.onDestroy();
807 if (mListeningToAccountUpdates) {
808 AccountManager.get(this).removeOnAccountsUpdatedListener(this);
809 }
810 }
811
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800812 protected boolean isValidFragment(String fragmentName) {
813 // Almost all fragments are wrapped in this,
814 // except for a few that have their own activities.
815 for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
816 if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
817 }
818 return false;
819 }
820
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800821 private CharSequence getHeaderTitle(Header header) {
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800822 if (header == null || header.fragment == null) return getTitle();
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800823 final CharSequence title;
824 if (header.fragment.equals(DashboardSummary.class.getName())) {
825 title = getResources().getString(R.string.settings_label);
826 } else {
827 title = header.getTitle(getResources());
828 }
829 return title;
830 }
831
Fabrice Di Meglio75eabc22014-03-07 18:17:03 -0800832 private void setSelectedHeaderById(long headerId) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800833 final int count = mHeaders.size();
834 for (int n = 0; n < count; n++) {
835 Header h = mHeaders.get(n);
Fabrice Di Meglio75eabc22014-03-07 18:17:03 -0800836 if (h.id == headerId) {
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800837 setSelectedHeader(h);
838 return;
839 }
840 }
841 }
842
Fabrice Di Meglio75eabc22014-03-07 18:17:03 -0800843 /**
844 * As the Headers can be rebuilt, their references can change, so use this method with caution!
845 */
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800846 private void setSelectedHeader(Header header) {
Fabrice Di Meglioa7ad6192014-02-12 15:38:26 -0800847 if (header == null) {
848 mCurrentHeader = null;
849 return;
850 }
851 // Update selected Header into Drawer only if it is not "Add Account"
852 if (header.id == R.id.account_add) {
853 mDrawer.clearChoices();
854 return;
855 }
Fabrice Di Meglio65027202014-02-11 15:19:46 -0800856 mCurrentHeader = header;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800857 int index = mHeaders.indexOf(header);
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800858 if (index >= 0) {
859 mDrawer.setItemChecked(index, true);
860 } else {
861 mDrawer.clearChoices();
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800862 }
863 }
864
Fabrice Di Meglio75eabc22014-03-07 18:17:03 -0800865 private void highlightHeader(int id) {
866 if (id != 0) {
867 Integer index = mHeaderIndexMap.get(id);
868 if (index != null) {
869 mDrawer.setItemChecked(index, true);
870 if (mDrawer.getVisibility() == View.VISIBLE) {
871 mDrawer.smoothScrollToPosition(index);
872 }
873 }
874 }
875 }
876
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800877 /**
878 * When in two-pane mode, switch to the fragment pane to show the given
879 * preference fragment.
880 *
881 * @param header The new header to display.
882 * @param validate true means that the fragment's Header needs to be validated.
883 * @param initial true means that it is the initial Header.
884 */
885 private void switchToHeader(Header header, boolean validate, boolean initial) {
886 if (header == null) {
887 return;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800888 }
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800889 // For switching to another Header it should be a different one
890 if (mCurrentHeader == null || header.id != mCurrentHeader.id) {
891 if (header.fragment != null) {
Fabrice Di Meglio832e5462014-03-06 19:12:14 -0800892 boolean addToBackStack;
893 if (initial) {
894 addToBackStack = false;
895 } else {
896 if (header.id != mInitialHeader.id) {
897 addToBackStack = true;
898 } else {
899 addToBackStack = (mTopLevelHeaderId > 0);
900 }
901 }
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800902 switchToHeaderInner(header.fragment, header.fragmentArguments, validate,
903 addToBackStack, getHeaderTitle(header));
904 setSelectedHeader(header);
905 } else if (header.intent != null) {
906 setSelectedHeader(header);
907 startActivity(header.intent);
908 } else {
909 throw new IllegalStateException(
910 "Can't switch to header that has no Fragment nor Intent");
911 }
912 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800913 }
914
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000915 /**
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800916 * Switch the fragment pane to show the given preference fragment.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000917 *
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800918 * (used for initial fragment)
919 *
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000920 * @param fragmentName The name of the fragment to display.
921 * @param args Optional arguments to supply to the fragment.
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800922 * @param validate true means that the fragment's Header needs to be validated.
923 * @param title The title of the fragment to display.
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000924 */
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800925 private void switchToHeader(String fragmentName, Bundle args, boolean validate,
926 CharSequence title) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000927 setSelectedHeader(null);
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800928 switchToHeaderInner(fragmentName, args, validate, false, title);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000929 }
930
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -0800931 /**
932 * Switch to a specific Header with taking care of validation, Title and BackStack
933 */
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800934 private void switchToHeaderInner(String fragmentName, Bundle args, boolean validate,
935 boolean addToBackStack, CharSequence title) {
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +0000936 getFragmentManager().popBackStack(BACK_STACK_PREFS,
937 FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800938 if (validate && !isValidFragment(fragmentName)) {
939 throw new IllegalArgumentException("Invalid fragment for this activity: "
940 + fragmentName);
941 }
942 Fragment f = Fragment.instantiate(this, fragmentName, args);
943 FragmentTransaction transaction = getFragmentManager().beginTransaction();
Fabrice Di Meglio4cc95a52014-02-07 18:53:14 -0800944 transaction.replace(R.id.prefs, f);
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -0800945 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
946 if (addToBackStack) {
947 transaction.addToBackStack(BACK_STACK_PREFS);
948 }
949 if (title != null) {
950 transaction.setBreadCrumbTitle(title);
951 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800952 transaction.commitAllowingStateLoss();
953 }
954
955 @Override
956 public void onNewIntent(Intent intent) {
957 super.onNewIntent(intent);
958
959 // If it is not launched from history, then reset to top-level
960 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
961 if (mDrawer != null) {
962 mDrawer.setSelectionFromTop(0, 0);
963 }
964 }
965 }
966
967 /**
968 * Called to determine whether the header list should be hidden.
969 * The default implementation returns the
970 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
971 * This is set to false, for example, when the activity is being re-launched
972 * to show a particular preference activity.
973 */
974 public boolean onIsHidingHeaders() {
975 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
976 }
977
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -0800978 @Override
979 public Intent getIntent() {
980 Intent superIntent = super.getIntent();
981 String startingFragment = getStartingFragmentClass(superIntent);
982 // This is called from super.onCreate, isMultiPane() is not yet reliable
983 // Do not use onIsHidingHeaders either, which relies itself on this method
984 if (startingFragment != null) {
985 Intent modIntent = new Intent(superIntent);
986 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
987 Bundle args = superIntent.getExtras();
988 if (args != null) {
989 args = new Bundle(args);
990 } else {
991 args = new Bundle();
992 }
993 args.putParcelable("intent", superIntent);
994 modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
995 return modIntent;
996 }
997 return superIntent;
998 }
999
1000 /**
1001 * Checks if the component name in the intent is different from the Settings class and
1002 * returns the class name to load as a fragment.
1003 */
1004 private String getStartingFragmentClass(Intent intent) {
1005 if (mFragmentClass != null) return mFragmentClass;
1006
1007 String intentClass = intent.getComponent().getClassName();
1008 if (intentClass.equals(getClass().getName())) return null;
1009
1010 if ("com.android.settings.ManageApplications".equals(intentClass)
1011 || "com.android.settings.RunningServices".equals(intentClass)
1012 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
1013 // Old names of manage apps.
1014 intentClass = com.android.settings.applications.ManageApplications.class.getName();
1015 }
1016
1017 return intentClass;
1018 }
1019
1020 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001021 * Start a new fragment containing a preference panel. If the preferences
1022 * are being displayed in multi-pane mode, the given fragment class will
1023 * be instantiated and placed in the appropriate pane. If running in
1024 * single-pane mode, a new activity will be launched in which to show the
1025 * fragment.
1026 *
1027 * @param fragmentClass Full name of the class implementing the fragment.
1028 * @param args Any desired arguments to supply to the fragment.
1029 * @param titleRes Optional resource identifier of the title of this
1030 * fragment.
1031 * @param titleText Optional text of the title of this fragment.
1032 * @param resultTo Optional fragment that result data should be sent to.
1033 * If non-null, resultTo.onActivityResult() will be called when this
1034 * preference panel is done. The launched panel must use
1035 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
1036 * @param resultRequestCode If resultTo is non-null, this is the caller's
1037 * request code to be received with the resut.
1038 */
1039 public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
1040 CharSequence titleText, Fragment resultTo,
1041 int resultRequestCode) {
1042 startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, titleText);
1043 }
1044
1045 /**
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001046 * Called by a preference panel fragment to finish itself.
1047 *
1048 * @param caller The fragment that is asking to be finished.
1049 * @param resultCode Optional result code to send back to the original
1050 * launching fragment.
1051 * @param resultData Optional result data to send back to the original
1052 * launching fragment.
1053 */
1054 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
1055 setResult(resultCode, resultData);
1056 }
1057
1058 /**
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001059 * Start a new fragment.
1060 *
1061 * @param fragment The fragment to start
1062 * @param push If true, the current fragment will be pushed onto the back stack. If false,
1063 * the current fragment will be replaced.
1064 */
1065 public void startPreferenceFragment(Fragment fragment, boolean push) {
1066 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1067 transaction.replace(R.id.prefs, fragment);
1068 if (push) {
1069 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1070 transaction.addToBackStack(BACK_STACK_PREFS);
1071 } else {
1072 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
1073 }
1074 transaction.commitAllowingStateLoss();
1075 }
1076
1077 /**
1078 * Start a new fragment.
1079 *
1080 * @param fragmentName The name of the fragment to display.
1081 * @param args Optional arguments to supply to the fragment.
1082 * @param resultTo Option fragment that should receive the result of
1083 * the activity launch.
1084 * @param resultRequestCode If resultTo is non-null, this is the request code in which to
1085 * report the result.
1086 * @param titleRes Resource ID of string to display for the title of. If the Resource ID is a
1087 * valid one then it will be used to get the title. Otherwise the titleText
1088 * argument will be used as the title.
1089 * @param titleText string to display for the title of.
1090 */
1091 private void startWithFragment(String fragmentName, Bundle args, Fragment resultTo,
1092 int resultRequestCode, int titleRes, CharSequence titleText) {
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -08001093 final CharSequence cs;
1094 if (titleRes != 0) {
1095 cs = getText(titleRes);
1096 } else {
1097 cs = titleText;
1098 }
1099
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001100 Fragment f = Fragment.instantiate(this, fragmentName, args);
1101 if (resultTo != null) {
1102 f.setTargetFragment(resultTo, resultRequestCode);
1103 }
1104 FragmentTransaction transaction = getFragmentManager().beginTransaction();
1105 transaction.replace(R.id.prefs, f);
1106 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
1107 transaction.addToBackStack(BACK_STACK_PREFS);
Fabrice Di Meglio8eb3f0f2014-02-27 15:51:46 -08001108 transaction.setBreadCrumbTitle(cs);
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001109 transaction.commitAllowingStateLoss();
Fabrice Di Meglio10afdb82014-02-11 19:50:56 +00001110 }
1111
1112 /**
Fabrice Di Meglio6d534a12014-03-03 11:34:18 -08001113 * Called when the activity needs its list of headers build.
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001114 *
1115 * @param headers The list in which to place the headers.
1116 */
1117 private void onBuildHeaders(List<Header> headers) {
1118 loadHeadersFromResource(R.xml.settings_headers, headers);
1119 updateHeaderList(headers);
1120 }
1121
1122 /**
1123 * Parse the given XML file as a header description, adding each
1124 * parsed Header into the target list.
1125 *
1126 * @param resid The XML resource to load and parse.
1127 * @param target The list in which the parsed headers should be placed.
1128 */
1129 private void loadHeadersFromResource(int resid, List<Header> target) {
1130 XmlResourceParser parser = null;
1131 try {
1132 parser = getResources().getXml(resid);
1133 AttributeSet attrs = Xml.asAttributeSet(parser);
1134
1135 int type;
1136 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1137 && type != XmlPullParser.START_TAG) {
1138 // Parse next until start tag is found
1139 }
1140
1141 String nodeName = parser.getName();
1142 if (!"preference-headers".equals(nodeName)) {
1143 throw new RuntimeException(
1144 "XML document must start with <preference-headers> tag; found"
1145 + nodeName + " at " + parser.getPositionDescription());
1146 }
1147
1148 Bundle curBundle = null;
1149
1150 final int outerDepth = parser.getDepth();
1151 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1152 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1153 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1154 continue;
1155 }
1156
1157 nodeName = parser.getName();
1158 if ("header".equals(nodeName)) {
1159 Header header = new Header();
1160
1161 TypedArray sa = obtainStyledAttributes(
1162 attrs, com.android.internal.R.styleable.PreferenceHeader);
1163 header.id = sa.getResourceId(
1164 com.android.internal.R.styleable.PreferenceHeader_id,
1165 (int)HEADER_ID_UNDEFINED);
1166 TypedValue tv = sa.peekValue(
1167 com.android.internal.R.styleable.PreferenceHeader_title);
1168 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1169 if (tv.resourceId != 0) {
1170 header.titleRes = tv.resourceId;
1171 } else {
1172 header.title = tv.string;
1173 }
1174 }
1175 tv = sa.peekValue(
1176 com.android.internal.R.styleable.PreferenceHeader_summary);
1177 if (tv != null && tv.type == TypedValue.TYPE_STRING) {
1178 if (tv.resourceId != 0) {
1179 header.summaryRes = tv.resourceId;
1180 } else {
1181 header.summary = tv.string;
1182 }
1183 }
1184 header.iconRes = sa.getResourceId(
1185 com.android.internal.R.styleable.PreferenceHeader_icon, 0);
1186 header.fragment = sa.getString(
1187 com.android.internal.R.styleable.PreferenceHeader_fragment);
1188 sa.recycle();
1189
1190 if (curBundle == null) {
1191 curBundle = new Bundle();
1192 }
1193
1194 final int innerDepth = parser.getDepth();
1195 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1196 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
1197 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1198 continue;
1199 }
1200
1201 String innerNodeName = parser.getName();
1202 if (innerNodeName.equals("extra")) {
1203 getResources().parseBundleExtra("extra", attrs, curBundle);
1204 XmlUtils.skipCurrentTag(parser);
1205
1206 } else if (innerNodeName.equals("intent")) {
1207 header.intent = Intent.parseIntent(getResources(), parser, attrs);
1208
1209 } else {
1210 XmlUtils.skipCurrentTag(parser);
1211 }
1212 }
1213
1214 if (curBundle.size() > 0) {
1215 header.fragmentArguments = curBundle;
1216 curBundle = null;
1217 }
1218
1219 target.add(header);
1220 } else {
1221 XmlUtils.skipCurrentTag(parser);
1222 }
1223 }
1224
1225 } catch (XmlPullParserException e) {
1226 throw new RuntimeException("Error parsing headers", e);
1227 } catch (IOException e) {
1228 throw new RuntimeException("Error parsing headers", e);
1229 } finally {
1230 if (parser != null) parser.close();
1231 }
1232 }
1233
1234 private void updateHeaderList(List<Header> target) {
1235 final boolean showDev = mDevelopmentPreferences.getBoolean(
1236 DevelopmentSettings.PREF_SHOW,
1237 android.os.Build.TYPE.equals("eng"));
1238 int i = 0;
1239
1240 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
1241 mHeaderIndexMap.clear();
1242 while (i < target.size()) {
1243 Header header = target.get(i);
1244 // Ids are integers, so downcasting
1245 int id = (int) header.id;
1246 if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
1247 Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
1248 } else if (id == R.id.wifi_settings) {
1249 // Remove WiFi Settings if WiFi service is not available.
1250 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
1251 target.remove(i);
1252 }
1253 } else if (id == R.id.bluetooth_settings) {
1254 // Remove Bluetooth Settings if Bluetooth service is not available.
1255 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
1256 target.remove(i);
1257 }
1258 } else if (id == R.id.data_usage_settings) {
1259 // Remove data usage when kernel module not enabled
1260 final INetworkManagementService netManager = INetworkManagementService.Stub
1261 .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
1262 try {
1263 if (!netManager.isBandwidthControlEnabled()) {
1264 target.remove(i);
1265 }
1266 } catch (RemoteException e) {
1267 // ignored
1268 }
1269 } else if (id == R.id.battery_settings) {
1270 // Remove battery settings when battery is not available. (e.g. TV)
1271
1272 if (!mBatteryPresent) {
1273 target.remove(i);
1274 }
1275 } else if (id == R.id.account_settings) {
1276 int headerIndex = i + 1;
1277 i = insertAccountsHeaders(target, headerIndex);
1278 } else if (id == R.id.home_settings) {
1279 if (!updateHomeSettingHeaders(header)) {
1280 target.remove(i);
1281 }
1282 } else if (id == R.id.user_settings) {
1283 if (!UserHandle.MU_ENABLED
1284 || !UserManager.supportsMultipleUsers()
1285 || Utils.isMonkeyRunning()) {
1286 target.remove(i);
1287 }
1288 } else if (id == R.id.nfc_payment_settings) {
1289 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
1290 target.remove(i);
1291 } else {
1292 // Only show if NFC is on and we have the HCE feature
1293 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
1294 if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
1295 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
1296 target.remove(i);
1297 }
1298 }
1299 } else if (id == R.id.development_settings) {
1300 if (!showDev) {
1301 target.remove(i);
1302 }
1303 } else if (id == R.id.account_add) {
1304 if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
1305 target.remove(i);
1306 }
1307 }
1308
1309 if (i < target.size() && target.get(i) == header
1310 && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
1311 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
1312 target.remove(i);
1313 }
1314
1315 // Increment if the current one wasn't removed by the Utils code.
1316 if (i < target.size() && target.get(i) == header) {
1317 // Hold on to the first header, when we need to reset to the top-level
1318 if (mFirstHeader == null &&
1319 HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
1320 mFirstHeader = header;
1321 }
1322 mHeaderIndexMap.put(id, i);
1323 i++;
1324 }
1325 }
1326 }
1327
1328 private int insertAccountsHeaders(List<Header> target, int headerIndex) {
1329 String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
1330 List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
1331 for (String accountType : accountTypes) {
1332 CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
1333 if (label == null) {
1334 continue;
1335 }
1336
1337 Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
1338 boolean skipToAccount = accounts.length == 1
1339 && !mAuthenticatorHelper.hasAccountPreferences(accountType);
1340 Header accHeader = new Header();
1341 accHeader.title = label;
1342 if (accHeader.extras == null) {
1343 accHeader.extras = new Bundle();
1344 }
1345 if (skipToAccount) {
1346 accHeader.fragment = AccountSyncSettings.class.getName();
1347 accHeader.fragmentArguments = new Bundle();
1348 // Need this for the icon
1349 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1350 accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
1351 accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
1352 accounts[0]);
1353 } else {
1354 accHeader.fragment = ManageAccountsSettings.class.getName();
1355 accHeader.fragmentArguments = new Bundle();
1356 accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
1357 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
1358 accountType);
1359 accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
1360 label.toString());
1361 }
1362 accountHeaders.add(accHeader);
1363 mAuthenticatorHelper.preloadDrawableForType(this, accountType);
1364 }
1365
1366 // Sort by label
1367 Collections.sort(accountHeaders, new Comparator<Header>() {
1368 @Override
1369 public int compare(Header h1, Header h2) {
1370 return h1.title.toString().compareTo(h2.title.toString());
1371 }
1372 });
1373
1374 for (Header header : accountHeaders) {
1375 target.add(headerIndex++, header);
1376 }
1377 if (!mListeningToAccountUpdates) {
1378 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
1379 mListeningToAccountUpdates = true;
1380 }
1381 return headerIndex;
1382 }
1383
1384 private boolean updateHomeSettingHeaders(Header header) {
1385 // Once we decide to show Home settings, keep showing it forever
1386 SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
1387 if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
1388 return true;
1389 }
1390
1391 try {
1392 final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
1393 getPackageManager().getHomeActivities(homeApps);
1394 if (homeApps.size() < 2) {
1395 // When there's only one available home app, omit this settings
1396 // category entirely at the top level UI. If the user just
1397 // uninstalled the penultimate home app candidiate, we also
1398 // now tell them about why they aren't seeing 'Home' in the list.
1399 if (sShowNoHomeNotice) {
1400 sShowNoHomeNotice = false;
1401 NoHomeDialogFragment.show(this);
1402 }
1403 return false;
1404 } else {
1405 // Okay, we're allowing the Home settings category. Tell it, when
1406 // invoked via this front door, that we'll need to be told about the
1407 // case when the user uninstalls all but one home app.
1408 if (header.fragmentArguments == null) {
1409 header.fragmentArguments = new Bundle();
1410 }
1411 header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
1412 }
1413 } catch (Exception e) {
1414 // Can't look up the home activity; bail on configuring the icon
1415 Log.w(LOG_TAG, "Problem looking up home activity!", e);
1416 }
1417
1418 sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
1419 return true;
1420 }
1421
1422 private void getMetaData() {
1423 try {
1424 ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
1425 PackageManager.GET_META_DATA);
1426 if (ai == null || ai.metaData == null) return;
1427 mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
1428 mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
1429 } catch (NameNotFoundException nnfe) {
1430 // No recovery
1431 Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
1432 }
1433 }
1434
1435 // give subclasses access to the Next button
1436 public boolean hasNextButton() {
1437 return mNextButton != null;
1438 }
1439
1440 public Button getNextButton() {
1441 return mNextButton;
1442 }
1443
1444 public static class NoHomeDialogFragment extends DialogFragment {
1445 public static void show(Activity parent) {
1446 final NoHomeDialogFragment dialog = new NoHomeDialogFragment();
1447 dialog.show(parent.getFragmentManager(), null);
1448 }
1449
1450 @Override
1451 public Dialog onCreateDialog(Bundle savedInstanceState) {
1452 return new AlertDialog.Builder(getActivity())
1453 .setMessage(R.string.only_one_home_message)
1454 .setPositiveButton(android.R.string.ok, null)
1455 .create();
1456 }
1457 }
1458
1459 private static class HeaderAdapter extends ArrayAdapter<Header> {
1460 static final int HEADER_TYPE_CATEGORY = 0;
1461 static final int HEADER_TYPE_NORMAL = 1;
1462 static final int HEADER_TYPE_SWITCH = 2;
1463 static final int HEADER_TYPE_BUTTON = 3;
1464 private static final int HEADER_TYPE_COUNT = HEADER_TYPE_BUTTON + 1;
1465
1466 private final WifiEnabler mWifiEnabler;
1467 private final BluetoothEnabler mBluetoothEnabler;
1468 private AuthenticatorHelper mAuthHelper;
1469 private DevicePolicyManager mDevicePolicyManager;
1470
1471 private static class HeaderViewHolder {
1472 ImageView mIcon;
1473 TextView mTitle;
1474 TextView mSummary;
1475 Switch mSwitch;
1476 ImageButton mButton;
1477 View mDivider;
1478 }
1479
1480 private LayoutInflater mInflater;
1481
1482 static int getHeaderType(Header header) {
1483 if (header.fragment == null && header.intent == null) {
1484 return HEADER_TYPE_CATEGORY;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001485 } else if (header.id == R.id.security_settings) {
1486 return HEADER_TYPE_BUTTON;
1487 } else {
1488 return HEADER_TYPE_NORMAL;
1489 }
1490 }
1491
1492 @Override
1493 public int getItemViewType(int position) {
1494 Header header = getItem(position);
1495 return getHeaderType(header);
1496 }
1497
1498 @Override
1499 public boolean areAllItemsEnabled() {
1500 return false; // because of categories
1501 }
1502
1503 @Override
1504 public boolean isEnabled(int position) {
1505 return getItemViewType(position) != HEADER_TYPE_CATEGORY;
1506 }
1507
1508 @Override
1509 public int getViewTypeCount() {
1510 return HEADER_TYPE_COUNT;
1511 }
1512
1513 @Override
1514 public boolean hasStableIds() {
1515 return true;
1516 }
1517
1518 public HeaderAdapter(Context context, List<Header> objects,
1519 AuthenticatorHelper authenticatorHelper, DevicePolicyManager dpm) {
1520 super(context, 0, objects);
1521
1522 mAuthHelper = authenticatorHelper;
1523 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1524
1525 // Temp Switches provided as placeholder until the adapter replaces these with actual
1526 // Switches inflated from their layouts. Must be done before adapter is set in super
1527 mWifiEnabler = new WifiEnabler(context, new Switch(context));
1528 mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
1529 mDevicePolicyManager = dpm;
1530 }
1531
1532 @Override
1533 public View getView(int position, View convertView, ViewGroup parent) {
1534 HeaderViewHolder holder;
1535 Header header = getItem(position);
1536 int headerType = getHeaderType(header);
1537 View view = null;
1538
1539 if (convertView == null) {
1540 holder = new HeaderViewHolder();
1541 switch (headerType) {
1542 case HEADER_TYPE_CATEGORY:
1543 view = new TextView(getContext(), null,
1544 android.R.attr.listSeparatorTextViewStyle);
1545 holder.mTitle = (TextView) view;
1546 break;
1547
1548 case HEADER_TYPE_SWITCH:
1549 view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
1550 false);
1551 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1552 holder.mTitle = (TextView)
1553 view.findViewById(com.android.internal.R.id.title);
1554 holder.mSummary = (TextView)
1555 view.findViewById(com.android.internal.R.id.summary);
1556 holder.mSwitch = (Switch) view.findViewById(R.id.switchWidget);
1557 break;
1558
1559 case HEADER_TYPE_BUTTON:
1560 view = mInflater.inflate(R.layout.preference_header_button_item, parent,
1561 false);
1562 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1563 holder.mTitle = (TextView)
1564 view.findViewById(com.android.internal.R.id.title);
1565 holder.mSummary = (TextView)
1566 view.findViewById(com.android.internal.R.id.summary);
1567 holder.mButton = (ImageButton) view.findViewById(R.id.buttonWidget);
1568 holder.mDivider = view.findViewById(R.id.divider);
1569 break;
1570
1571 case HEADER_TYPE_NORMAL:
1572 view = mInflater.inflate(
1573 R.layout.preference_header_item, parent,
1574 false);
1575 holder.mIcon = (ImageView) view.findViewById(R.id.icon);
1576 holder.mTitle = (TextView)
1577 view.findViewById(com.android.internal.R.id.title);
1578 holder.mSummary = (TextView)
1579 view.findViewById(com.android.internal.R.id.summary);
1580 break;
1581 }
Fabrice Di Meglio0e2f9492014-02-25 14:26:27 -08001582 if (holder.mIcon != null) {
1583 holder.mIcon.setBackgroundResource(R.color.background_drawer_icon);
1584 }
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001585 view.setTag(holder);
1586 } else {
1587 view = convertView;
1588 holder = (HeaderViewHolder) view.getTag();
1589 }
1590
1591 // All view fields must be updated every time, because the view may be recycled
1592 switch (headerType) {
1593 case HEADER_TYPE_CATEGORY:
1594 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1595 break;
1596
1597 case HEADER_TYPE_SWITCH:
1598 // Would need a different treatment if the main menu had more switches
1599 if (header.id == R.id.wifi_settings) {
1600 mWifiEnabler.setSwitch(holder.mSwitch);
1601 } else {
1602 mBluetoothEnabler.setSwitch(holder.mSwitch);
1603 }
1604 updateCommonHeaderView(header, holder);
1605 break;
1606
1607 case HEADER_TYPE_BUTTON:
1608 if (header.id == R.id.security_settings) {
1609 boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled();
1610 if (hasCert) {
1611 holder.mButton.setVisibility(View.VISIBLE);
1612 holder.mDivider.setVisibility(View.VISIBLE);
1613 boolean isManaged = mDevicePolicyManager.getDeviceOwner() != null;
1614 if (isManaged) {
1615 holder.mButton.setImageResource(R.drawable.ic_settings_about);
1616 } else {
1617 holder.mButton.setImageResource(
1618 android.R.drawable.stat_notify_error);
1619 }
1620 holder.mButton.setOnClickListener(new OnClickListener() {
1621 @Override
1622 public void onClick(View v) {
1623 Intent intent = new Intent(
1624 android.provider.Settings.ACTION_MONITORING_CERT_INFO);
1625 getContext().startActivity(intent);
1626 }
1627 });
1628 } else {
1629 holder.mButton.setVisibility(View.GONE);
1630 holder.mDivider.setVisibility(View.GONE);
1631 }
1632 }
1633 updateCommonHeaderView(header, holder);
1634 break;
1635
1636 case HEADER_TYPE_NORMAL:
1637 updateCommonHeaderView(header, holder);
1638 break;
1639 }
1640
1641 return view;
1642 }
1643
1644 private void updateCommonHeaderView(Header header, HeaderViewHolder holder) {
1645 if (header.extras != null
1646 && header.extras.containsKey(ManageAccountsSettings.KEY_ACCOUNT_TYPE)) {
1647 String accType = header.extras.getString(
1648 ManageAccountsSettings.KEY_ACCOUNT_TYPE);
1649 Drawable icon = mAuthHelper.getDrawableForType(getContext(), accType);
1650 setHeaderIcon(holder, icon);
1651 } else {
1652 holder.mIcon.setImageResource(header.iconRes);
1653 }
1654 holder.mTitle.setText(header.getTitle(getContext().getResources()));
1655 CharSequence summary = header.getSummary(getContext().getResources());
1656 if (!TextUtils.isEmpty(summary)) {
1657 holder.mSummary.setVisibility(View.VISIBLE);
1658 holder.mSummary.setText(summary);
1659 } else {
1660 holder.mSummary.setVisibility(View.GONE);
1661 }
1662 }
1663
1664 private void setHeaderIcon(HeaderViewHolder holder, Drawable icon) {
1665 ViewGroup.LayoutParams lp = holder.mIcon.getLayoutParams();
1666 lp.width = getContext().getResources().getDimensionPixelSize(
1667 R.dimen.header_icon_width);
1668 lp.height = lp.width;
1669 holder.mIcon.setLayoutParams(lp);
1670 holder.mIcon.setImageDrawable(icon);
1671 }
1672
Matthew Xiea504c4d2014-02-14 16:32:32 -08001673 public void resume(Context context) {
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001674 mWifiEnabler.resume();
Matthew Xiea504c4d2014-02-14 16:32:32 -08001675 mBluetoothEnabler.resume(context);
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001676 }
1677
1678 public void pause() {
1679 mWifiEnabler.pause();
1680 mBluetoothEnabler.pause();
1681 }
1682 }
1683
1684 private void onListItemClick(ListView l, View v, int position, long id) {
1685 if (!isResumed()) {
1686 return;
1687 }
1688 Object item = mHeaderAdapter.getItem(position);
1689 if (item instanceof Header) {
Fabrice Di Meglio65027202014-02-11 15:19:46 -08001690 mSelectedHeader = (Header) item;
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001691 }
1692 }
1693
Fabrice Di Meglio263bcc82014-01-17 19:17:58 -08001694 @Override
1695 public boolean shouldUpRecreateTask(Intent targetIntent) {
1696 return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
1697 }
1698
1699 @Override
1700 public void onAccountsUpdated(Account[] accounts) {
1701 // TODO: watch for package upgrades to invalidate cache; see 7206643
1702 mAuthenticatorHelper.updateAuthDescriptions(this);
1703 mAuthenticatorHelper.onAccountsUpdated(this, accounts);
1704 invalidateHeaders();
1705 }
1706
1707 public static void requestHomeNotice() {
1708 sShowNoHomeNotice = true;
1709 }
1710
1711 /**
1712 * Default value for {@link Header#id Header.id} indicating that no
1713 * identifier value is set. All other values (including those below -1)
1714 * are valid.
1715 */
1716 private static final long HEADER_ID_UNDEFINED = -1;
1717
1718 /**
1719 * Description of a single Header item that the user can select.
1720 */
1721 static final class Header implements Parcelable {
1722 /**
1723 * Identifier for this header, to correlate with a new list when
1724 * it is updated. The default value is
1725 * {@link SettingsActivity#HEADER_ID_UNDEFINED}, meaning no id.
1726 * @attr ref android.R.styleable#PreferenceHeader_id
1727 */
1728 public long id = HEADER_ID_UNDEFINED;
1729
1730 /**
1731 * Resource ID of title of the header that is shown to the user.
1732 * @attr ref android.R.styleable#PreferenceHeader_title
1733 */
1734 public int titleRes;
1735
1736 /**
1737 * Title of the header that is shown to the user.
1738 * @attr ref android.R.styleable#PreferenceHeader_title
1739 */
1740 public CharSequence title;
1741
1742 /**
1743 * Resource ID of optional summary describing what this header controls.
1744 * @attr ref android.R.styleable#PreferenceHeader_summary
1745 */
1746 public int summaryRes;
1747
1748 /**
1749 * Optional summary describing what this header controls.
1750 * @attr ref android.R.styleable#PreferenceHeader_summary
1751 */
1752 public CharSequence summary;
1753
1754 /**
1755 * Optional icon resource to show for this header.
1756 * @attr ref android.R.styleable#PreferenceHeader_icon
1757 */
1758 public int iconRes;
1759
1760 /**
1761 * Full class name of the fragment to display when this header is
1762 * selected.
1763 * @attr ref android.R.styleable#PreferenceHeader_fragment
1764 */
1765 public String fragment;
1766
1767 /**
1768 * Optional arguments to supply to the fragment when it is
1769 * instantiated.
1770 */
1771 public Bundle fragmentArguments;
1772
1773 /**
1774 * Intent to launch when the preference is selected.
1775 */
1776 public Intent intent;
1777
1778 /**
1779 * Optional additional data for use by subclasses of the activity
1780 */
1781 public Bundle extras;
1782
1783 public Header() {
1784 // Empty
1785 }
1786
1787 /**
1788 * Return the currently set title. If {@link #titleRes} is set,
1789 * this resource is loaded from <var>res</var> and returned. Otherwise
1790 * {@link #title} is returned.
1791 */
1792 public CharSequence getTitle(Resources res) {
1793 if (titleRes != 0) {
1794 return res.getText(titleRes);
1795 }
1796 return title;
1797 }
1798
1799 /**
1800 * Return the currently set summary. If {@link #summaryRes} is set,
1801 * this resource is loaded from <var>res</var> and returned. Otherwise
1802 * {@link #summary} is returned.
1803 */
1804 public CharSequence getSummary(Resources res) {
1805 if (summaryRes != 0) {
1806 return res.getText(summaryRes);
1807 }
1808 return summary;
1809 }
1810
1811 @Override
1812 public int describeContents() {
1813 return 0;
1814 }
1815
1816 @Override
1817 public void writeToParcel(Parcel dest, int flags) {
1818 dest.writeLong(id);
1819 dest.writeInt(titleRes);
1820 TextUtils.writeToParcel(title, dest, flags);
1821 dest.writeInt(summaryRes);
1822 TextUtils.writeToParcel(summary, dest, flags);
1823 dest.writeInt(iconRes);
1824 dest.writeString(fragment);
1825 dest.writeBundle(fragmentArguments);
1826 if (intent != null) {
1827 dest.writeInt(1);
1828 intent.writeToParcel(dest, flags);
1829 } else {
1830 dest.writeInt(0);
1831 }
1832 dest.writeBundle(extras);
1833 }
1834
1835 public void readFromParcel(Parcel in) {
1836 id = in.readLong();
1837 titleRes = in.readInt();
1838 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1839 summaryRes = in.readInt();
1840 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1841 iconRes = in.readInt();
1842 fragment = in.readString();
1843 fragmentArguments = in.readBundle();
1844 if (in.readInt() != 0) {
1845 intent = Intent.CREATOR.createFromParcel(in);
1846 }
1847 extras = in.readBundle();
1848 }
1849
1850 Header(Parcel in) {
1851 readFromParcel(in);
1852 }
1853
1854 public static final Creator<Header> CREATOR = new Creator<Header>() {
1855 public Header createFromParcel(Parcel source) {
1856 return new Header(source);
1857 }
1858 public Header[] newArray(int size) {
1859 return new Header[size];
1860 }
1861 };
1862 }
1863}