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}