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