1 /* 2 * Copyright 2017, 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 package com.android.managedprovisioning.common; 17 18 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK; 19 20 import android.app.Activity; 21 import androidx.annotation.NonNull; 22 import androidx.annotation.Nullable; 23 import android.text.Spanned; 24 import android.text.style.ClickableSpan; 25 import android.view.ContextMenu; 26 import android.view.View; 27 import android.view.accessibility.AccessibilityManager; 28 import android.view.accessibility.AccessibilityNodeInfo; 29 import android.widget.TextView; 30 31 import com.android.managedprovisioning.R; 32 33 /** 34 * Creates a new {@link ContextMenu}, and populates it with a list of links contained in a target 35 * {@link TextView}. 36 * <p> 37 * Known issue: does not listen to TalkBack on / off events. 38 */ 39 public class AccessibilityContextMenuMaker { 40 private final Activity mActivity; 41 42 /** 43 * @param activity the target {@link TextView} belongs to 44 */ AccessibilityContextMenuMaker(Activity activity)45 public AccessibilityContextMenuMaker(Activity activity) { 46 mActivity = activity; 47 } 48 49 /** 50 * If {@link ClickableSpan} links present, registers a context menu with the {@link Activity}. 51 * If no links present, unregisters, which is useful in case of recyclable views. 52 * 53 * @param textView target TextView potentially containing links. 54 */ registerWithActivity(TextView textView)55 public void registerWithActivity(TextView textView) { 56 if (getSpans(getText(textView)).length == 0) { 57 mActivity.unregisterForContextMenu(textView); 58 textView.setAccessibilityDelegate(null); 59 textView.setClickable(false); 60 textView.setLongClickable(false); 61 return; 62 } 63 64 mActivity.registerForContextMenu(textView); 65 textView.setOnClickListener(View::showContextMenu); 66 textView.setLongClickable(false); 67 textView.setAccessibilityDelegate( 68 new View.AccessibilityDelegate() { 69 @Override 70 public void onInitializeAccessibilityNodeInfo(View host, 71 AccessibilityNodeInfo info) { 72 super.onInitializeAccessibilityNodeInfo(host, info); 73 info.addAction( 74 new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK.getId(), 75 textView.getContext().getString( 76 R.string.access_list_of_links))); 77 } 78 } 79 ); 80 } 81 82 /** 83 * Call inside 84 * {@link Activity#onCreateContextMenu(ContextMenu, View, ContextMenu.ContextMenuInfo)} 85 */ populateMenuContent(ContextMenu menu, TextView textView)86 public void populateMenuContent(ContextMenu menu, TextView textView) { 87 if (!isScreenReaderEnabled()) { 88 return; 89 } 90 91 Spanned spanned = getText(textView); 92 ClickableSpan[] spans = getSpans(spanned); 93 94 if (spanned == null || spans.length == 0) { 95 return; 96 } 97 98 for (ClickableSpan span : spans) { 99 int s = spanned.getSpanStart(span); 100 int t = spanned.getSpanEnd(span); 101 menu.add(spanned.subSequence(s, t)).setOnMenuItemClickListener(menuItem -> { 102 span.onClick(textView); 103 return false; 104 }); 105 } 106 menu.add(R.string.close_list).setOnMenuItemClickListener(menuItem -> { 107 menu.close(); 108 return false; 109 }); 110 } 111 isScreenReaderEnabled()112 private boolean isScreenReaderEnabled() { 113 AccessibilityManager am = mActivity.getSystemService(AccessibilityManager.class); 114 return am.isEnabled() && am.isTouchExplorationEnabled(); 115 } 116 getText(TextView textView)117 private @Nullable Spanned getText(TextView textView) { 118 CharSequence text = textView.getText(); 119 return (text instanceof Spanned) ? (Spanned) text : null; 120 } 121 getSpans(Spanned spanned)122 private @NonNull ClickableSpan[] getSpans(Spanned spanned) { 123 if (spanned == null) { 124 return new ClickableSpan[0]; 125 } 126 ClickableSpan[] spans = spanned.getSpans(0, spanned.length(), ClickableSpan.class); 127 return spans.length == 0 ? new ClickableSpan[0] : spans; 128 } 129 }