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