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