1 /* 2 * Copyright (C) 2021 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.widget; 18 19 import android.content.Context; 20 import android.graphics.Rect; 21 import android.graphics.drawable.Drawable; 22 import android.text.TextUtils; 23 import android.util.Log; 24 import android.util.SparseArray; 25 import android.view.View; 26 import android.widget.ImageView; 27 import android.widget.TextView; 28 29 import androidx.annotation.DrawableRes; 30 import androidx.annotation.VisibleForTesting; 31 import androidx.preference.PreferenceGroup; 32 import androidx.preference.PreferenceViewHolder; 33 import androidx.recyclerview.widget.RecyclerView; 34 import androidx.window.embedding.ActivityEmbeddingController; 35 36 import com.android.settings.R; 37 import com.android.settings.Utils; 38 import com.android.settings.core.RoundCornerPreferenceAdapter; 39 import com.android.settings.flags.Flags; 40 import com.android.settings.homepage.SettingsHomepageActivity; 41 import com.android.settingslib.widget.SettingsThemeHelper; 42 43 /** 44 * Adapter for highlighting top level preferences 45 */ 46 public class HighlightableTopLevelPreferenceAdapter extends RoundCornerPreferenceAdapter implements 47 SettingsHomepageActivity.HomepageLoadedListener { 48 49 private static final String TAG = "HighlightableTopLevelAdapter"; 50 51 static final long DELAY_HIGHLIGHT_DURATION_MILLIS = 100L; 52 private static final int RES_NORMAL_BACKGROUND = 53 R.drawable.homepage_selectable_item_background; 54 private static final int RES_HIGHLIGHTED_BACKGROUND = 55 R.drawable.homepage_highlighted_item_background; 56 57 private final int mTitleColorNormal; 58 private final int mTitleColorHighlight; 59 private final int mSummaryColorNormal; 60 private final int mSummaryColorHighlight; 61 private final int mIconColorNormal; 62 private final int mIconColorHighlight; 63 64 private final SettingsHomepageActivity mHomepageActivity; 65 private final RecyclerView mRecyclerView; 66 private String mHighlightKey; 67 private int mHighlightPosition = RecyclerView.NO_POSITION; 68 private int mScrollPosition = RecyclerView.NO_POSITION; 69 private boolean mHighlightNeeded; 70 private boolean mScrolled; 71 private SparseArray<PreferenceViewHolder> mViewHolders; 72 HighlightableTopLevelPreferenceAdapter(SettingsHomepageActivity homepageActivity, PreferenceGroup preferenceGroup, RecyclerView recyclerView, String key, boolean scrollNeeded)73 public HighlightableTopLevelPreferenceAdapter(SettingsHomepageActivity homepageActivity, 74 PreferenceGroup preferenceGroup, RecyclerView recyclerView, String key, 75 boolean scrollNeeded) { 76 super(preferenceGroup); 77 mRecyclerView = recyclerView; 78 mHighlightKey = key; 79 mScrolled = !scrollNeeded; 80 mViewHolders = new SparseArray<>(); 81 mHomepageActivity = homepageActivity; 82 Context context = preferenceGroup.getContext(); 83 mTitleColorNormal = Utils.getColorAttrDefaultColor(context, 84 android.R.attr.textColorPrimary); 85 mTitleColorHighlight = context.getColor(R.color.accent_select_primary_text); 86 mSummaryColorNormal = Utils.getColorAttrDefaultColor(context, 87 android.R.attr.textColorSecondary); 88 mSummaryColorHighlight = context.getColor(R.color.accent_select_secondary_text); 89 mIconColorNormal = Utils.getHomepageIconColor(context); 90 mIconColorHighlight = Utils.getHomepageIconColorHighlight(context); 91 } 92 93 @Override onBindViewHolder(PreferenceViewHolder holder, int position)94 public void onBindViewHolder(PreferenceViewHolder holder, int position) { 95 super.onBindViewHolder(holder, position); 96 mViewHolders.put(position, holder); 97 updateBackground(holder, position); 98 } 99 100 @VisibleForTesting updateBackground(PreferenceViewHolder holder, int position)101 void updateBackground(PreferenceViewHolder holder, int position) { 102 if (!isHighlightNeeded()) { 103 removeHighlightBackground(holder, position); 104 return; 105 } 106 107 if (position == mHighlightPosition 108 && mHighlightKey != null 109 && TextUtils.equals(mHighlightKey, getItem(position).getKey())) { 110 // This position should be highlighted. 111 addHighlightBackground(holder, position); 112 } else { 113 removeHighlightBackground(holder, position); 114 } 115 } 116 117 /** 118 * A function can highlight a specific setting in recycler view. 119 */ requestHighlight()120 public void requestHighlight() { 121 if (mRecyclerView == null) { 122 return; 123 } 124 125 final int previousPosition = mHighlightPosition; 126 if (TextUtils.isEmpty(mHighlightKey)) { 127 // De-highlight previous preference. 128 mHighlightPosition = RecyclerView.NO_POSITION; 129 mScrolled = true; 130 if (previousPosition >= 0) { 131 notifyItemChanged(previousPosition); 132 } 133 return; 134 } 135 136 final int position = getPreferenceAdapterPosition(mHighlightKey); 137 if (position < 0) { 138 return; 139 } 140 141 // Scroll before highlight if needed. 142 final boolean highlightNeeded = isHighlightNeeded(); 143 if (highlightNeeded) { 144 mScrollPosition = position; 145 scroll(); 146 } 147 148 // Turn on/off highlight when screen split mode is changed. 149 if (highlightNeeded != mHighlightNeeded) { 150 Log.d(TAG, "Highlight needed change: " + highlightNeeded); 151 mHighlightNeeded = highlightNeeded; 152 mHighlightPosition = position; 153 notifyItemChanged(position); 154 if (!highlightNeeded) { 155 // De-highlight to prevent a flicker 156 removeHighlightAt(previousPosition); 157 } 158 return; 159 } 160 161 if (position == mHighlightPosition) { 162 return; 163 } 164 165 mHighlightPosition = position; 166 Log.d(TAG, "Request highlight position " + position); 167 Log.d(TAG, "Is highlight needed: " + highlightNeeded); 168 if (!highlightNeeded) { 169 return; 170 } 171 172 // Highlight preference. 173 notifyItemChanged(position); 174 175 // De-highlight previous preference. 176 if (previousPosition >= 0) { 177 notifyItemChanged(previousPosition); 178 } 179 } 180 181 /** 182 * A function that highlights a setting by specifying a preference key. Usually used whenever a 183 * preference is clicked. 184 */ highlightPreference(String key, boolean scrollNeeded)185 public void highlightPreference(String key, boolean scrollNeeded) { 186 mHighlightKey = key; 187 mScrolled = !scrollNeeded; 188 requestHighlight(); 189 } 190 191 @Override onHomepageLoaded()192 public void onHomepageLoaded() { 193 scroll(); 194 } 195 scroll()196 private void scroll() { 197 if (mScrolled || mScrollPosition < 0) { 198 return; 199 } 200 201 if (mHomepageActivity.addHomepageLoadedListener(this)) { 202 return; 203 } 204 205 // Only when the recyclerView is loaded, it can be scrolled 206 final View view = mRecyclerView.getChildAt(mScrollPosition); 207 if (view == null) { 208 mRecyclerView.postDelayed(() -> scroll(), DELAY_HIGHLIGHT_DURATION_MILLIS); 209 return; 210 } 211 212 mScrolled = true; 213 Log.d(TAG, "Scroll to position " + mScrollPosition); 214 // Scroll to the top to reset the position. 215 mRecyclerView.nestedScrollBy(0, -mRecyclerView.getHeight()); 216 217 // get the visible area of the recycler view 218 Rect rvRect = new Rect(); 219 mRecyclerView.getGlobalVisibleRect(rvRect); 220 if (Flags.homepageRevamp() && view.getBottom() <= rvRect.height()) { 221 // the request position already fully visible in the visible area 222 return; 223 } 224 225 final int scrollY = view.getTop(); 226 if (scrollY > 0) { 227 mRecyclerView.nestedScrollBy(0, scrollY); 228 } 229 } 230 removeHighlightAt(int position)231 private void removeHighlightAt(int position) { 232 if (position >= 0) { 233 // De-highlight the existing preference view holder at an early stage 234 final PreferenceViewHolder holder = mViewHolders.get(position); 235 if (holder != null) { 236 removeHighlightBackground(holder, position); 237 } 238 notifyItemChanged(position); 239 } 240 } 241 addHighlightBackground(PreferenceViewHolder holder, int position)242 private void addHighlightBackground(PreferenceViewHolder holder, int position) { 243 final View v = holder.itemView; 244 if (Flags.homepageRevamp()) { 245 @DrawableRes int bgRes = getRoundCornerDrawableRes(position, true /*isSelected*/); 246 v.setBackgroundResource(bgRes); 247 Context context = v.getContext(); 248 if (SettingsThemeHelper.isExpressiveTheme(context)) { 249 TextView title = v.findViewById(android.R.id.title); 250 if (title != null) { 251 title.setTextAppearance(context, com.android.settingslib.widget.theme.R.style 252 .TextAppearance_SettingsLib_TitleMedium_Emphasized); 253 } 254 } 255 } else { 256 v.setBackgroundResource(RES_HIGHLIGHTED_BACKGROUND); 257 ((TextView) v.findViewById(android.R.id.title)).setTextColor(mTitleColorHighlight); 258 ((TextView) v.findViewById(android.R.id.summary)).setTextColor(mSummaryColorHighlight); 259 final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable(); 260 if (drawable != null) { 261 drawable.setTint(mIconColorHighlight); 262 } 263 } 264 } 265 removeHighlightBackground(PreferenceViewHolder holder, int position)266 private void removeHighlightBackground(PreferenceViewHolder holder, int position) { 267 final View v = holder.itemView; 268 if (Flags.homepageRevamp()) { 269 @DrawableRes int bgRes = getRoundCornerDrawableRes(position, false /*isSelected*/); 270 v.setBackgroundResource(bgRes); 271 Context context = v.getContext(); 272 if (SettingsThemeHelper.isExpressiveTheme(context)) { 273 TextView title = v.findViewById(android.R.id.title); 274 if (title != null) { 275 title.setTextAppearance(context, com.android.settingslib.widget.theme.R.style 276 .TextAppearance_SettingsLib_TitleMedium); 277 } 278 } 279 } else { 280 v.setBackgroundResource(RES_NORMAL_BACKGROUND); 281 ((TextView) v.findViewById(android.R.id.title)).setTextColor(mTitleColorNormal); 282 ((TextView) v.findViewById(android.R.id.summary)).setTextColor(mSummaryColorNormal); 283 final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable(); 284 if (drawable != null) { 285 drawable.setTint(mIconColorNormal); 286 } 287 } 288 } 289 isHighlightNeeded()290 private boolean isHighlightNeeded() { 291 return ActivityEmbeddingController.getInstance(mHomepageActivity) 292 .isActivityEmbedded(mHomepageActivity); 293 } 294 } 295