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