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