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 */
16package com.android.managedprovisioning.common;
17
18import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK;
19
20import android.app.Activity;
21import android.support.annotation.NonNull;
22import android.support.annotation.Nullable;
23import android.text.Spanned;
24import android.text.style.ClickableSpan;
25import android.view.ContextMenu;
26import android.view.View;
27import android.view.accessibility.AccessibilityManager;
28import android.view.accessibility.AccessibilityNodeInfo;
29import android.widget.TextView;
30
31import 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 */
39public class AccessibilityContextMenuMaker {
40    private final Activity mActivity;
41
42    /**
43     * @param activity the target {@link TextView} belongs to
44     */
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     */
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     */
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
112    private boolean isScreenReaderEnabled() {
113        AccessibilityManager am = mActivity.getSystemService(AccessibilityManager.class);
114        return am.isEnabled() && am.isTouchExplorationEnabled();
115    }
116
117    private @Nullable Spanned getText(TextView textView) {
118        CharSequence text = textView.getText();
119        return (text instanceof Spanned) ? (Spanned) text : null;
120    }
121
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}