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