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.systemui.accessibility.floatingmenu;
18 
19 import android.text.Annotation;
20 import android.text.SpannableString;
21 import android.text.SpannableStringBuilder;
22 import android.text.style.ClickableSpan;
23 import android.view.View;
24 
25 import androidx.annotation.NonNull;
26 
27 import java.util.Arrays;
28 import java.util.Optional;
29 
30 /**
31  * A span that turns the text wrapped by annotation tag into the clickable link text.
32  */
33 public class AnnotationLinkSpan extends ClickableSpan {
34     private final Optional<View.OnClickListener> mClickListener;
35 
AnnotationLinkSpan(View.OnClickListener listener)36     private AnnotationLinkSpan(View.OnClickListener listener) {
37         mClickListener = Optional.ofNullable(listener);
38     }
39 
40     @Override
onClick(View view)41     public void onClick(View view) {
42         mClickListener.ifPresent(listener -> listener.onClick(view));
43     }
44 
45     /**
46      * Makes the text has the link with the click action. In addition, the span will match first
47      * LinkInfo and attach into the text.
48      *
49      * @param text the text wrapped by annotation tag
50      * @param linkInfos used to attach the click action into the corresponding span
51      * @return the text attached with the span
52      */
linkify(CharSequence text, LinkInfo... linkInfos)53     public static CharSequence linkify(CharSequence text, LinkInfo... linkInfos) {
54         final SpannableString msg = new SpannableString(text);
55         final Annotation[] spans =
56                 msg.getSpans(/* queryStart= */ 0, msg.length(), Annotation.class);
57         final SpannableStringBuilder builder = new SpannableStringBuilder(msg);
58 
59         Arrays.asList(spans).forEach(annotationTag -> {
60             final String key = annotationTag.getValue();
61             final Optional<LinkInfo> linkInfo =
62                     Arrays.asList(linkInfos).stream().filter(
63                             info -> info.mAnnotation.isPresent()
64                                     && info.mAnnotation.get().equals(key)).findFirst();
65 
66             linkInfo.flatMap(info -> info.mListener).ifPresent(listener -> {
67                 final AnnotationLinkSpan span = new AnnotationLinkSpan(listener);
68                 builder.setSpan(span,
69                         msg.getSpanStart(annotationTag),
70                         msg.getSpanEnd(annotationTag),
71                         msg.getSpanFlags(span));
72             });
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         public static final String DEFAULT_ANNOTATION = "link";
83         private final Optional<String> mAnnotation;
84         private final Optional<View.OnClickListener> mListener;
85 
LinkInfo(@onNull String annotation, View.OnClickListener listener)86         public LinkInfo(@NonNull String annotation, View.OnClickListener listener) {
87             mAnnotation = Optional.of(annotation);
88             mListener = Optional.ofNullable(listener);
89         }
90     }
91 }
92