1 /* 2 * Copyright (C) 2016 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.setupwizardlib.span; 18 19 import android.content.Context; 20 import android.content.ContextWrapper; 21 import android.graphics.Typeface; 22 import android.os.Build; 23 import android.support.annotation.Nullable; 24 import android.text.TextPaint; 25 import android.text.style.ClickableSpan; 26 import android.util.Log; 27 import android.view.View; 28 29 /** 30 * A clickable span that will listen for click events and send it back to the context. To use this 31 * class, implement {@link OnLinkClickListener} in your TextView, or use 32 * {@link com.android.setupwizardlib.view.RichTextView#setOnClickListener(View.OnClickListener)}. 33 * 34 * <p />Note on accessibility: For TalkBack to be able to traverse and interact with the links, you 35 * should use {@code LinkAccessibilityHelper} in your {@code TextView} subclass. Optionally you can 36 * also use {@code RichTextView}, which includes link support. 37 */ 38 public class LinkSpan extends ClickableSpan { 39 40 /* 41 * Implementation note: When the orientation changes, TextView retains a reference to this span 42 * instead of writing it to a parcel (ClickableSpan is not Parcelable). If this class has any 43 * reference to the containing Activity (i.e. the activity context, or any views in the 44 * activity), it will cause memory leak. 45 */ 46 47 /* static section */ 48 49 private static final String TAG = "LinkSpan"; 50 51 private static final Typeface TYPEFACE_MEDIUM = 52 Typeface.create("sans-serif-medium", Typeface.NORMAL); 53 54 /** 55 * @deprecated Use {@link OnLinkClickListener} 56 */ 57 @Deprecated 58 public interface OnClickListener { 59 void onClick(LinkSpan span); 60 } 61 62 /** 63 * Listener that is invoked when a link span is clicked. If the containing view of this span 64 * implements this interface, this will be invoked when the link is clicked. 65 */ 66 public interface OnLinkClickListener { 67 68 /** 69 * Called when a link has been clicked. 70 * 71 * @param span The span that was clicked. 72 * @return True if the click was handled, stopping further propagation of the click event. 73 */ 74 boolean onLinkClick(LinkSpan span); 75 } 76 77 /* non-static section */ 78 79 private final String mId; 80 81 public LinkSpan(String id) { 82 mId = id; 83 } 84 85 @Override 86 public void onClick(View view) { 87 if (dispatchClick(view)) { 88 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 89 view.cancelPendingInputEvents(); 90 } 91 } else { 92 Log.w(TAG, "Dropping click event. No listener attached."); 93 } 94 } 95 96 private boolean dispatchClick(View view) { 97 boolean handled = false; 98 if (view instanceof OnLinkClickListener) { 99 handled = ((OnLinkClickListener) view).onLinkClick(this); 100 } 101 if (!handled) { 102 final OnClickListener listener = getLegacyListenerFromContext(view.getContext()); 103 if (listener != null) { 104 listener.onClick(this); 105 handled = true; 106 } 107 } 108 return handled; 109 } 110 111 /** 112 * @deprecated Deprecated together with {@link OnClickListener} 113 */ 114 @Nullable 115 @Deprecated 116 private OnClickListener getLegacyListenerFromContext(@Nullable Context context) { 117 while (true) { 118 if (context instanceof OnClickListener) { 119 return (OnClickListener) context; 120 } else if (context instanceof ContextWrapper) { 121 // Unwrap any context wrapper, in base the base context implements onClickListener. 122 // ContextWrappers cannot have circular base contexts, so at some point this will 123 // reach the one of the other cases and return. 124 context = ((ContextWrapper) context).getBaseContext(); 125 } else { 126 return null; 127 } 128 } 129 } 130 131 @Override 132 public void updateDrawState(TextPaint drawState) { 133 super.updateDrawState(drawState); 134 drawState.setUnderlineText(false); 135 drawState.setTypeface(TYPEFACE_MEDIUM); 136 } 137 138 public String getId() { 139 return mId; 140 } 141 } 142