1 /*
2  * Copyright (C) 2020 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.settingslib.widget;
18 
19 import android.content.Context;
20 import android.text.SpannableString;
21 import android.text.TextUtils;
22 import android.text.style.URLSpan;
23 import android.util.AttributeSet;
24 import android.view.View;
25 import android.widget.TextView;
26 
27 import androidx.annotation.NonNull;
28 import androidx.annotation.StringRes;
29 import androidx.annotation.VisibleForTesting;
30 import androidx.preference.Preference;
31 import androidx.preference.PreferenceViewHolder;
32 
33 /**
34  * A custom preference acting as "footer" of a page. It has a field for icon and text. It is added
35  * to screen as the last preference.
36  */
37 public class FooterPreference extends Preference {
38 
39     public static final String KEY_FOOTER = "footer_preference";
40     static final int ORDER_FOOTER = Integer.MAX_VALUE - 1;
41     @VisibleForTesting
42     View.OnClickListener mLearnMoreListener;
43     @VisibleForTesting
44     int mIconVisibility = View.VISIBLE;
45     private CharSequence mContentDescription;
46     private CharSequence mLearnMoreText;
47     private FooterLearnMoreSpan mLearnMoreSpan;
48 
FooterPreference(Context context, AttributeSet attrs)49     public FooterPreference(Context context, AttributeSet attrs) {
50         super(context, attrs, R.attr.footerPreferenceStyle);
51         init();
52     }
53 
FooterPreference(Context context)54     public FooterPreference(Context context) {
55         this(context, null);
56     }
57 
58     @Override
onBindViewHolder(PreferenceViewHolder holder)59     public void onBindViewHolder(PreferenceViewHolder holder) {
60         super.onBindViewHolder(holder);
61         TextView title = holder.itemView.findViewById(android.R.id.title);
62         if (title != null && !TextUtils.isEmpty(mContentDescription)) {
63             title.setContentDescription(mContentDescription);
64         }
65 
66         TextView learnMore = holder.itemView.findViewById(R.id.settingslib_learn_more);
67         if (learnMore != null) {
68             if (mLearnMoreListener != null) {
69                 learnMore.setVisibility(View.VISIBLE);
70                 if (TextUtils.isEmpty(mLearnMoreText)) {
71                     mLearnMoreText = learnMore.getText();
72                 } else {
73                     learnMore.setText(mLearnMoreText);
74                 }
75                 SpannableString learnMoreText = new SpannableString(mLearnMoreText);
76                 if (mLearnMoreSpan != null) {
77                     learnMoreText.removeSpan(mLearnMoreSpan);
78                 }
79                 mLearnMoreSpan = new FooterLearnMoreSpan(mLearnMoreListener);
80                 learnMoreText.setSpan(mLearnMoreSpan, 0,
81                         learnMoreText.length(), 0);
82                 learnMore.setText(learnMoreText);
83             } else {
84                 learnMore.setVisibility(View.GONE);
85             }
86         }
87 
88         View icon = holder.itemView.findViewById(R.id.icon_frame);
89         if (icon != null) {
90             icon.setVisibility(mIconVisibility);
91         }
92     }
93 
94     @Override
setSummary(CharSequence summary)95     public void setSummary(CharSequence summary) {
96         setTitle(summary);
97     }
98 
99     @Override
setSummary(int summaryResId)100     public void setSummary(int summaryResId) {
101         setTitle(summaryResId);
102     }
103 
104     @Override
getSummary()105     public CharSequence getSummary() {
106         return getTitle();
107     }
108 
109     /**
110      * To set content description of the {@link FooterPreference}. This can use for talkback
111      * environment if developer wants to have a customization content.
112      *
113      * @param contentDescription The resource id of the content description.
114      */
setContentDescription(CharSequence contentDescription)115     public void setContentDescription(CharSequence contentDescription) {
116         if (!TextUtils.equals(mContentDescription, contentDescription)) {
117             mContentDescription = contentDescription;
118             notifyChanged();
119         }
120     }
121 
122     /**
123      * Return the content description of footer preference.
124      */
125     @VisibleForTesting
getContentDescription()126     CharSequence getContentDescription() {
127         return mContentDescription;
128     }
129 
130     /**
131      * Sets the learn more text.
132      *
133      * @param learnMoreText The string of the learn more text.
134      */
setLearnMoreText(CharSequence learnMoreText)135     public void setLearnMoreText(CharSequence learnMoreText) {
136         if (!TextUtils.equals(mLearnMoreText, learnMoreText)) {
137             mLearnMoreText = learnMoreText;
138             notifyChanged();
139         }
140     }
141 
142     /**
143      * Assign an action for the learn more link.
144      */
setLearnMoreAction(View.OnClickListener listener)145     public void setLearnMoreAction(View.OnClickListener listener) {
146         if (mLearnMoreListener != listener) {
147             mLearnMoreListener = listener;
148             notifyChanged();
149         }
150     }
151 
152     /**
153      * Set visibility of footer icon.
154      */
setIconVisibility(int iconVisibility)155     public void setIconVisibility(int iconVisibility) {
156         if (mIconVisibility == iconVisibility) {
157             return;
158         }
159         mIconVisibility = iconVisibility;
160         notifyChanged();
161     }
162 
init()163     private void init() {
164         setLayoutResource(R.layout.preference_footer);
165         if (getIcon() == null) {
166             setIcon(R.drawable.settingslib_ic_info_outline_24);
167         }
168         setOrder(ORDER_FOOTER);
169         if (TextUtils.isEmpty(getKey())) {
170             setKey(KEY_FOOTER);
171         }
172         setSelectable(false);
173     }
174 
175     /**
176      * The builder is convenient to creat a dynamic FooterPreference.
177      */
178     public static class Builder {
179         private Context mContext;
180         private String mKey;
181         private CharSequence mTitle;
182         private CharSequence mContentDescription;
183         private CharSequence mLearnMoreText;
184 
Builder(@onNull Context context)185         public Builder(@NonNull Context context) {
186             mContext = context;
187         }
188 
189         /**
190          * To set the key value of the {@link FooterPreference}.
191          *
192          * @param key The key value.
193          */
setKey(@onNull String key)194         public Builder setKey(@NonNull String key) {
195             mKey = key;
196             return this;
197         }
198 
199         /**
200          * To set the title of the {@link FooterPreference}.
201          *
202          * @param title The title.
203          */
setTitle(CharSequence title)204         public Builder setTitle(CharSequence title) {
205             mTitle = title;
206             return this;
207         }
208 
209         /**
210          * To set the title of the {@link FooterPreference}.
211          *
212          * @param titleResId The resource id of the title.
213          */
setTitle(@tringRes int titleResId)214         public Builder setTitle(@StringRes int titleResId) {
215             mTitle = mContext.getText(titleResId);
216             return this;
217         }
218 
219         /**
220          * To set content description of the {@link FooterPreference}. This can use for talkback
221          * environment if developer wants to have a customization content.
222          *
223          * @param contentDescription The resource id of the content description.
224          */
setContentDescription(CharSequence contentDescription)225         public Builder setContentDescription(CharSequence contentDescription) {
226             mContentDescription = contentDescription;
227             return this;
228         }
229 
230         /**
231          * To set content description of the {@link FooterPreference}. This can use for talkback
232          * environment if developer wants to have a customization content.
233          *
234          * @param contentDescriptionResId The resource id of the content description.
235          */
setContentDescription(@tringRes int contentDescriptionResId)236         public Builder setContentDescription(@StringRes int contentDescriptionResId) {
237             mContentDescription = mContext.getText(contentDescriptionResId);
238             return this;
239         }
240 
241         /**
242          * To set learn more string of the learn more text. This can use for talkback
243          * environment if developer wants to have a customization content.
244          *
245          * @param learnMoreText The resource id of the learn more string.
246          */
setLearnMoreText(CharSequence learnMoreText)247         public Builder setLearnMoreText(CharSequence learnMoreText) {
248             mLearnMoreText = learnMoreText;
249             return this;
250         }
251 
252         /**
253          * To set learn more string of the {@link FooterPreference}. This can use for talkback
254          * environment if developer wants to have a customization content.
255          *
256          * @param learnMoreTextResId The resource id of the learn more string.
257          */
setLearnMoreText(@tringRes int learnMoreTextResId)258         public Builder setLearnMoreText(@StringRes int learnMoreTextResId) {
259             mLearnMoreText = mContext.getText(learnMoreTextResId);
260             return this;
261         }
262 
263 
264         /**
265          * To generate the {@link FooterPreference}.
266          */
build()267         public FooterPreference build() {
268             final FooterPreference footerPreference = new FooterPreference(mContext);
269             footerPreference.setSelectable(false);
270             if (TextUtils.isEmpty(mTitle)) {
271                 throw new IllegalArgumentException("Footer title cannot be empty!");
272             }
273             footerPreference.setTitle(mTitle);
274             if (!TextUtils.isEmpty(mKey)) {
275                 footerPreference.setKey(mKey);
276             }
277 
278             if (!TextUtils.isEmpty(mContentDescription)) {
279                 footerPreference.setContentDescription(mContentDescription);
280             }
281 
282             if (!TextUtils.isEmpty(mLearnMoreText)) {
283                 footerPreference.setLearnMoreText(mLearnMoreText);
284             }
285             return footerPreference;
286         }
287     }
288 
289     /**
290      * A {@link URLSpan} that opens a support page when clicked
291      */
292     static class FooterLearnMoreSpan extends URLSpan {
293 
294         private final View.OnClickListener mClickListener;
295 
FooterLearnMoreSpan(View.OnClickListener clickListener)296         FooterLearnMoreSpan(View.OnClickListener clickListener) {
297             // sets the url to empty string so we can prevent any other span processing from
298             // clearing things we need in this string.
299             super("");
300             mClickListener = clickListener;
301         }
302 
303         @Override
onClick(View widget)304         public void onClick(View widget) {
305             if (mClickListener != null) {
306                 mClickListener.onClick(widget);
307             }
308         }
309     }
310 }
311