1 /*
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 
17 package com.android.settings;
18 
19 import android.app.ActionBar;
20 import android.app.ActivityManager;
21 import android.app.Fragment;
22 import android.app.FragmentManager;
23 import android.app.FragmentTransaction;
24 import android.content.BroadcastReceiver;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.SharedPreferences;
30 import android.content.pm.ActivityInfo;
31 import android.content.pm.PackageManager;
32 import android.content.pm.PackageManager.NameNotFoundException;
33 import android.graphics.Bitmap;
34 import android.graphics.Canvas;
35 import android.graphics.drawable.Drawable;
36 import android.nfc.NfcAdapter;
37 import android.os.AsyncTask;
38 import android.os.Bundle;
39 import android.os.UserHandle;
40 import android.os.UserManager;
41 import android.support.annotation.VisibleForTesting;
42 import android.support.v14.preference.PreferenceFragment;
43 import android.support.v7.preference.Preference;
44 import android.support.v7.preference.PreferenceManager;
45 import android.text.TextUtils;
46 import android.transition.TransitionManager;
47 import android.util.Log;
48 import android.view.Menu;
49 import android.view.View;
50 import android.view.View.OnClickListener;
51 import android.view.ViewGroup;
52 import android.widget.Button;
53 
54 import com.android.internal.util.ArrayUtils;
55 import com.android.settings.Settings.WifiSettingsActivity;
56 import com.android.settings.backup.BackupSettingsActivity;
57 import com.android.settings.core.gateway.SettingsGateway;
58 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
59 import com.android.settings.core.instrumentation.SharedPreferencesLogger;
60 import com.android.settings.dashboard.DashboardFeatureProvider;
61 import com.android.settings.dashboard.DashboardSummary;
62 import com.android.settings.development.DevelopmentSettings;
63 import com.android.settings.overlay.FeatureFactory;
64 import com.android.settings.search.DynamicIndexableContentMonitor;
65 import com.android.settings.search2.SearchFeatureProvider;
66 import com.android.settings.wfd.WifiDisplaySettings;
67 import com.android.settings.widget.SwitchBar;
68 import com.android.settingslib.drawer.DashboardCategory;
69 import com.android.settingslib.drawer.SettingsDrawerActivity;
70 
71 import java.util.ArrayList;
72 import java.util.List;
73 import java.util.Set;
74 
75 public class SettingsActivity extends SettingsDrawerActivity
76         implements PreferenceManager.OnPreferenceTreeClickListener,
77         PreferenceFragment.OnPreferenceStartFragmentCallback,
78         ButtonBarHandler, FragmentManager.OnBackStackChangedListener {
79 
80     private static final String LOG_TAG = "Settings";
81 
82     public static final int LOADER_ID_INDEXABLE_CONTENT_MONITOR = 1;
83 
84     // Constants for state save/restore
85     private static final String SAVE_KEY_CATEGORIES = ":settings:categories";
86     @VisibleForTesting
87     static final String SAVE_KEY_SHOW_HOME_AS_UP = ":settings:show_home_as_up";
88     @VisibleForTesting
89     static final String SAVE_KEY_SHOW_SEARCH = ":settings:show_search";
90 
91     /**
92      * When starting this activity, the invoking Intent can contain this extra
93      * string to specify which fragment should be initially displayed.
94      * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
95      * will call isValidFragment() to confirm that the fragment class name is valid for this
96      * activity.
97      */
98     public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
99 
100     /**
101      * The metrics category constant for logging source when a setting fragment is opened.
102      */
103     public static final String EXTRA_SOURCE_METRICS_CATEGORY = ":settings:source_metrics";
104 
105     /**
106      * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
107      * this extra can also be specified to supply a Bundle of arguments to pass
108      * to that fragment when it is instantiated during the initial creation
109      * of the activity.
110      */
111     public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
112 
113     /**
114      * Fragment "key" argument passed thru {@link #EXTRA_SHOW_FRAGMENT_ARGUMENTS}
115      */
116     public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
117 
118     public static final String BACK_STACK_PREFS = ":settings:prefs";
119 
120     // extras that allow any preference activity to be launched as part of a wizard
121 
122     // show Back and Next buttons? takes boolean parameter
123     // Back will then return RESULT_CANCELED and Next RESULT_OK
124     protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
125 
126     // add a Skip button?
127     private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
128 
129     // specify custom text for the Back or Next buttons, or cause a button to not appear
130     // at all by setting it to null
131     protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
132     protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
133 
134     /**
135      * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
136      * those extra can also be specify to supply the title or title res id to be shown for
137      * that fragment.
138      */
139     public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
140     /**
141      * The package name used to resolve the title resource id.
142      */
143     public static final String EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME =
144             ":settings:show_fragment_title_res_package_name";
145     public static final String EXTRA_SHOW_FRAGMENT_TITLE_RESID =
146             ":settings:show_fragment_title_resid";
147     public static final String EXTRA_SHOW_FRAGMENT_AS_SHORTCUT =
148             ":settings:show_fragment_as_shortcut";
149 
150     public static final String EXTRA_SHOW_FRAGMENT_AS_SUBSETTING =
151             ":settings:show_fragment_as_subsetting";
152 
153     @Deprecated
154     public static final String EXTRA_HIDE_DRAWER = ":settings:hide_drawer";
155 
156     public static final String META_DATA_KEY_FRAGMENT_CLASS =
157         "com.android.settings.FRAGMENT_CLASS";
158 
159     private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
160 
161     private static final int REQUEST_SUGGESTION = 42;
162 
163     private String mFragmentClass;
164 
165     private CharSequence mInitialTitle;
166     private int mInitialTitleResId;
167 
168     private static final String[] LIKE_SHORTCUT_INTENT_ACTION_ARRAY = {
169             "android.settings.APPLICATION_DETAILS_SETTINGS"
170     };
171 
172     private SharedPreferences mDevelopmentPreferences;
173     private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
174 
175     private boolean mBatteryPresent = true;
176     private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
177         @Override
178         public void onReceive(Context context, Intent intent) {
179             String action = intent.getAction();
180             if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
181                 boolean batteryPresent = Utils.isBatteryPresent(intent);
182 
183                 if (mBatteryPresent != batteryPresent) {
184                     mBatteryPresent = batteryPresent;
185                     updateTilesList();
186                 }
187             }
188         }
189     };
190 
191     private DynamicIndexableContentMonitor mDynamicIndexableContentMonitor;
192 
193     private ActionBar mActionBar;
194     private SwitchBar mSwitchBar;
195 
196     private Button mNextButton;
197 
198     @VisibleForTesting
199     boolean mDisplayHomeAsUpEnabled;
200     @VisibleForTesting
201     boolean mDisplaySearch;
202 
203     private boolean mIsShowingDashboard;
204     private boolean mIsShortcut;
205 
206     private ViewGroup mContent;
207 
208     private SearchFeatureProvider mSearchFeatureProvider;
209     private MetricsFeatureProvider mMetricsFeatureProvider;
210 
211     // Categories
212     private ArrayList<DashboardCategory> mCategories = new ArrayList<>();
213 
214     private DashboardFeatureProvider mDashboardFeatureProvider;
215     private ComponentName mCurrentSuggestion;
216 
getSwitchBar()217     public SwitchBar getSwitchBar() {
218         return mSwitchBar;
219     }
220 
221     @Override
onPreferenceStartFragment(PreferenceFragment caller, Preference pref)222     public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
223         startPreferencePanel(caller, pref.getFragment(), pref.getExtras(), -1, pref.getTitle(),
224                 null, 0);
225         return true;
226     }
227 
228     @Override
onPreferenceTreeClick(Preference preference)229     public boolean onPreferenceTreeClick(Preference preference) {
230         return false;
231     }
232 
233     @Override
onCreateOptionsMenu(Menu menu)234     public boolean onCreateOptionsMenu(Menu menu) {
235         if (!mDisplaySearch) {
236             return false;
237         }
238         mSearchFeatureProvider.setUpSearchMenu(menu, this);
239         return true;
240     }
241 
242     @Override
getSharedPreferences(String name, int mode)243     public SharedPreferences getSharedPreferences(String name, int mode) {
244         if (name.equals(getPackageName() + "_preferences")) {
245             return new SharedPreferencesLogger(this, getMetricsTag());
246         }
247         return super.getSharedPreferences(name, mode);
248     }
249 
getMetricsTag()250     private String getMetricsTag() {
251         String tag = getClass().getName();
252         if (getIntent() != null && getIntent().hasExtra(EXTRA_SHOW_FRAGMENT)) {
253             tag = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
254         }
255         if (tag.startsWith("com.android.settings.")) {
256             tag = tag.replace("com.android.settings.", "");
257         }
258         return tag;
259     }
260 
isShortCutIntent(final Intent intent)261     private static boolean isShortCutIntent(final Intent intent) {
262         Set<String> categories = intent.getCategories();
263         return (categories != null) && categories.contains("com.android.settings.SHORTCUT");
264     }
265 
isLikeShortCutIntent(final Intent intent)266     private static boolean isLikeShortCutIntent(final Intent intent) {
267         String action = intent.getAction();
268         if (action == null) {
269             return false;
270         }
271         for (int i = 0; i < LIKE_SHORTCUT_INTENT_ACTION_ARRAY.length; i++) {
272             if (LIKE_SHORTCUT_INTENT_ACTION_ARRAY[i].equals(action)) return true;
273         }
274         return false;
275     }
276 
277     @Override
onCreate(Bundle savedState)278     protected void onCreate(Bundle savedState) {
279         super.onCreate(savedState);
280         long startTime = System.currentTimeMillis();
281 
282         final FeatureFactory factory = FeatureFactory.getFactory(this);
283 
284         mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this);
285         mSearchFeatureProvider = factory.getSearchFeatureProvider();
286         mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
287 
288         // Should happen before any call to getIntent()
289         getMetaData();
290 
291         final Intent intent = getIntent();
292         if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
293             getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
294         }
295 
296         mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
297                 Context.MODE_PRIVATE);
298 
299         // Getting Intent properties can only be done after the super.onCreate(...)
300         final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
301 
302         mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) ||
303                 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false);
304 
305         final ComponentName cn = intent.getComponent();
306         final String className = cn.getClassName();
307 
308         mIsShowingDashboard = className.equals(Settings.class.getName());
309 
310         // This is a "Sub Settings" when:
311         // - this is a real SubSettings
312         // - or :settings:show_fragment_as_subsetting is passed to the Intent
313         final boolean isSubSettings = this instanceof SubSettings ||
314                 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
315 
316         // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content
317         // insets
318         if (isSubSettings) {
319             setTheme(R.style.Theme_SubSettings);
320         }
321 
322         setContentView(mIsShowingDashboard ?
323                 R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
324 
325         mContent = (ViewGroup) findViewById(R.id.main_content);
326 
327         getFragmentManager().addOnBackStackChangedListener(this);
328 
329         if (savedState != null) {
330             // We are restarting from a previous saved state; used that to initialize, instead
331             // of starting fresh.
332             setTitleFromIntent(intent);
333 
334             ArrayList<DashboardCategory> categories =
335                     savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);
336             if (categories != null) {
337                 mCategories.clear();
338                 mCategories.addAll(categories);
339                 setTitleFromBackStack();
340             }
341 
342             mDisplayHomeAsUpEnabled = savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP);
343 
344         } else {
345             launchSettingFragment(initialFragmentName, isSubSettings, intent);
346         }
347 
348         mActionBar = getActionBar();
349         if (mActionBar != null) {
350             mActionBar.setDisplayHomeAsUpEnabled(mDisplayHomeAsUpEnabled);
351             mActionBar.setHomeButtonEnabled(mDisplayHomeAsUpEnabled);
352         }
353         mSwitchBar = (SwitchBar) findViewById(R.id.switch_bar);
354         if (mSwitchBar != null) {
355             mSwitchBar.setMetricsTag(getMetricsTag());
356         }
357 
358         // see if we should show Back/Next buttons
359         if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
360 
361             View buttonBar = findViewById(R.id.button_bar);
362             if (buttonBar != null) {
363                 buttonBar.setVisibility(View.VISIBLE);
364 
365                 Button backButton = (Button)findViewById(R.id.back_button);
366                 backButton.setOnClickListener(new OnClickListener() {
367                     public void onClick(View v) {
368                         setResult(RESULT_CANCELED, null);
369                         finish();
370                     }
371                 });
372                 Button skipButton = (Button)findViewById(R.id.skip_button);
373                 skipButton.setOnClickListener(new OnClickListener() {
374                     public void onClick(View v) {
375                         setResult(RESULT_OK, null);
376                         finish();
377                     }
378                 });
379                 mNextButton = (Button)findViewById(R.id.next_button);
380                 mNextButton.setOnClickListener(new OnClickListener() {
381                     public void onClick(View v) {
382                         setResult(RESULT_OK, null);
383                         finish();
384                     }
385                 });
386 
387                 // set our various button parameters
388                 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
389                     String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
390                     if (TextUtils.isEmpty(buttonText)) {
391                         mNextButton.setVisibility(View.GONE);
392                     }
393                     else {
394                         mNextButton.setText(buttonText);
395                     }
396                 }
397                 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
398                     String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
399                     if (TextUtils.isEmpty(buttonText)) {
400                         backButton.setVisibility(View.GONE);
401                     }
402                     else {
403                         backButton.setText(buttonText);
404                     }
405                 }
406                 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
407                     skipButton.setVisibility(View.VISIBLE);
408                 }
409             }
410         }
411 
412         if (DEBUG_TIMING) {
413             Log.d(LOG_TAG, "onCreate took " + (System.currentTimeMillis() - startTime) + " ms");
414         }
415     }
416 
417     @VisibleForTesting
launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent)418     void launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent) {
419         if (!mIsShowingDashboard && initialFragmentName != null) {
420             mDisplaySearch = false;
421             // UP will be shown only if it is a sub settings
422             if (mIsShortcut) {
423                 mDisplayHomeAsUpEnabled = isSubSettings;
424             } else if (isSubSettings) {
425                 mDisplayHomeAsUpEnabled = true;
426             } else {
427                 mDisplayHomeAsUpEnabled = false;
428             }
429             setTitleFromIntent(intent);
430 
431             Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
432             switchToFragment(initialFragmentName, initialArguments, true, false,
433                 mInitialTitleResId, mInitialTitle, false);
434         } else {
435             // No UP affordance if we are displaying the main Dashboard
436             mDisplayHomeAsUpEnabled = false;
437             // Show Search affordance
438             mDisplaySearch = true;
439             mInitialTitleResId = R.string.dashboard_title;
440 
441             switchToFragment(DashboardSummary.class.getName(), null /* args */, false, false,
442                 mInitialTitleResId, mInitialTitle, false);
443         }
444     }
445 
setDisplaySearchMenu(boolean displaySearch)446     public void setDisplaySearchMenu(boolean displaySearch) {
447         if (displaySearch != mDisplaySearch) {
448             mDisplaySearch = displaySearch;
449             invalidateOptionsMenu();
450         }
451     }
452 
setTitleFromIntent(Intent intent)453     private void setTitleFromIntent(Intent intent) {
454         final int initialTitleResId = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, -1);
455         if (initialTitleResId > 0) {
456             mInitialTitle = null;
457             mInitialTitleResId = initialTitleResId;
458 
459             final String initialTitleResPackageName = intent.getStringExtra(
460                     EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME);
461             if (initialTitleResPackageName != null) {
462                 try {
463                     Context authContext = createPackageContextAsUser(initialTitleResPackageName,
464                             0 /* flags */, new UserHandle(UserHandle.myUserId()));
465                     mInitialTitle = authContext.getResources().getText(mInitialTitleResId);
466                     setTitle(mInitialTitle);
467                     mInitialTitleResId = -1;
468                     return;
469                 } catch (NameNotFoundException e) {
470                     Log.w(LOG_TAG, "Could not find package" + initialTitleResPackageName);
471                 }
472             } else {
473                 setTitle(mInitialTitleResId);
474             }
475         } else {
476             mInitialTitleResId = -1;
477             final String initialTitle = intent.getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
478             mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
479             setTitle(mInitialTitle);
480         }
481     }
482 
483     @Override
onBackStackChanged()484     public void onBackStackChanged() {
485         setTitleFromBackStack();
486     }
487 
setTitleFromBackStack()488     private void setTitleFromBackStack() {
489         final int count = getFragmentManager().getBackStackEntryCount();
490 
491         if (count == 0) {
492             if (mInitialTitleResId > 0) {
493                 setTitle(mInitialTitleResId);
494             } else {
495                 setTitle(mInitialTitle);
496             }
497             return;
498         }
499 
500         FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1);
501         setTitleFromBackStackEntry(bse);
502     }
503 
setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse)504     private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
505         final CharSequence title;
506         final int titleRes = bse.getBreadCrumbTitleRes();
507         if (titleRes > 0) {
508             title = getText(titleRes);
509         } else {
510             title = bse.getBreadCrumbTitle();
511         }
512         if (title != null) {
513             setTitle(title);
514         }
515     }
516 
517     @Override
onSaveInstanceState(Bundle outState)518     protected void onSaveInstanceState(Bundle outState) {
519         super.onSaveInstanceState(outState);
520         saveState(outState);
521     }
522 
523     /**
524      * For testing purposes to avoid crashes from final variables in Activity's onSaveInstantState.
525      */
526     @VisibleForTesting
saveState(Bundle outState)527     void saveState(Bundle outState) {
528         if (mCategories.size() > 0) {
529             outState.putParcelableArrayList(SAVE_KEY_CATEGORIES, mCategories);
530         }
531 
532         outState.putBoolean(SAVE_KEY_SHOW_HOME_AS_UP, mDisplayHomeAsUpEnabled);
533         outState.putBoolean(SAVE_KEY_SHOW_SEARCH, mDisplaySearch);
534     }
535 
536     @Override
onRestoreInstanceState(Bundle savedInstanceState)537     protected void onRestoreInstanceState(Bundle savedInstanceState) {
538         super.onRestoreInstanceState(savedInstanceState);
539 
540         mDisplayHomeAsUpEnabled = savedInstanceState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP);
541         mDisplaySearch = savedInstanceState.getBoolean(SAVE_KEY_SHOW_SEARCH);
542     }
543 
544     @Override
onResume()545     protected void onResume() {
546         super.onResume();
547 
548         mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
549             @Override
550             public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
551                 updateTilesList();
552             }
553         };
554         mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
555                 mDevelopmentPreferencesListener);
556 
557         registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
558         if (mDynamicIndexableContentMonitor == null) {
559             mDynamicIndexableContentMonitor = new DynamicIndexableContentMonitor();
560         }
561         mDynamicIndexableContentMonitor.register(this, LOADER_ID_INDEXABLE_CONTENT_MONITOR);
562 
563         updateTilesList();
564     }
565 
566     @Override
onPause()567     protected void onPause() {
568         super.onPause();
569         mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
570                 mDevelopmentPreferencesListener);
571         mDevelopmentPreferencesListener = null;
572         unregisterReceiver(mBatteryInfoReceiver);
573         if (mDynamicIndexableContentMonitor != null) {
574             mDynamicIndexableContentMonitor.unregister(this, LOADER_ID_INDEXABLE_CONTENT_MONITOR);
575         }
576     }
577 
578     @Override
setTaskDescription(ActivityManager.TaskDescription taskDescription)579     public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
580         final Bitmap icon = getBitmapFromXmlResource(R.drawable.ic_launcher_settings);
581         taskDescription.setIcon(icon);
582         super.setTaskDescription(taskDescription);
583     }
584 
isValidFragment(String fragmentName)585     protected boolean isValidFragment(String fragmentName) {
586         // Almost all fragments are wrapped in this,
587         // except for a few that have their own activities.
588         for (int i = 0; i < SettingsGateway.ENTRY_FRAGMENTS.length; i++) {
589             if (SettingsGateway.ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
590         }
591         return false;
592     }
593 
594     @Override
getIntent()595     public Intent getIntent() {
596         Intent superIntent = super.getIntent();
597         String startingFragment = getStartingFragmentClass(superIntent);
598         // This is called from super.onCreate, isMultiPane() is not yet reliable
599         // Do not use onIsHidingHeaders either, which relies itself on this method
600         if (startingFragment != null) {
601             Intent modIntent = new Intent(superIntent);
602             modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
603             Bundle args = superIntent.getExtras();
604             if (args != null) {
605                 args = new Bundle(args);
606             } else {
607                 args = new Bundle();
608             }
609             args.putParcelable("intent", superIntent);
610             modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
611             return modIntent;
612         }
613         return superIntent;
614     }
615 
616     /**
617      * Checks if the component name in the intent is different from the Settings class and
618      * returns the class name to load as a fragment.
619      */
getStartingFragmentClass(Intent intent)620     private String getStartingFragmentClass(Intent intent) {
621         if (mFragmentClass != null) return mFragmentClass;
622 
623         String intentClass = intent.getComponent().getClassName();
624         if (intentClass.equals(getClass().getName())) return null;
625 
626         if ("com.android.settings.ManageApplications".equals(intentClass)
627                 || "com.android.settings.RunningServices".equals(intentClass)
628                 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
629             // Old names of manage apps.
630             intentClass = com.android.settings.applications.ManageApplications.class.getName();
631         }
632 
633         return intentClass;
634     }
635 
636     /**
637      * Start a new fragment containing a preference panel.  If the preferences
638      * are being displayed in multi-pane mode, the given fragment class will
639      * be instantiated and placed in the appropriate pane.  If running in
640      * single-pane mode, a new activity will be launched in which to show the
641      * fragment.
642      *
643      * @param fragmentClass Full name of the class implementing the fragment.
644      * @param args Any desired arguments to supply to the fragment.
645      * @param titleRes Optional resource identifier of the title of this
646      * fragment.
647      * @param titleText Optional text of the title of this fragment.
648      * @param resultTo Optional fragment that result data should be sent to.
649      * If non-null, resultTo.onActivityResult() will be called when this
650      * preference panel is done.  The launched panel must use
651      * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
652      * @param resultRequestCode If resultTo is non-null, this is the caller's
653      * request code to be received with the result.
654      */
startPreferencePanel(Fragment caller, String fragmentClass, Bundle args, int titleRes, CharSequence titleText, Fragment resultTo, int resultRequestCode)655     public void startPreferencePanel(Fragment caller, String fragmentClass, Bundle args,
656             int titleRes, CharSequence titleText, Fragment resultTo, int resultRequestCode) {
657         String title = null;
658         if (titleRes < 0) {
659             if (titleText != null) {
660                 title = titleText.toString();
661             } else {
662                 // There not much we can do in that case
663                 title = "";
664             }
665         }
666         Utils.startWithFragment(this, fragmentClass, args, resultTo, resultRequestCode,
667                 titleRes, title, mIsShortcut, mMetricsFeatureProvider.getMetricsCategory(caller));
668     }
669 
670     /**
671      * Start a new fragment in a new activity containing a preference panel for a given user. If the
672      * preferences are being displayed in multi-pane mode, the given fragment class will be
673      * instantiated and placed in the appropriate pane. If running in single-pane mode, a new
674      * activity will be launched in which to show the fragment.
675      *
676      * @param fragmentClass Full name of the class implementing the fragment.
677      * @param args Any desired arguments to supply to the fragment.
678      * @param titleRes Optional resource identifier of the title of this fragment.
679      * @param titleText Optional text of the title of this fragment.
680      * @param userHandle The user for which the panel has to be started.
681      */
startPreferencePanelAsUser(Fragment caller, String fragmentClass, Bundle args, int titleRes, CharSequence titleText, UserHandle userHandle)682     public void startPreferencePanelAsUser(Fragment caller, String fragmentClass,
683             Bundle args, int titleRes, CharSequence titleText, UserHandle userHandle) {
684         // This is a workaround.
685         //
686         // Calling startWithFragmentAsUser() without specifying FLAG_ACTIVITY_NEW_TASK to the intent
687         // starting the fragment could cause a native stack corruption. See b/17523189. However,
688         // adding that flag and start the preference panel with the same UserHandler will make it
689         // impossible to use back button to return to the previous screen. See b/20042570.
690         //
691         // We work around this issue by adding FLAG_ACTIVITY_NEW_TASK to the intent, while doing
692         // another check here to call startPreferencePanel() instead of startWithFragmentAsUser()
693         // when we're calling it as the same user.
694         if (userHandle.getIdentifier() == UserHandle.myUserId()) {
695             startPreferencePanel(caller, fragmentClass, args, titleRes, titleText, null, 0);
696         } else {
697             String title = null;
698             if (titleRes < 0) {
699                 if (titleText != null) {
700                     title = titleText.toString();
701                 } else {
702                     // There not much we can do in that case
703                     title = "";
704                 }
705             }
706             Utils.startWithFragmentAsUser(this, fragmentClass, args, titleRes, title,
707                     mIsShortcut, mMetricsFeatureProvider.getMetricsCategory(caller), userHandle);
708         }
709     }
710 
711     /**
712      * Called by a preference panel fragment to finish itself.
713      *
714      * @param caller The fragment that is asking to be finished.
715      * @param resultCode Optional result code to send back to the original
716      * launching fragment.
717      * @param resultData Optional result data to send back to the original
718      * launching fragment.
719      */
finishPreferencePanel(Fragment caller, int resultCode, Intent resultData)720     public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
721         setResult(resultCode, resultData);
722         finish();
723     }
724 
725     /**
726      * Start a new fragment.
727      *
728      * @param fragment The fragment to start
729      * @param push If true, the current fragment will be pushed onto the back stack.  If false,
730      * the current fragment will be replaced.
731      */
startPreferenceFragment(Fragment fragment, boolean push)732     public void startPreferenceFragment(Fragment fragment, boolean push) {
733         FragmentTransaction transaction = getFragmentManager().beginTransaction();
734         transaction.replace(R.id.main_content, fragment);
735         if (push) {
736             transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
737             transaction.addToBackStack(BACK_STACK_PREFS);
738         } else {
739             transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
740         }
741         transaction.commitAllowingStateLoss();
742     }
743 
744     /**
745      * Switch to a specific Fragment with taking care of validation, Title and BackStack
746      */
switchToFragment(String fragmentName, Bundle args, boolean validate, boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition)747     private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
748             boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition) {
749         if (validate && !isValidFragment(fragmentName)) {
750             throw new IllegalArgumentException("Invalid fragment for this activity: "
751                     + fragmentName);
752         }
753         Fragment f = Fragment.instantiate(this, fragmentName, args);
754         FragmentTransaction transaction = getFragmentManager().beginTransaction();
755         transaction.replace(R.id.main_content, f);
756         if (withTransition) {
757             TransitionManager.beginDelayedTransition(mContent);
758         }
759         if (addToBackStack) {
760             transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
761         }
762         if (titleResId > 0) {
763             transaction.setBreadCrumbTitle(titleResId);
764         } else if (title != null) {
765             transaction.setBreadCrumbTitle(title);
766         }
767         transaction.commitAllowingStateLoss();
768         getFragmentManager().executePendingTransactions();
769         return f;
770     }
771 
updateTilesList()772     private void updateTilesList() {
773         // Generally the items that are will be changing from these updates will
774         // not be in the top list of tiles, so run it in the background and the
775         // SettingsDrawerActivity will pick up on the updates automatically.
776         AsyncTask.execute(new Runnable() {
777             @Override
778             public void run() {
779                 doUpdateTilesList();
780             }
781         });
782     }
783 
doUpdateTilesList()784     private void doUpdateTilesList() {
785         PackageManager pm = getPackageManager();
786         final UserManager um = UserManager.get(this);
787         final boolean isAdmin = um.isAdminUser();
788 
789         String packageName = getPackageName();
790         setTileEnabled(new ComponentName(packageName, WifiSettingsActivity.class.getName()),
791                 pm.hasSystemFeature(PackageManager.FEATURE_WIFI), isAdmin);
792 
793         setTileEnabled(new ComponentName(packageName,
794                         Settings.BluetoothSettingsActivity.class.getName()),
795                 pm.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH), isAdmin);
796 
797         setTileEnabled(new ComponentName(packageName,
798                         Settings.DataUsageSummaryActivity.class.getName()),
799                 Utils.isBandwidthControlEnabled(), isAdmin);
800 
801         setTileEnabled(new ComponentName(packageName,
802                         Settings.SimSettingsActivity.class.getName()),
803                 Utils.showSimCardTile(this), isAdmin);
804 
805         setTileEnabled(new ComponentName(packageName,
806                         Settings.PowerUsageSummaryActivity.class.getName()),
807                 mBatteryPresent, isAdmin);
808 
809         setTileEnabled(new ComponentName(packageName,
810                         Settings.UserSettingsActivity.class.getName()),
811                 UserHandle.MU_ENABLED && UserManager.supportsMultipleUsers()
812                         && !Utils.isMonkeyRunning(), isAdmin);
813 
814         setTileEnabled(new ComponentName(packageName,
815                         Settings.NetworkDashboardActivity.class.getName()),
816                 !UserManager.isDeviceInDemoMode(this), isAdmin);
817 
818         setTileEnabled(new ComponentName(packageName,
819                         Settings.ConnectedDeviceDashboardActivity.class.getName()),
820                 !UserManager.isDeviceInDemoMode(this), isAdmin);
821 
822         setTileEnabled(new ComponentName(packageName,
823                         Settings.DateTimeSettingsActivity.class.getName()),
824                 !UserManager.isDeviceInDemoMode(this), isAdmin);
825         NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
826         setTileEnabled(new ComponentName(packageName,
827                         Settings.PaymentSettingsActivity.class.getName()),
828                 pm.hasSystemFeature(PackageManager.FEATURE_NFC)
829                         && pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
830                         && adapter != null && adapter.isEnabled(), isAdmin);
831 
832         setTileEnabled(new ComponentName(packageName,
833                         Settings.PrintSettingsActivity.class.getName()),
834                 pm.hasSystemFeature(PackageManager.FEATURE_PRINTING), isAdmin);
835 
836         final boolean showDev = mDevelopmentPreferences.getBoolean(
837                 DevelopmentSettings.PREF_SHOW, android.os.Build.TYPE.equals("eng"))
838                 && !um.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES);
839         setTileEnabled(new ComponentName(packageName,
840                         Settings.DevelopmentSettingsActivity.class.getName()),
841                 showDev, isAdmin);
842 
843         // Enable/disable backup settings depending on whether the user is admin.
844         setTileEnabled(new ComponentName(packageName,
845                         BackupSettingsActivity.class.getName()), true, isAdmin);
846 
847         setTileEnabled(new ComponentName(packageName,
848                         Settings.WifiDisplaySettingsActivity.class.getName()),
849                 WifiDisplaySettings.isAvailable(this), isAdmin);
850 
851         if (UserHandle.MU_ENABLED && !isAdmin) {
852 
853             // When on restricted users, disable all extra categories (but only the settings ones).
854             final List<DashboardCategory> categories = mDashboardFeatureProvider.getAllCategories();
855             synchronized (categories) {
856                 for (DashboardCategory category : categories) {
857                     final int tileCount = category.getTilesCount();
858                     for (int i = 0; i < tileCount; i++) {
859                         final ComponentName component = category.getTile(i).intent.getComponent();
860 
861                         final String name = component.getClassName();
862                         final boolean isEnabledForRestricted = ArrayUtils.contains(
863                                 SettingsGateway.SETTINGS_FOR_RESTRICTED, name);
864                         if (packageName.equals(component.getPackageName())
865                                 && !isEnabledForRestricted) {
866                             setTileEnabled(component, false, isAdmin);
867                         }
868                     }
869                 }
870             }
871         }
872 
873         // Final step, refresh categories.
874         updateCategories();
875     }
876 
setTileEnabled(ComponentName component, boolean enabled, boolean isAdmin)877     private void setTileEnabled(ComponentName component, boolean enabled, boolean isAdmin) {
878         if (UserHandle.MU_ENABLED && !isAdmin && getPackageName().equals(component.getPackageName())
879                 && !ArrayUtils.contains(SettingsGateway.SETTINGS_FOR_RESTRICTED,
880                 component.getClassName())) {
881             enabled = false;
882         }
883         setTileEnabled(component, enabled);
884     }
885 
getMetaData()886     private void getMetaData() {
887         try {
888             ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
889                     PackageManager.GET_META_DATA);
890             if (ai == null || ai.metaData == null) return;
891             mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
892         } catch (NameNotFoundException nnfe) {
893             // No recovery
894             Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
895         }
896     }
897 
898     // give subclasses access to the Next button
hasNextButton()899     public boolean hasNextButton() {
900         return mNextButton != null;
901     }
902 
getNextButton()903     public Button getNextButton() {
904         return mNextButton;
905     }
906 
907     @Override
shouldUpRecreateTask(Intent targetIntent)908     public boolean shouldUpRecreateTask(Intent targetIntent) {
909         return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
910     }
911 
startSuggestion(Intent intent)912     public void startSuggestion(Intent intent) {
913         if (intent == null || ActivityManager.isUserAMonkey()) {
914             return;
915         }
916         mCurrentSuggestion = intent.getComponent();
917         startActivityForResult(intent, REQUEST_SUGGESTION);
918     }
919 
920     @Override
onActivityResult(int requestCode, int resultCode, Intent data)921     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
922         if (requestCode == REQUEST_SUGGESTION && mCurrentSuggestion != null
923                 && resultCode != RESULT_CANCELED) {
924             getPackageManager().setComponentEnabledSetting(mCurrentSuggestion,
925                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
926         }
927         super.onActivityResult(requestCode, resultCode, data);
928     }
929 
930     @VisibleForTesting
getBitmapFromXmlResource(int drawableRes)931     Bitmap getBitmapFromXmlResource(int drawableRes) {
932         Drawable drawable = getResources().getDrawable(drawableRes, getTheme());
933         Canvas canvas = new Canvas();
934         Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
935                 drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
936         canvas.setBitmap(bitmap);
937         drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
938         drawable.draw(canvas);
939 
940         return bitmap;
941     }
942 }