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 }