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