1 /*
2  * Copyright (C) 2018 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.utils;
18 
19 import android.content.ActivityNotFoundException;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.text.Annotation;
23 import android.text.SpannableString;
24 import android.text.SpannableStringBuilder;
25 import android.text.TextPaint;
26 import android.text.style.URLSpan;
27 import android.util.Log;
28 import android.view.View;
29 
30 /**
31  * This class is used to add {@link View.OnClickListener} for the text been wrapped by
32  * annotation.
33  */
34 public class AnnotationSpan extends URLSpan {
35 
36     private final View.OnClickListener mClickListener;
37 
AnnotationSpan(View.OnClickListener lsn)38     private AnnotationSpan(View.OnClickListener lsn) {
39         super((String) null);
40         mClickListener = lsn;
41     }
42 
43     @Override
onClick(View widget)44     public void onClick(View widget) {
45         if (mClickListener != null) {
46             mClickListener.onClick(widget);
47         }
48     }
49 
50     @Override
updateDrawState(TextPaint ds)51     public void updateDrawState(TextPaint ds) {
52         super.updateDrawState(ds);
53         ds.setUnderlineText(false);
54     }
55 
linkify(CharSequence rawText, LinkInfo... linkInfos)56     public static CharSequence linkify(CharSequence rawText, LinkInfo... linkInfos) {
57         SpannableString msg = new SpannableString(rawText);
58         Annotation[] spans = msg.getSpans(0, msg.length(), Annotation.class);
59         SpannableStringBuilder builder = new SpannableStringBuilder(msg);
60         for (Annotation annotation : spans) {
61             final String key = annotation.getValue();
62             int start = msg.getSpanStart(annotation);
63             int end = msg.getSpanEnd(annotation);
64             AnnotationSpan link = null;
65             for (LinkInfo linkInfo : linkInfos) {
66                 if (linkInfo.mAnnotation.equals(key)) {
67                     link = new AnnotationSpan(linkInfo.mListener);
68                     break;
69                 }
70             }
71             if (link != null) {
72                 builder.setSpan(link, start, end, msg.getSpanFlags(link));
73             }
74         }
75         return builder;
76     }
77 
78     /**
79      * Data class to store the annotation and the click action
80      */
81     public static class LinkInfo {
82         private static final String TAG = "AnnotationSpan.LinkInfo";
83         public static final String DEFAULT_ANNOTATION = "link";
84         private final String mAnnotation;
85         private final Boolean mActionable;
86         private final View.OnClickListener mListener;
87 
LinkInfo(String annotation, View.OnClickListener listener)88         public LinkInfo(String annotation, View.OnClickListener listener) {
89             mAnnotation = annotation;
90             mListener = listener;
91             mActionable = true; // assume actionable
92         }
93 
LinkInfo(Context context, String annotation, Intent intent)94         public LinkInfo(Context context, String annotation, Intent intent) {
95             mAnnotation = annotation;
96             if (intent != null) {
97                 mActionable = context.getPackageManager()
98                         .resolveActivity(intent, 0 /* flags */) != null;
99             } else {
100                 mActionable = false;
101             }
102             if (!mActionable) {
103                 mListener = null;
104             } else {
105                 mListener = view -> {
106                     try {
107                         view.startActivityForResult(intent, 0);
108                     } catch (ActivityNotFoundException e) {
109                         Log.w(TAG, "Activity was not found for intent, " + intent);
110                     }
111                 };
112             }
113         }
114 
isActionable()115         public boolean isActionable() {
116             return mActionable;
117         }
118     }
119 }
120