1ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak/* 2ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * Copyright 2017, The Android Open Source Project 3ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * 4ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * Licensed under the Apache License, Version 2.0 (the "License"); 5ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * you may not use this file except in compliance with the License. 6ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * You may obtain a copy of the License at 7ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * 8ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * http://www.apache.org/licenses/LICENSE-2.0 9ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * 10ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * Unless required by applicable law or agreed to in writing, software 11ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * distributed under the License is distributed on an "AS IS" BASIS, 12ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * See the License for the specific language governing permissions and 14ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * limitations under the License. 15ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak */ 16ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzakpackage com.android.managedprovisioning.common; 17ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak 18ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzakimport static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK; 19ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak 20ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzakimport android.app.Activity; 21ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzakimport android.support.annotation.NonNull; 22ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzakimport android.support.annotation.Nullable; 23ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzakimport android.text.Spanned; 24ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzakimport android.text.style.ClickableSpan; 25ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzakimport android.view.ContextMenu; 26ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzakimport android.view.View; 27ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzakimport android.view.accessibility.AccessibilityManager; 28ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzakimport android.view.accessibility.AccessibilityNodeInfo; 29ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzakimport android.widget.TextView; 30ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak 31ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzakimport com.android.managedprovisioning.R; 32ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak 33ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak/** 34ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * Creates a new {@link ContextMenu}, and populates it with a list of links contained in a target 35ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * {@link TextView}. 36ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * <p> 37ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * Known issue: does not listen to TalkBack on / off events. 38ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak */ 39ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzakpublic class AccessibilityContextMenuMaker { 40ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak private final Activity mActivity; 41ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak 42ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak /** 43ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * @param activity the target {@link TextView} belongs to 44ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak */ 45ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak public AccessibilityContextMenuMaker(Activity activity) { 46ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak mActivity = activity; 47ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak } 48ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak 49ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak /** 50ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * If {@link ClickableSpan} links present, registers a context menu with the {@link Activity}. 51ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * If no links present, unregisters, which is useful in case of recyclable views. 52ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * 53ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * @param textView target TextView potentially containing links. 54ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak */ 55ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak public void registerWithActivity(TextView textView) { 56ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak if (getSpans(getText(textView)).length == 0) { 57ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak mActivity.unregisterForContextMenu(textView); 58ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak textView.setAccessibilityDelegate(null); 59ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak textView.setClickable(false); 60ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak textView.setLongClickable(false); 61ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak return; 62ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak } 63ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak 64ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak mActivity.registerForContextMenu(textView); 65ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak textView.setOnClickListener(View::showContextMenu); 66ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak textView.setLongClickable(false); 67ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak textView.setAccessibilityDelegate( 68ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak new View.AccessibilityDelegate() { 69ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak @Override 70ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak public void onInitializeAccessibilityNodeInfo(View host, 71ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak AccessibilityNodeInfo info) { 72ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak super.onInitializeAccessibilityNodeInfo(host, info); 73ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak info.addAction( 74ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK.getId(), 75ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak textView.getContext().getString( 76ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak R.string.access_list_of_links))); 77ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak } 78ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak } 79ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak ); 80ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak } 81ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak 82ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak /** 83ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * Call inside 84ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak * {@link Activity#onCreateContextMenu(ContextMenu, View, ContextMenu.ContextMenuInfo)} 85ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak */ 86ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak public void populateMenuContent(ContextMenu menu, TextView textView) { 87ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak if (!isScreenReaderEnabled()) { 88ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak return; 89ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak } 90ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak 91ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak Spanned spanned = getText(textView); 92ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak ClickableSpan[] spans = getSpans(spanned); 93ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak 94ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak if (spanned == null || spans.length == 0) { 95ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak return; 96ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak } 97ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak 98ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak for (ClickableSpan span : spans) { 99ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak int s = spanned.getSpanStart(span); 100ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak int t = spanned.getSpanEnd(span); 101ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak menu.add(spanned.subSequence(s, t)).setOnMenuItemClickListener(menuItem -> { 102ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak span.onClick(textView); 103ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak return false; 104ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak }); 105ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak } 106ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak menu.add(R.string.close_list).setOnMenuItemClickListener(menuItem -> { 107ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak menu.close(); 108ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak return false; 109ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak }); 110ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak } 111ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak 112ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak private boolean isScreenReaderEnabled() { 113ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak AccessibilityManager am = mActivity.getSystemService(AccessibilityManager.class); 114ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak return am.isEnabled() && am.isTouchExplorationEnabled(); 115ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak } 116ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak 117ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak private @Nullable Spanned getText(TextView textView) { 118ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak CharSequence text = textView.getText(); 119ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak return (text instanceof Spanned) ? (Spanned) text : null; 120ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak } 121ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak 122ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak private @NonNull ClickableSpan[] getSpans(Spanned spanned) { 123ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak if (spanned == null) { 124ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak return new ClickableSpan[0]; 125ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak } 126ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak ClickableSpan[] spans = spanned.getSpans(0, spanned.length(), ClickableSpan.class); 127ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak return spans.length == 0 ? new ClickableSpan[0] : spans; 128ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak } 129ddc4fac0d501df001a8fe3e3f4e3b8f4c9c05a93Jakub Gielzak}