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