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