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 static com.android.settings.applications.appinfo.AppButtonsPreferenceController.KEY_REMOVE_TASK_WHEN_FINISHING;
20 
21 import android.app.ActionBar;
22 import android.app.ActivityManager;
23 import android.content.BroadcastReceiver;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.SharedPreferences;
29 import android.content.pm.ActivityInfo;
30 import android.content.pm.PackageManager;
31 import android.content.pm.PackageManager.NameNotFoundException;
32 import android.content.res.Resources.Theme;
33 import android.graphics.drawable.Icon;
34 import android.os.AsyncTask;
35 import android.os.Bundle;
36 import android.os.UserHandle;
37 import android.os.UserManager;
38 import android.text.TextUtils;
39 import android.util.Log;
40 import android.view.View;
41 import android.widget.Button;
42 
43 import androidx.annotation.Nullable;
44 import androidx.annotation.VisibleForTesting;
45 import androidx.fragment.app.Fragment;
46 import androidx.fragment.app.FragmentManager;
47 import androidx.fragment.app.FragmentTransaction;
48 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
49 import androidx.preference.Preference;
50 import androidx.preference.PreferenceFragmentCompat;
51 import androidx.preference.PreferenceManager;
52 
53 import com.android.internal.util.ArrayUtils;
54 import com.android.settings.Settings.WifiSettingsActivity;
55 import com.android.settings.applications.manageapplications.ManageApplications;
56 import com.android.settings.core.OnActivityResultListener;
57 import com.android.settings.core.SettingsBaseActivity;
58 import com.android.settings.core.SubSettingLauncher;
59 import com.android.settings.core.gateway.SettingsGateway;
60 import com.android.settings.dashboard.DashboardFeatureProvider;
61 import com.android.settings.homepage.TopLevelSettings;
62 import com.android.settings.overlay.FeatureFactory;
63 import com.android.settings.wfd.WifiDisplaySettings;
64 import com.android.settings.widget.SwitchBar;
65 import com.android.settingslib.core.instrumentation.Instrumentable;
66 import com.android.settingslib.core.instrumentation.SharedPreferencesLogger;
67 import com.android.settingslib.development.DevelopmentSettingsEnabler;
68 import com.android.settingslib.drawer.DashboardCategory;
69 
70 import com.google.android.setupcompat.util.WizardManagerHelper;
71 
72 import java.util.ArrayList;
73 import java.util.List;
74 
75 
76 public class SettingsActivity extends SettingsBaseActivity
77         implements PreferenceManager.OnPreferenceTreeClickListener,
78         PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
79         ButtonBarHandler, FragmentManager.OnBackStackChangedListener {
80 
81     private static final String LOG_TAG = "SettingsActivity";
82 
83     // Constants for state save/restore
84     private static final String SAVE_KEY_CATEGORIES = ":settings:categories";
85 
86     /**
87      * When starting this activity, the invoking Intent can contain this extra
88      * string to specify which fragment should be initially displayed.
89      * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
90      * will call isValidFragment() to confirm that the fragment class name is valid for this
91      * activity.
92      */
93     public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
94 
95     /**
96      * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
97      * this extra can also be specified to supply a Bundle of arguments to pass
98      * to that fragment when it is instantiated during the initial creation
99      * of the activity.
100      */
101     public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
102 
103     /**
104      * Fragment "key" argument passed thru {@link #EXTRA_SHOW_FRAGMENT_ARGUMENTS}
105      */
106     public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
107 
108     // extras that allow any preference activity to be launched as part of a wizard
109 
110     // show Back and Next buttons? takes boolean parameter
111     // Back will then return RESULT_CANCELED and Next RESULT_OK
112     protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
113 
114     // add a Skip button?
115     private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
116 
117     // specify custom text for the Back or Next buttons, or cause a button to not appear
118     // at all by setting it to null
119     protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
120     protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
121 
122     /**
123      * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
124      * those extra can also be specify to supply the title or title res id to be shown for
125      * that fragment.
126      */
127     public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
128     /**
129      * The package name used to resolve the title resource id.
130      */
131     public static final String EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME =
132             ":settings:show_fragment_title_res_package_name";
133     public static final String EXTRA_SHOW_FRAGMENT_TITLE_RESID =
134             ":settings:show_fragment_title_resid";
135 
136     public static final String EXTRA_SHOW_FRAGMENT_AS_SUBSETTING =
137             ":settings:show_fragment_as_subsetting";
138 
139     /**
140      * Personal or Work profile tab of {@link ProfileSelectFragment}
141      * <p>0: Personal tab.
142      * <p>1: Work profile tab.
143      */
144     public static final String EXTRA_SHOW_FRAGMENT_TAB =
145             ":settings:show_fragment_tab";
146 
147     public static final String META_DATA_KEY_FRAGMENT_CLASS =
148             "com.android.settings.FRAGMENT_CLASS";
149 
150     private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
151 
152     private String mFragmentClass;
153 
154     private CharSequence mInitialTitle;
155     private int mInitialTitleResId;
156 
157     private BroadcastReceiver mDevelopmentSettingsListener;
158 
159     private boolean mBatteryPresent = true;
160     private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
161         @Override
162         public void onReceive(Context context, Intent intent) {
163             String action = intent.getAction();
164             if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
165                 boolean batteryPresent = Utils.isBatteryPresent(intent);
166 
167                 if (mBatteryPresent != batteryPresent) {
168                     mBatteryPresent = batteryPresent;
169                     updateTilesList();
170                 }
171             }
172         }
173     };
174 
175     private SwitchBar mSwitchBar;
176 
177     private Button mNextButton;
178 
179     // Categories
180     private ArrayList<DashboardCategory> mCategories = new ArrayList<>();
181 
182     private DashboardFeatureProvider mDashboardFeatureProvider;
183 
getSwitchBar()184     public SwitchBar getSwitchBar() {
185         return mSwitchBar;
186     }
187 
188     @Override
onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref)189     public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
190         new SubSettingLauncher(this)
191                 .setDestination(pref.getFragment())
192                 .setArguments(pref.getExtras())
193                 .setSourceMetricsCategory(caller instanceof Instrumentable
194                         ? ((Instrumentable) caller).getMetricsCategory()
195                         : Instrumentable.METRICS_CATEGORY_UNKNOWN)
196                 .setTitleRes(-1)
197                 .launch();
198         return true;
199     }
200 
201     @Override
onPreferenceTreeClick(Preference preference)202     public boolean onPreferenceTreeClick(Preference preference) {
203         return false;
204     }
205 
206     @Override
getSharedPreferences(String name, int mode)207     public SharedPreferences getSharedPreferences(String name, int mode) {
208         if (name.equals(getPackageName() + "_preferences")) {
209             return new SharedPreferencesLogger(this, getMetricsTag(),
210                     FeatureFactory.getFactory(this).getMetricsFeatureProvider());
211         }
212         return super.getSharedPreferences(name, mode);
213     }
214 
getMetricsTag()215     private String getMetricsTag() {
216         String tag = null;
217         if (getIntent() != null && getIntent().hasExtra(EXTRA_SHOW_FRAGMENT)) {
218             tag = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
219         }
220         if (TextUtils.isEmpty(tag)) {
221             Log.w(LOG_TAG, "MetricsTag is invalid " + tag);
222             tag = getClass().getName();
223         }
224         if (tag.startsWith("com.android.settings.")) {
225             tag = tag.replace("com.android.settings.", "");
226         }
227         return tag;
228     }
229 
230     @Override
onCreate(Bundle savedState)231     protected void onCreate(Bundle savedState) {
232         super.onCreate(savedState);
233         Log.d(LOG_TAG, "Starting onCreate");
234         long startTime = System.currentTimeMillis();
235 
236         final FeatureFactory factory = FeatureFactory.getFactory(this);
237 
238         mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this);
239 
240         // Should happen before any call to getIntent()
241         getMetaData();
242 
243         final Intent intent = getIntent();
244         if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
245             getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
246         }
247 
248         // Getting Intent properties can only be done after the super.onCreate(...)
249         final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
250 
251         // This is a "Sub Settings" when:
252         // - this is a real SubSettings
253         // - or :settings:show_fragment_as_subsetting is passed to the Intent
254         final boolean isSubSettings = this instanceof SubSettings ||
255                 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
256 
257         // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content
258         // insets.
259         // If this is in setup flow, don't apply theme. Because light theme needs to be applied
260         // in SettingsBaseActivity#onCreate().
261         if (isSubSettings && !WizardManagerHelper.isAnySetupWizard(getIntent())) {
262             setTheme(R.style.Theme_SubSettings);
263         }
264 
265         setContentView(R.layout.settings_main_prefs);
266 
267         getSupportFragmentManager().addOnBackStackChangedListener(this);
268 
269         if (savedState != null) {
270             // We are restarting from a previous saved state; used that to initialize, instead
271             // of starting fresh.
272             setTitleFromIntent(intent);
273 
274             ArrayList<DashboardCategory> categories =
275                     savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);
276             if (categories != null) {
277                 mCategories.clear();
278                 mCategories.addAll(categories);
279                 setTitleFromBackStack();
280             }
281         } else {
282             launchSettingFragment(initialFragmentName, intent);
283         }
284 
285         final boolean isInSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
286 
287         final ActionBar actionBar = getActionBar();
288         if (actionBar != null) {
289             actionBar.setDisplayHomeAsUpEnabled(!isInSetupWizard);
290             actionBar.setHomeButtonEnabled(!isInSetupWizard);
291             actionBar.setDisplayShowTitleEnabled(true);
292         }
293         mSwitchBar = findViewById(R.id.switch_bar);
294         if (mSwitchBar != null) {
295             mSwitchBar.setMetricsTag(getMetricsTag());
296         }
297 
298         // see if we should show Back/Next buttons
299         if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
300 
301             View buttonBar = findViewById(R.id.button_bar);
302             if (buttonBar != null) {
303                 buttonBar.setVisibility(View.VISIBLE);
304 
305                 Button backButton = findViewById(R.id.back_button);
306                 backButton.setOnClickListener(v -> {
307                     setResult(RESULT_CANCELED, null);
308                     finish();
309                 });
310                 Button skipButton = findViewById(R.id.skip_button);
311                 skipButton.setOnClickListener(v -> {
312                     setResult(RESULT_OK, null);
313                     finish();
314                 });
315                 mNextButton = findViewById(R.id.next_button);
316                 mNextButton.setOnClickListener(v -> {
317                     setResult(RESULT_OK, null);
318                     finish();
319                 });
320 
321                 // set our various button parameters
322                 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
323                     String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
324                     if (TextUtils.isEmpty(buttonText)) {
325                         mNextButton.setVisibility(View.GONE);
326                     } else {
327                         mNextButton.setText(buttonText);
328                     }
329                 }
330                 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
331                     String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
332                     if (TextUtils.isEmpty(buttonText)) {
333                         backButton.setVisibility(View.GONE);
334                     } else {
335                         backButton.setText(buttonText);
336                     }
337                 }
338                 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
339                     skipButton.setVisibility(View.VISIBLE);
340                 }
341             }
342         }
343 
344         if (DEBUG_TIMING) {
345             Log.d(LOG_TAG, "onCreate took " + (System.currentTimeMillis() - startTime) + " ms");
346         }
347     }
348 
349     @Override
onApplyThemeResource(Theme theme, int resid, boolean first)350     protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
351         theme.applyStyle(R.style.SetupWizardPartnerResource, true);
352         super.onApplyThemeResource(theme, resid, first);
353     }
354 
355     @Override
onActivityResult(int requestCode, int resultCode, @Nullable Intent data)356     protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
357         super.onActivityResult(requestCode, resultCode, data);
358         final List<Fragment> fragments = getSupportFragmentManager().getFragments();
359         if (fragments != null) {
360             for (Fragment fragment : fragments) {
361                 if (fragment instanceof OnActivityResultListener) {
362                     fragment.onActivityResult(requestCode, resultCode, data);
363                 }
364             }
365         }
366     }
367 
368     @VisibleForTesting
launchSettingFragment(String initialFragmentName, Intent intent)369     void launchSettingFragment(String initialFragmentName, Intent intent) {
370         if (initialFragmentName != null) {
371             setTitleFromIntent(intent);
372 
373             Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
374             switchToFragment(initialFragmentName, initialArguments, true,
375                     mInitialTitleResId, mInitialTitle);
376         } else {
377             // Show search icon as up affordance if we are displaying the main Dashboard
378             mInitialTitleResId = R.string.dashboard_title;
379             switchToFragment(TopLevelSettings.class.getName(), null /* args */, false,
380                     mInitialTitleResId, mInitialTitle);
381         }
382     }
383 
setTitleFromIntent(Intent intent)384     private void setTitleFromIntent(Intent intent) {
385         Log.d(LOG_TAG, "Starting to set activity title");
386         final int initialTitleResId = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, -1);
387         if (initialTitleResId > 0) {
388             mInitialTitle = null;
389             mInitialTitleResId = initialTitleResId;
390 
391             final String initialTitleResPackageName = intent.getStringExtra(
392                     EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME);
393             if (initialTitleResPackageName != null) {
394                 try {
395                     Context authContext = createPackageContextAsUser(initialTitleResPackageName,
396                             0 /* flags */, new UserHandle(UserHandle.myUserId()));
397                     mInitialTitle = authContext.getResources().getText(mInitialTitleResId);
398                     setTitle(mInitialTitle);
399                     mInitialTitleResId = -1;
400                     return;
401                 } catch (NameNotFoundException e) {
402                     Log.w(LOG_TAG, "Could not find package" + initialTitleResPackageName);
403                 }
404             } else {
405                 setTitle(mInitialTitleResId);
406             }
407         } else {
408             mInitialTitleResId = -1;
409             final String initialTitle = intent.getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
410             mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
411             setTitle(mInitialTitle);
412         }
413         Log.d(LOG_TAG, "Done setting title");
414     }
415 
416     @Override
onBackStackChanged()417     public void onBackStackChanged() {
418         setTitleFromBackStack();
419     }
420 
setTitleFromBackStack()421     private void setTitleFromBackStack() {
422         final int count = getSupportFragmentManager().getBackStackEntryCount();
423 
424         if (count == 0) {
425             if (mInitialTitleResId > 0) {
426                 setTitle(mInitialTitleResId);
427             } else {
428                 setTitle(mInitialTitle);
429             }
430             return;
431         }
432 
433         FragmentManager.BackStackEntry bse = getSupportFragmentManager().
434                 getBackStackEntryAt(count - 1);
435         setTitleFromBackStackEntry(bse);
436     }
437 
setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse)438     private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
439         final CharSequence title;
440         final int titleRes = bse.getBreadCrumbTitleRes();
441         if (titleRes > 0) {
442             title = getText(titleRes);
443         } else {
444             title = bse.getBreadCrumbTitle();
445         }
446         if (title != null) {
447             setTitle(title);
448         }
449     }
450 
451     @Override
onSaveInstanceState(Bundle outState)452     protected void onSaveInstanceState(Bundle outState) {
453         super.onSaveInstanceState(outState);
454         saveState(outState);
455     }
456 
457     /**
458      * For testing purposes to avoid crashes from final variables in Activity's onSaveInstantState.
459      */
460     @VisibleForTesting
saveState(Bundle outState)461     void saveState(Bundle outState) {
462         if (mCategories.size() > 0) {
463             outState.putParcelableArrayList(SAVE_KEY_CATEGORIES, mCategories);
464         }
465     }
466 
467     @Override
onResume()468     protected void onResume() {
469         super.onResume();
470 
471         mDevelopmentSettingsListener = new BroadcastReceiver() {
472             @Override
473             public void onReceive(Context context, Intent intent) {
474                 updateTilesList();
475             }
476         };
477         LocalBroadcastManager.getInstance(this).registerReceiver(mDevelopmentSettingsListener,
478                 new IntentFilter(DevelopmentSettingsEnabler.DEVELOPMENT_SETTINGS_CHANGED_ACTION));
479 
480         registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
481 
482         updateTilesList();
483     }
484 
485     @Override
onPause()486     protected void onPause() {
487         super.onPause();
488         LocalBroadcastManager.getInstance(this).unregisterReceiver(mDevelopmentSettingsListener);
489         mDevelopmentSettingsListener = null;
490         unregisterReceiver(mBatteryInfoReceiver);
491     }
492 
493     @Override
setTaskDescription(ActivityManager.TaskDescription taskDescription)494     public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
495         taskDescription.setIcon(Icon.createWithResource(this, R.drawable.ic_launcher_settings));
496         super.setTaskDescription(taskDescription);
497     }
498 
isValidFragment(String fragmentName)499     protected boolean isValidFragment(String fragmentName) {
500         // Almost all fragments are wrapped in this,
501         // except for a few that have their own activities.
502         for (int i = 0; i < SettingsGateway.ENTRY_FRAGMENTS.length; i++) {
503             if (SettingsGateway.ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
504         }
505         return false;
506     }
507 
508     @Override
getIntent()509     public Intent getIntent() {
510         Intent superIntent = super.getIntent();
511         String startingFragment = getStartingFragmentClass(superIntent);
512         // This is called from super.onCreate, isMultiPane() is not yet reliable
513         // Do not use onIsHidingHeaders either, which relies itself on this method
514         if (startingFragment != null) {
515             Intent modIntent = new Intent(superIntent);
516             modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
517             Bundle args = superIntent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
518             if (args != null) {
519                 args = new Bundle(args);
520             } else {
521                 args = new Bundle();
522             }
523             args.putParcelable("intent", superIntent);
524             modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
525             return modIntent;
526         }
527         return superIntent;
528     }
529 
530     /**
531      * Checks if the component name in the intent is different from the Settings class and
532      * returns the class name to load as a fragment.
533      */
getStartingFragmentClass(Intent intent)534     private String getStartingFragmentClass(Intent intent) {
535         if (mFragmentClass != null) return mFragmentClass;
536 
537         String intentClass = intent.getComponent().getClassName();
538         if (intentClass.equals(getClass().getName())) return null;
539 
540         if ("com.android.settings.RunningServices".equals(intentClass)
541                 || "com.android.settings.applications.StorageUse".equals(intentClass)) {
542             // Old names of manage apps.
543             intentClass = ManageApplications.class.getName();
544         }
545 
546         return intentClass;
547     }
548 
549     /**
550      * Called by a preference panel fragment to finish itself.
551      *
552      * @param resultCode Optional result code to send back to the original
553      *                   launching fragment.
554      * @param resultData Optional result data to send back to the original
555      *                   launching fragment.
556      */
finishPreferencePanel(int resultCode, Intent resultData)557     public void finishPreferencePanel(int resultCode, Intent resultData) {
558         setResult(resultCode, resultData);
559         if (resultData != null &&
560                 resultData.getBooleanExtra(KEY_REMOVE_TASK_WHEN_FINISHING, false)) {
561             finishAndRemoveTask();
562         } else {
563             finish();
564         }
565     }
566 
567     /**
568      * Switch to a specific Fragment with taking care of validation, Title and BackStack
569      */
switchToFragment(String fragmentName, Bundle args, boolean validate, int titleResId, CharSequence title)570     private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
571             int titleResId, CharSequence title) {
572         Log.d(LOG_TAG, "Switching to fragment " + fragmentName);
573         if (validate && !isValidFragment(fragmentName)) {
574             throw new IllegalArgumentException("Invalid fragment for this activity: "
575                     + fragmentName);
576         }
577         Fragment f = Utils.getTargetFragment(this, fragmentName, args);
578         FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
579         transaction.replace(R.id.main_content, f);
580         if (titleResId > 0) {
581             transaction.setBreadCrumbTitle(titleResId);
582         } else if (title != null) {
583             transaction.setBreadCrumbTitle(title);
584         }
585         transaction.commitAllowingStateLoss();
586         getSupportFragmentManager().executePendingTransactions();
587         Log.d(LOG_TAG, "Executed frag manager pendingTransactions");
588         return f;
589     }
590 
updateTilesList()591     private void updateTilesList() {
592         // Generally the items that are will be changing from these updates will
593         // not be in the top list of tiles, so run it in the background and the
594         // SettingsBaseActivity will pick up on the updates automatically.
595         AsyncTask.execute(() -> doUpdateTilesList());
596     }
597 
doUpdateTilesList()598     private void doUpdateTilesList() {
599         PackageManager pm = getPackageManager();
600         final UserManager um = UserManager.get(this);
601         final boolean isAdmin = um.isAdminUser();
602         boolean somethingChanged = false;
603         final String packageName = getPackageName();
604         final StringBuilder changedList = new StringBuilder();
605         somethingChanged = setTileEnabled(changedList,
606                 new ComponentName(packageName, WifiSettingsActivity.class.getName()),
607                 pm.hasSystemFeature(PackageManager.FEATURE_WIFI), isAdmin) || somethingChanged;
608 
609         somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
610                         Settings.BluetoothSettingsActivity.class.getName()),
611                 pm.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH), isAdmin)
612                 || somethingChanged;
613 
614         // Enable DataUsageSummaryActivity if the data plan feature flag is turned on otherwise
615         // enable DataPlanUsageSummaryActivity.
616         somethingChanged = setTileEnabled(changedList,
617                 new ComponentName(packageName, Settings.DataUsageSummaryActivity.class.getName()),
618                 Utils.isBandwidthControlEnabled() /* enabled */,
619                 isAdmin) || somethingChanged;
620 
621         somethingChanged = setTileEnabled(changedList,
622                 new ComponentName(packageName,
623                         Settings.ConnectedDeviceDashboardActivity.class.getName()),
624                 !UserManager.isDeviceInDemoMode(this) /* enabled */,
625                 isAdmin) || somethingChanged;
626 
627         somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
628                         Settings.PowerUsageSummaryActivity.class.getName()),
629                 mBatteryPresent, isAdmin) || somethingChanged;
630 
631         somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
632                         Settings.DataUsageSummaryActivity.class.getName()),
633                 Utils.isBandwidthControlEnabled(), isAdmin)
634                 || somethingChanged;
635 
636         somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
637                         Settings.UserSettingsActivity.class.getName()),
638                 UserHandle.MU_ENABLED && UserManager.supportsMultipleUsers()
639                         && !Utils.isMonkeyRunning(), isAdmin)
640                 || somethingChanged;
641 
642         final boolean showDev = DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(this)
643                 && !Utils.isMonkeyRunning();
644         somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
645                         Settings.DevelopmentSettingsDashboardActivity.class.getName()),
646                 showDev, isAdmin)
647                 || somethingChanged;
648 
649         somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
650                         Settings.WifiDisplaySettingsActivity.class.getName()),
651                 WifiDisplaySettings.isAvailable(this), isAdmin)
652                 || somethingChanged;
653 
654         if (UserHandle.MU_ENABLED && !isAdmin) {
655             // When on restricted users, disable all extra categories (but only the settings ones).
656             final List<DashboardCategory> categories = mDashboardFeatureProvider.getAllCategories();
657             synchronized (categories) {
658                 for (DashboardCategory category : categories) {
659                     final int tileCount = category.getTilesCount();
660                     for (int i = 0; i < tileCount; i++) {
661                         final ComponentName component = category.getTile(i)
662                                 .getIntent().getComponent();
663                         final String name = component.getClassName();
664                         final boolean isEnabledForRestricted = ArrayUtils.contains(
665                                 SettingsGateway.SETTINGS_FOR_RESTRICTED, name);
666                         if (packageName.equals(component.getPackageName())
667                                 && !isEnabledForRestricted) {
668                             somethingChanged =
669                                     setTileEnabled(changedList, component, false, isAdmin)
670                                             || somethingChanged;
671                         }
672                     }
673                 }
674             }
675         }
676 
677         // Final step, refresh categories.
678         if (somethingChanged) {
679             Log.d(LOG_TAG, "Enabled state changed for some tiles, reloading all categories "
680                     + changedList.toString());
681             updateCategories();
682         } else {
683             Log.d(LOG_TAG, "No enabled state changed, skipping updateCategory call");
684         }
685     }
686 
687     /**
688      * @return whether or not the enabled state actually changed.
689      */
setTileEnabled(StringBuilder changedList, ComponentName component, boolean enabled, boolean isAdmin)690     private boolean setTileEnabled(StringBuilder changedList, ComponentName component,
691             boolean enabled, boolean isAdmin) {
692         if (UserHandle.MU_ENABLED && !isAdmin && getPackageName().equals(component.getPackageName())
693                 && !ArrayUtils.contains(SettingsGateway.SETTINGS_FOR_RESTRICTED,
694                 component.getClassName())) {
695             enabled = false;
696         }
697         boolean changed = setTileEnabled(component, enabled);
698         if (changed) {
699             changedList.append(component.toShortString()).append(",");
700         }
701         return changed;
702     }
703 
getMetaData()704     private void getMetaData() {
705         try {
706             ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
707                     PackageManager.GET_META_DATA);
708             if (ai == null || ai.metaData == null) return;
709             mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
710         } catch (NameNotFoundException nnfe) {
711             // No recovery
712             Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
713         }
714     }
715 
716     // give subclasses access to the Next button
hasNextButton()717     public boolean hasNextButton() {
718         return mNextButton != null;
719     }
720 
getNextButton()721     public Button getNextButton() {
722         return mNextButton;
723     }
724 }
725