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