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