/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.android.talkback.menurules;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.view.Menu;
import android.view.MenuItem;
import com.android.talkback.R;
import com.google.android.marvin.talkback.TalkBackService;
import com.android.talkback.contextmenu.ContextMenuItem;
import com.android.talkback.contextmenu.ContextMenuItemBuilder;
import java.util.LinkedList;
import java.util.List;
/**
* Menu population rule for views with Spannable link contents.
*/
public class RuleSpannables implements NodeMenuRule {
/**
* Retrieves SpannableString in the accessibility node. The content description and text of the
* node is checked in order.
* @param node
* @return SpannableString with at least 1 UrlSpan. null if no UrlSpan found in the node.
*/
private static SpannableString getStringWithUrlSpan(AccessibilityNodeInfoCompat node) {
CharSequence text = node.getContentDescription();
if (!TextUtils.isEmpty(text)) {
if (!(text instanceof SpannableString)) {
return null;
}
} else {
text = node.getText();
if (TextUtils.isEmpty(text) || !(text instanceof SpannableString)) {
return null;
}
}
SpannableString spannable = (SpannableString) text;
final URLSpan[] urlSpans = spannable.getSpans(0, spannable.length(), URLSpan.class);
if (urlSpans == null || urlSpans.length == 0) {
return null;
}
return spannable;
}
@Override
public boolean accept(TalkBackService service, AccessibilityNodeInfoCompat node) {
return getStringWithUrlSpan(node) != null;
}
@Override
public List<ContextMenuItem> getMenuItemsForNode(
TalkBackService service, ContextMenuItemBuilder menuItemBuilder,
AccessibilityNodeInfoCompat node) {
final LinkedList<ContextMenuItem> result = new LinkedList<>();
final SpannableString spannable = getStringWithUrlSpan(node);
if (spannable == null) {
return result;
}
final URLSpan[] urlSpans = spannable.getSpans(0, spannable.length(), URLSpan.class);
if ((urlSpans == null) || (urlSpans.length == 0)) {
return result;
}
for (int i = 0; i < urlSpans.length; i++) {
final URLSpan urlSpan = urlSpans[i];
final String url = urlSpan.getURL();
final int start = spannable.getSpanStart(urlSpan);
final int end = spannable.getSpanEnd(urlSpan);
final CharSequence label = spannable.subSequence(start, end);
if (TextUtils.isEmpty(url) || TextUtils.isEmpty(label)) {
continue;
}
final Uri uri = Uri.parse(url);
if (uri.isRelative()) {
// Generally, only absolute URIs are resolvable to an activity
continue;
}
final ContextMenuItem item = menuItemBuilder.createMenuItem(service, Menu.NONE, i,
Menu.NONE, label);
item.setOnMenuItemClickListener(new SpannableMenuClickListener(service, uri));
result.add(item);
}
return result;
}
@Override
public CharSequence getUserFriendlyMenuName(Context context) {
return context.getString(R.string.links);
}
@Override
public boolean canCollapseMenu() {
return true;
}
/**
* Click listener for menu items representing {@link Spannable}s.
*/
private static class SpannableMenuClickListener implements MenuItem.OnMenuItemClickListener {
final Context mContext;
final Uri mUri;
public SpannableMenuClickListener(Context context, Uri uri) {
mContext = context;
mUri = uri;
}
@Override
public boolean onMenuItemClick(MenuItem item) {
if (mContext == null) {
return false;
}
final Intent intent = new Intent(Intent.ACTION_VIEW, mUri);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
mContext.startActivity(intent);
} catch (ActivityNotFoundException e) {
return false;
}
return true;
}
}
}