• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.homepage;
18 
19 import static com.android.settings.search.actionbar.SearchMenuController.NEED_SEARCH_ICON_IN_ACTION_BAR;
20 import static com.android.settingslib.search.SearchIndexable.MOBILE;
21 
22 import android.app.ActivityManager;
23 import android.app.settings.SettingsEnums;
24 import android.content.Context;
25 import android.content.res.Configuration;
26 import android.graphics.drawable.Drawable;
27 import android.os.Bundle;
28 import android.provider.SearchIndexableResource;
29 import android.text.TextUtils;
30 import android.util.Log;
31 import android.view.LayoutInflater;
32 import android.view.ViewGroup;
33 
34 import androidx.annotation.NonNull;
35 import androidx.annotation.VisibleForTesting;
36 import androidx.fragment.app.Fragment;
37 import androidx.preference.Preference;
38 import androidx.preference.PreferenceCategory;
39 import androidx.preference.PreferenceFragmentCompat;
40 import androidx.preference.PreferenceGroup;
41 import androidx.preference.PreferenceScreen;
42 import androidx.recyclerview.widget.RecyclerView;
43 import androidx.window.embedding.ActivityEmbeddingController;
44 
45 import com.android.settings.R;
46 import com.android.settings.Utils;
47 import com.android.settings.activityembedding.ActivityEmbeddingRulesController;
48 import com.android.settings.activityembedding.ActivityEmbeddingUtils;
49 import com.android.settings.core.RoundCornerPreferenceAdapter;
50 import com.android.settings.core.SubSettingLauncher;
51 import com.android.settings.dashboard.DashboardFragment;
52 import com.android.settings.flags.Flags;
53 import com.android.settings.overlay.FeatureFactory;
54 import com.android.settings.search.BaseSearchIndexProvider;
55 import com.android.settings.support.SupportPreferenceController;
56 import com.android.settings.widget.HomepagePreference;
57 import com.android.settings.widget.HomepagePreferenceLayoutHelper.HomepagePreferenceLayout;
58 import com.android.settingslib.core.instrumentation.Instrumentable;
59 import com.android.settingslib.drawer.Tile;
60 import com.android.settingslib.search.SearchIndexable;
61 import com.android.settingslib.widget.SettingsThemeHelper;
62 
63 import java.util.List;
64 
65 @SearchIndexable(forTarget = MOBILE)
66 public class TopLevelSettings extends DashboardFragment implements SplitLayoutListener,
67         PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
68 
69     private static final String TAG = "TopLevelSettings";
70     private static final String SAVED_HIGHLIGHT_MIXIN = "highlight_mixin";
71     private static final String PREF_KEY_SUPPORT = "top_level_support";
72 
73     private boolean mIsEmbeddingActivityEnabled;
74     private TopLevelHighlightMixin mHighlightMixin;
75     private int mPaddingHorizontal;
76     private boolean mScrollNeeded = true;
77     private boolean mFirstStarted = true;
78     private ActivityEmbeddingController mActivityEmbeddingController;
79 
TopLevelSettings()80     public TopLevelSettings() {
81         final Bundle args = new Bundle();
82         // Disable the search icon because this page uses a full search view in actionbar.
83         args.putBoolean(NEED_SEARCH_ICON_IN_ACTION_BAR, false);
84         setArguments(args);
85     }
86 
87     /** Dependency injection ctor only for testing. */
88     @VisibleForTesting
TopLevelSettings(TopLevelHighlightMixin highlightMixin)89     public TopLevelSettings(TopLevelHighlightMixin highlightMixin) {
90         this();
91         mHighlightMixin = highlightMixin;
92     }
93 
94     @Override
getPreferenceScreenResId()95     protected int getPreferenceScreenResId() {
96         return getPreferenceLayoutResId(getContext());
97     }
98 
99     @Override
getLogTag()100     protected String getLogTag() {
101         return TAG;
102     }
103 
104     @Override
getMetricsCategory()105     public int getMetricsCategory() {
106         return SettingsEnums.DASHBOARD_SUMMARY;
107     }
108 
109     @Override
onAttach(Context context)110     public void onAttach(Context context) {
111         super.onAttach(context);
112         HighlightableMenu.fromXml(context, getPreferenceScreenResId());
113         use(SupportPreferenceController.class).setActivity(getActivity());
114     }
115 
116     @Override
getHelpResource()117     public int getHelpResource() {
118         // Disable the help icon because this page uses a full search view in actionbar.
119         return 0;
120     }
121 
122     @Override
getCallbackFragment()123     public Fragment getCallbackFragment() {
124         return this;
125     }
126 
127     @Override
onPreferenceTreeClick(Preference preference)128     public boolean onPreferenceTreeClick(Preference preference) {
129         if (isDuplicateClick(preference)) {
130             return true;
131         }
132 
133         // Register SplitPairRule for SubSettings.
134         ActivityEmbeddingRulesController.registerSubSettingsPairRule(getContext(),
135                 true /* clearTop */);
136 
137         setHighlightPreferenceKey(preference.getKey());
138         return super.onPreferenceTreeClick(preference);
139     }
140 
141     @Override
onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref)142     public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
143         new SubSettingLauncher(getActivity())
144                 .setDestination(pref.getFragment())
145                 .setArguments(pref.getExtras())
146                 .setSourceMetricsCategory(caller instanceof Instrumentable
147                         ? ((Instrumentable) caller).getMetricsCategory()
148                         : Instrumentable.METRICS_CATEGORY_UNKNOWN)
149                 .setTitleRes(-1)
150                 .setIsSecondLayerPage(true)
151                 .launch();
152         return true;
153     }
154 
155     @Override
onCreate(Bundle icicle)156     public void onCreate(Bundle icicle) {
157         super.onCreate(icicle);
158         mIsEmbeddingActivityEnabled =
159                 ActivityEmbeddingUtils.isEmbeddingActivityEnabled(getContext());
160         if (!mIsEmbeddingActivityEnabled) {
161             return;
162         }
163 
164         boolean activityEmbedded = isActivityEmbedded();
165         if (icicle != null) {
166             mHighlightMixin = icicle.getParcelable(SAVED_HIGHLIGHT_MIXIN);
167             if (mHighlightMixin != null) {
168                 mScrollNeeded = !mHighlightMixin.isActivityEmbedded() && activityEmbedded;
169                 mHighlightMixin.setActivityEmbedded(activityEmbedded);
170             }
171         }
172         if (mHighlightMixin == null) {
173             mHighlightMixin = new TopLevelHighlightMixin(activityEmbedded);
174         }
175     }
176 
177     /** Wrap ActivityEmbeddingController#isActivityEmbedded for testing. */
178     @VisibleForTesting
isActivityEmbedded()179     public boolean isActivityEmbedded() {
180         if (mActivityEmbeddingController == null) {
181             mActivityEmbeddingController = ActivityEmbeddingController.getInstance(getActivity());
182         }
183         return mActivityEmbeddingController.isActivityEmbedded(getActivity());
184     }
185 
186     @Override
onStart()187     public void onStart() {
188         if (mFirstStarted) {
189             mFirstStarted = false;
190             FeatureFactory.getFeatureFactory().getSearchFeatureProvider().sendPreIndexIntent(
191                     getContext());
192         } else if (mIsEmbeddingActivityEnabled && isOnlyOneActivityInTask()
193                 && !isActivityEmbedded()) {
194             // Set default highlight menu key for 1-pane homepage since it will show the placeholder
195             // page once changing back to 2-pane.
196             Log.i(TAG, "Set default menu key");
197             setHighlightMenuKey(getString(SettingsHomepageActivity.DEFAULT_HIGHLIGHT_MENU_KEY),
198                     /* scrollNeeded= */ false);
199         }
200         super.onStart();
201     }
202 
isOnlyOneActivityInTask()203     private boolean isOnlyOneActivityInTask() {
204         final ActivityManager.RunningTaskInfo taskInfo = getSystemService(ActivityManager.class)
205                 .getRunningTasks(1).get(0);
206         return taskInfo.numActivities == 1;
207     }
208 
209     @Override
onSaveInstanceState(Bundle outState)210     public void onSaveInstanceState(Bundle outState) {
211         super.onSaveInstanceState(outState);
212         if (mHighlightMixin != null) {
213             outState.putParcelable(SAVED_HIGHLIGHT_MIXIN, mHighlightMixin);
214         }
215     }
216 
217     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)218     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
219         super.onCreatePreferences(savedInstanceState, rootKey);
220         if (Flags.homepageRevamp()) {
221             return;
222         }
223         int tintColor = Utils.getHomepageIconColor(getContext());
224         iteratePreferences(preference -> {
225             Drawable icon = preference.getIcon();
226             if (icon != null) {
227                 icon.setTint(tintColor);
228             }
229         });
230     }
231 
232     @Override
onConfigurationChanged(Configuration newConfig)233     public void onConfigurationChanged(Configuration newConfig) {
234         super.onConfigurationChanged(newConfig);
235         highlightPreferenceIfNeeded();
236     }
237 
238     @Override
onSplitLayoutChanged(boolean isRegularLayout)239     public void onSplitLayoutChanged(boolean isRegularLayout) {
240         iteratePreferences(preference -> {
241             if (preference instanceof HomepagePreferenceLayout) {
242                 ((HomepagePreferenceLayout) preference).getHelper().setIconVisible(isRegularLayout);
243             }
244         });
245     }
246 
247     @Override
highlightPreferenceIfNeeded()248     public void highlightPreferenceIfNeeded() {
249         if (mHighlightMixin != null) {
250             mHighlightMixin.highlightPreferenceIfNeeded();
251         }
252     }
253 
254     @Override
onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)255     public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
256             Bundle savedInstanceState) {
257         RecyclerView recyclerView = super.onCreateRecyclerView(inflater, parent,
258                 savedInstanceState);
259         recyclerView.setVerticalScrollBarEnabled(false);
260         recyclerView.setHorizontalScrollBarEnabled(false);
261         recyclerView.setPadding(mPaddingHorizontal, 0, mPaddingHorizontal, 0);
262         return recyclerView;
263     }
264 
265     /** Sets the horizontal padding */
setPaddingHorizontal(int padding)266     public void setPaddingHorizontal(int padding) {
267         mPaddingHorizontal = padding;
268         RecyclerView recyclerView = getListView();
269         if (recyclerView != null) {
270             recyclerView.setPadding(padding, 0, padding, 0);
271         }
272     }
273 
274     /** Updates the preference internal paddings */
updatePreferencePadding(boolean isTwoPane)275     public void updatePreferencePadding(boolean isTwoPane) {
276         iteratePreferences(new PreferenceJob() {
277             private int mIconPaddingStart;
278             private int mTextPaddingStart;
279 
280             @Override
281             public void init() {
282                 mIconPaddingStart = getResources().getDimensionPixelSize(isTwoPane
283                         ? R.dimen.homepage_preference_icon_padding_start_two_pane
284                         : R.dimen.homepage_preference_icon_padding_start);
285                 mTextPaddingStart = getResources().getDimensionPixelSize(isTwoPane
286                         ? R.dimen.homepage_preference_text_padding_start_two_pane
287                         : R.dimen.homepage_preference_text_padding_start);
288             }
289 
290             @Override
291             public void doForEach(Preference preference) {
292                 if (preference instanceof HomepagePreferenceLayout) {
293                     ((HomepagePreferenceLayout) preference).getHelper()
294                             .setIconPaddingStart(mIconPaddingStart);
295                     ((HomepagePreferenceLayout) preference).getHelper()
296                             .setTextPaddingStart(mTextPaddingStart);
297                 }
298             }
299         });
300     }
301 
302     /** Returns a {@link TopLevelHighlightMixin} that performs highlighting */
getHighlightMixin()303     public TopLevelHighlightMixin getHighlightMixin() {
304         return mHighlightMixin;
305     }
306 
307     /** Highlight a preference with specified preference key */
setHighlightPreferenceKey(String prefKey)308     public void setHighlightPreferenceKey(String prefKey) {
309         // Skip Tips & support since it's full screen
310         if (mHighlightMixin != null && !TextUtils.equals(prefKey, PREF_KEY_SUPPORT)) {
311             mHighlightMixin.setHighlightPreferenceKey(prefKey);
312         }
313     }
314 
315     /** Returns whether clicking the specified preference is considered as a duplicate click. */
isDuplicateClick(Preference pref)316     public boolean isDuplicateClick(Preference pref) {
317         /* Return true if
318          * 1. the device supports activity embedding, and
319          * 2. the target preference is highlighted, and
320          * 3. the current activity is embedded */
321         return mHighlightMixin != null
322                 && TextUtils.equals(pref.getKey(), mHighlightMixin.getHighlightPreferenceKey())
323                 && isActivityEmbedded();
324     }
325 
326     /** Show/hide the highlight on the menu entry for the search page presence */
setMenuHighlightShowed(boolean show)327     public void setMenuHighlightShowed(boolean show) {
328         if (mHighlightMixin != null) {
329             mHighlightMixin.setMenuHighlightShowed(show);
330         }
331     }
332 
333     /** Highlight and scroll to a preference with specified menu key */
setHighlightMenuKey(String menuKey, boolean scrollNeeded)334     public void setHighlightMenuKey(String menuKey, boolean scrollNeeded) {
335         if (mHighlightMixin != null) {
336             mHighlightMixin.setHighlightMenuKey(menuKey, scrollNeeded);
337         }
338     }
339 
340     @Override
shouldForceRoundedIcon()341     protected boolean shouldForceRoundedIcon() {
342         return getContext().getResources()
343                 .getBoolean(R.bool.config_force_rounded_icon_TopLevelSettings);
344     }
345 
346     @Override
onCreateAdapter(PreferenceScreen preferenceScreen)347     protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
348         if (mIsEmbeddingActivityEnabled && (getActivity() instanceof SettingsHomepageActivity)) {
349             return mHighlightMixin.onCreateAdapter(this, preferenceScreen, mScrollNeeded);
350         }
351 
352         if (Flags.homepageRevamp()) {
353             return new RoundCornerPreferenceAdapter(preferenceScreen);
354         }
355         return super.onCreateAdapter(preferenceScreen);
356     }
357 
358     @Override
createPreference(Tile tile)359     protected Preference createPreference(Tile tile) {
360         return new HomepagePreference(getPrefContext());
361     }
362 
reloadHighlightMenuKey()363     void reloadHighlightMenuKey() {
364         if (mHighlightMixin != null) {
365             mHighlightMixin.reloadHighlightMenuKey(getArguments());
366         }
367     }
368 
iteratePreferences(PreferenceJob job)369     private void iteratePreferences(PreferenceJob job) {
370         if (job == null || getPreferenceManager() == null) {
371             return;
372         }
373         PreferenceScreen screen = getPreferenceScreen();
374         if (screen == null) {
375             return;
376         }
377 
378         job.init();
379         iteratePreferences(screen, job);
380     }
381 
iteratePreferences(PreferenceGroup group, PreferenceJob job)382     private void iteratePreferences(PreferenceGroup group, PreferenceJob job) {
383         int count = group.getPreferenceCount();
384         for (int i = 0; i < count; i++) {
385             Preference preference = group.getPreference(i);
386             job.doForEach(preference);
387             if (preference instanceof PreferenceCategory) {
388                 iteratePreferences((PreferenceCategory) preference, job);
389             }
390         }
391     }
392 
393     private interface PreferenceJob {
init()394         default void init() {
395         }
396 
doForEach(Preference preference)397         void doForEach(Preference preference);
398     }
399 
getPreferenceLayoutResId(Context context)400     private static int getPreferenceLayoutResId(Context context) {
401         return Flags.homepageRevamp()
402                 ? SettingsThemeHelper.isExpressiveTheme(context)
403                         ? R.xml.top_level_settings_expressive
404                         : R.xml.top_level_settings_v2
405                 : R.xml.top_level_settings;
406     }
407 
408     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
409             new BaseSearchIndexProvider() {
410 
411                 @Override
412                 @NonNull
413                 public List<SearchIndexableResource> getXmlResourcesToIndex(
414                         @NonNull Context context, boolean enabled) {
415                     SearchIndexableResource sir = new SearchIndexableResource(context);
416                     sir.xmlResId = getPreferenceLayoutResId(context);
417                     return List.of(sir);
418                 }
419 
420                 @Override
421                 protected boolean isPageSearchEnabled(Context context) {
422                     // Never searchable, all entries in this page are already indexed elsewhere.
423                     return false;
424                 }
425             };
426 }
427