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 import java.util.Arrays; 31 import java.util.Comparator; 32 33 /** 34 * This class is used to add {@link View.OnClickListener} for the text been wrapped by 35 * annotation. 36 */ 37 public class AnnotationSpan extends URLSpan { 38 39 private final View.OnClickListener mClickListener; 40 AnnotationSpan(View.OnClickListener lsn)41 private AnnotationSpan(View.OnClickListener lsn) { 42 super((String) null); 43 mClickListener = lsn; 44 } 45 46 @Override onClick(View widget)47 public void onClick(View widget) { 48 if (mClickListener != null) { 49 mClickListener.onClick(widget); 50 } 51 } 52 53 @Override updateDrawState(TextPaint ds)54 public void updateDrawState(TextPaint ds) { 55 super.updateDrawState(ds); 56 ds.setUnderlineText(true); 57 } 58 linkify(CharSequence rawText, LinkInfo... linkInfos)59 public static CharSequence linkify(CharSequence rawText, LinkInfo... linkInfos) { 60 SpannableString msg = new SpannableString(rawText); 61 Annotation[] spans = msg.getSpans(0, msg.length(), Annotation.class); 62 SpannableStringBuilder builder = new SpannableStringBuilder(msg); 63 for (Annotation annotation : spans) { 64 final String key = annotation.getValue(); 65 int start = msg.getSpanStart(annotation); 66 int end = msg.getSpanEnd(annotation); 67 AnnotationSpan link = null; 68 for (LinkInfo linkInfo : linkInfos) { 69 if (linkInfo.mAnnotation.equals(key)) { 70 link = new AnnotationSpan(linkInfo.mListener); 71 break; 72 } 73 } 74 if (link != null) { 75 builder.setSpan(link, start, end, msg.getSpanFlags(link)); 76 } 77 } 78 return builder; 79 } 80 81 /** 82 * get the text part without having text for link part 83 */ textWithoutLink(CharSequence encodedText)84 public static CharSequence textWithoutLink(CharSequence encodedText) { 85 SpannableString msg = new SpannableString(encodedText); 86 Annotation[] spans = msg.getSpans(0, msg.length(), Annotation.class); 87 if (spans == null) { 88 return encodedText; 89 } 90 Arrays.sort(spans, Comparator.comparingInt(span -> -msg.getSpanStart(span))); 91 StringBuilder msgWithoutLink = new StringBuilder(msg.toString()); 92 for (Annotation span : spans) { 93 msgWithoutLink.delete(msg.getSpanStart(span), msg.getSpanEnd(span)); 94 } 95 return msgWithoutLink.toString(); 96 } 97 98 /** 99 * Data class to store the annotation and the click action 100 */ 101 public static class LinkInfo { 102 private static final String TAG = "AnnotationSpan.LinkInfo"; 103 public static final String DEFAULT_ANNOTATION = "link"; 104 private final String mAnnotation; 105 private final Boolean mActionable; 106 private final View.OnClickListener mListener; 107 LinkInfo(String annotation, View.OnClickListener listener)108 public LinkInfo(String annotation, View.OnClickListener listener) { 109 mAnnotation = annotation; 110 mListener = listener; 111 mActionable = true; // assume actionable 112 } 113 LinkInfo(Context context, String annotation, Intent intent)114 public LinkInfo(Context context, String annotation, Intent intent) { 115 mAnnotation = annotation; 116 if (intent != null) { 117 mActionable = context.getPackageManager() 118 .resolveActivity(intent, 0 /* flags */) != null; 119 } else { 120 mActionable = false; 121 } 122 if (!mActionable) { 123 mListener = null; 124 } else { 125 mListener = view -> { 126 try { 127 view.startActivityForResult(intent, 0); 128 } catch (ActivityNotFoundException e) { 129 Log.w(TAG, "Activity was not found for intent, " + intent); 130 } 131 }; 132 } 133 } 134 isActionable()135 public boolean isActionable() { 136 return mActionable; 137 } 138 } 139 } 140