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