M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions

View File

@ -0,0 +1,543 @@
/*
* Copyright (C) 2007 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 android.text.util;
import android.text.method.LinkMovementMethod;
import android.text.method.MovementMethod;
import android.text.style.URLSpan;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.util.Patterns;
import android.webkit.WebView;
import android.widget.TextView;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Linkify take a piece of text and a regular expression and turns all of the
* regex matches in the text into clickable links. This is particularly
* useful for matching things like email addresses, web urls, etc. and making
* them actionable.
*
* Alone with the pattern that is to be matched, a url scheme prefix is also
* required. Any pattern match that does not begin with the supplied scheme
* will have the scheme prepended to the matched text when the clickable url
* is created. For instance, if you are matching web urls you would supply
* the scheme <code>http://</code>. If the pattern matches example.com, which
* does not have a url scheme prefix, the supplied scheme will be prepended to
* create <code>http://example.com</code> when the clickable url link is
* created.
*/
public class Linkify {
/**
* Bit field indicating that web URLs should be matched in methods that
* take an options mask
*/
public static final int WEB_URLS = 0x01;
/**
* Bit field indicating that email addresses should be matched in methods
* that take an options mask
*/
public static final int EMAIL_ADDRESSES = 0x02;
/**
* Bit field indicating that phone numbers should be matched in methods that
* take an options mask
*/
public static final int PHONE_NUMBERS = 0x04;
/**
* Bit field indicating that street addresses should be matched in methods that
* take an options mask
*/
public static final int MAP_ADDRESSES = 0x08;
/**
* Bit mask indicating that all available patterns should be matched in
* methods that take an options mask
*/
public static final int ALL = WEB_URLS | EMAIL_ADDRESSES | PHONE_NUMBERS | MAP_ADDRESSES;
/**
* Don't treat anything with fewer than this many digits as a
* phone number.
*/
private static final int PHONE_NUMBER_MINIMUM_DIGITS = 5;
/**
* Filters out web URL matches that occur after an at-sign (@). This is
* to prevent turning the domain name in an email address into a web link.
*/
public static final MatchFilter sUrlMatchFilter = new MatchFilter() {
public final boolean acceptMatch(CharSequence s, int start, int end) {
if (start == 0) {
return true;
}
if (s.charAt(start - 1) == '@') {
return false;
}
return true;
}
};
/**
* Filters out URL matches that don't have enough digits to be a
* phone number.
*/
public static final MatchFilter sPhoneNumberMatchFilter = new MatchFilter() {
public final boolean acceptMatch(CharSequence s, int start, int end) {
int digitCount = 0;
for (int i = start; i < end; i++) {
if (Character.isDigit(s.charAt(i))) {
digitCount++;
if (digitCount >= PHONE_NUMBER_MINIMUM_DIGITS) {
return true;
}
}
}
return false;
}
};
/**
* Transforms matched phone number text into something suitable
* to be used in a tel: URL. It does this by removing everything
* but the digits and plus signs. For instance:
* &apos;+1 (919) 555-1212&apos;
* becomes &apos;+19195551212&apos;
*/
public static final TransformFilter sPhoneNumberTransformFilter = new TransformFilter() {
public final String transformUrl(final Matcher match, String url) {
return Patterns.digitsAndPlusOnly(match);
}
};
/**
* MatchFilter enables client code to have more control over
* what is allowed to match and become a link, and what is not.
*
* For example: when matching web urls you would like things like
* http://www.example.com to match, as well as just example.com itelf.
* However, you would not want to match against the domain in
* support@example.com. So, when matching against a web url pattern you
* might also include a MatchFilter that disallows the match if it is
* immediately preceded by an at-sign (@).
*/
public interface MatchFilter {
/**
* Examines the character span matched by the pattern and determines
* if the match should be turned into an actionable link.
*
* @param s The body of text against which the pattern
* was matched
* @param start The index of the first character in s that was
* matched by the pattern - inclusive
* @param end The index of the last character in s that was
* matched - exclusive
*
* @return Whether this match should be turned into a link
*/
boolean acceptMatch(CharSequence s, int start, int end);
}
/**
* TransformFilter enables client code to have more control over
* how matched patterns are represented as URLs.
*
* For example: when converting a phone number such as (919) 555-1212
* into a tel: URL the parentheses, white space, and hyphen need to be
* removed to produce tel:9195551212.
*/
public interface TransformFilter {
/**
* Examines the matched text and either passes it through or uses the
* data in the Matcher state to produce a replacement.
*
* @param match The regex matcher state that found this URL text
* @param url The text that was matched
*
* @return The transformed form of the URL
*/
String transformUrl(final Matcher match, String url);
}
/**
* Scans the text of the provided Spannable and turns all occurrences
* of the link types indicated in the mask into clickable links.
* If the mask is nonzero, it also removes any existing URLSpans
* attached to the Spannable, to avoid problems if you call it
* repeatedly on the same text.
*/
public static final boolean addLinks(Spannable text, int mask) {
if (mask == 0) {
return false;
}
URLSpan[] old = text.getSpans(0, text.length(), URLSpan.class);
for (int i = old.length - 1; i >= 0; i--) {
text.removeSpan(old[i]);
}
ArrayList<LinkSpec> links = new ArrayList<LinkSpec>();
if ((mask & WEB_URLS) != 0) {
gatherLinks(links, text, Patterns.WEB_URL,
new String[] { "http://", "https://", "rtsp://" },
sUrlMatchFilter, null);
}
if ((mask & EMAIL_ADDRESSES) != 0) {
gatherLinks(links, text, Patterns.EMAIL_ADDRESS,
new String[] { "mailto:" },
null, null);
}
if ((mask & PHONE_NUMBERS) != 0) {
gatherLinks(links, text, Patterns.PHONE,
new String[] { "tel:" },
sPhoneNumberMatchFilter, sPhoneNumberTransformFilter);
}
if ((mask & MAP_ADDRESSES) != 0) {
gatherMapLinks(links, text);
}
pruneOverlaps(links);
if (links.size() == 0) {
return false;
}
for (LinkSpec link: links) {
applyLink(link.url, link.start, link.end, text);
}
return true;
}
/**
* Scans the text of the provided TextView and turns all occurrences of
* the link types indicated in the mask into clickable links. If matches
* are found the movement method for the TextView is set to
* LinkMovementMethod.
*/
public static final boolean addLinks(TextView text, int mask) {
if (mask == 0) {
return false;
}
CharSequence t = text.getText();
if (t instanceof Spannable) {
if (addLinks((Spannable) t, mask)) {
addLinkMovementMethod(text);
return true;
}
return false;
} else {
SpannableString s = SpannableString.valueOf(t);
if (addLinks(s, mask)) {
addLinkMovementMethod(text);
text.setText(s);
return true;
}
return false;
}
}
private static final void addLinkMovementMethod(TextView t) {
MovementMethod m = t.getMovementMethod();
if ((m == null) || !(m instanceof LinkMovementMethod)) {
if (t.getLinksClickable()) {
t.setMovementMethod(LinkMovementMethod.getInstance());
}
}
}
/**
* Applies a regex to the text of a TextView turning the matches into
* links. If links are found then UrlSpans are applied to the link
* text match areas, and the movement method for the text is changed
* to LinkMovementMethod.
*
* @param text TextView whose text is to be marked-up with links
* @param pattern Regex pattern to be used for finding links
* @param scheme Url scheme string (eg <code>http://</code> to be
* prepended to the url of links that do not have
* a scheme specified in the link text
*/
public static final void addLinks(TextView text, Pattern pattern, String scheme) {
addLinks(text, pattern, scheme, null, null);
}
/**
* Applies a regex to the text of a TextView turning the matches into
* links. If links are found then UrlSpans are applied to the link
* text match areas, and the movement method for the text is changed
* to LinkMovementMethod.
*
* @param text TextView whose text is to be marked-up with links
* @param p Regex pattern to be used for finding links
* @param scheme Url scheme string (eg <code>http://</code> to be
* prepended to the url of links that do not have
* a scheme specified in the link text
* @param matchFilter The filter that is used to allow the client code
* additional control over which pattern matches are
* to be converted into links.
*/
public static final void addLinks(TextView text, Pattern p, String scheme,
MatchFilter matchFilter, TransformFilter transformFilter) {
SpannableString s = SpannableString.valueOf(text.getText());
if (addLinks(s, p, scheme, matchFilter, transformFilter)) {
text.setText(s);
addLinkMovementMethod(text);
}
}
/**
* Applies a regex to a Spannable turning the matches into
* links.
*
* @param text Spannable whose text is to be marked-up with
* links
* @param pattern Regex pattern to be used for finding links
* @param scheme Url scheme string (eg <code>http://</code> to be
* prepended to the url of links that do not have
* a scheme specified in the link text
*/
public static final boolean addLinks(Spannable text, Pattern pattern, String scheme) {
return addLinks(text, pattern, scheme, null, null);
}
/**
* Applies a regex to a Spannable turning the matches into
* links.
*
* @param s Spannable whose text is to be marked-up with
* links
* @param p Regex pattern to be used for finding links
* @param scheme Url scheme string (eg <code>http://</code> to be
* prepended to the url of links that do not have
* a scheme specified in the link text
* @param matchFilter The filter that is used to allow the client code
* additional control over which pattern matches are
* to be converted into links.
*/
public static final boolean addLinks(Spannable s, Pattern p,
String scheme, MatchFilter matchFilter,
TransformFilter transformFilter) {
boolean hasMatches = false;
String prefix = (scheme == null) ? "" : scheme.toLowerCase();
Matcher m = p.matcher(s);
while (m.find()) {
int start = m.start();
int end = m.end();
boolean allowed = true;
if (matchFilter != null) {
allowed = matchFilter.acceptMatch(s, start, end);
}
if (allowed) {
String url = makeUrl(m.group(0), new String[] { prefix },
m, transformFilter);
applyLink(url, start, end, s);
hasMatches = true;
}
}
return hasMatches;
}
private static final void applyLink(String url, int start, int end, Spannable text) {
URLSpan span = new URLSpan(url);
text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
private static final String makeUrl(String url, String[] prefixes,
Matcher m, TransformFilter filter) {
if (filter != null) {
url = filter.transformUrl(m, url);
}
boolean hasPrefix = false;
for (int i = 0; i < prefixes.length; i++) {
if (url.regionMatches(true, 0, prefixes[i], 0,
prefixes[i].length())) {
hasPrefix = true;
// Fix capitalization if necessary
if (!url.regionMatches(false, 0, prefixes[i], 0,
prefixes[i].length())) {
url = prefixes[i] + url.substring(prefixes[i].length());
}
break;
}
}
if (!hasPrefix) {
url = prefixes[0] + url;
}
return url;
}
private static final void gatherLinks(ArrayList<LinkSpec> links,
Spannable s, Pattern pattern, String[] schemes,
MatchFilter matchFilter, TransformFilter transformFilter) {
Matcher m = pattern.matcher(s);
while (m.find()) {
int start = m.start();
int end = m.end();
if (matchFilter == null || matchFilter.acceptMatch(s, start, end)) {
LinkSpec spec = new LinkSpec();
String url = makeUrl(m.group(0), schemes, m, transformFilter);
spec.url = url;
spec.start = start;
spec.end = end;
links.add(spec);
}
}
}
private static final void gatherMapLinks(ArrayList<LinkSpec> links, Spannable s) {
String string = s.toString();
String address;
int base = 0;
while ((address = WebView.findAddress(string)) != null) {
int start = string.indexOf(address);
if (start < 0) {
break;
}
LinkSpec spec = new LinkSpec();
int length = address.length();
int end = start + length;
spec.start = base + start;
spec.end = base + end;
string = string.substring(end);
base += end;
String encodedAddress = null;
try {
encodedAddress = URLEncoder.encode(address,"UTF-8");
} catch (UnsupportedEncodingException e) {
continue;
}
spec.url = "geo:0,0?q=" + encodedAddress;
links.add(spec);
}
}
private static final void pruneOverlaps(ArrayList<LinkSpec> links) {
Comparator<LinkSpec> c = new Comparator<LinkSpec>() {
public final int compare(LinkSpec a, LinkSpec b) {
if (a.start < b.start) {
return -1;
}
if (a.start > b.start) {
return 1;
}
if (a.end < b.end) {
return 1;
}
if (a.end > b.end) {
return -1;
}
return 0;
}
public final boolean equals(Object o) {
return false;
}
};
Collections.sort(links, c);
int len = links.size();
int i = 0;
while (i < len - 1) {
LinkSpec a = links.get(i);
LinkSpec b = links.get(i + 1);
int remove = -1;
if ((a.start <= b.start) && (a.end > b.start)) {
if (b.end <= a.end) {
remove = i + 1;
} else if ((a.end - a.start) > (b.end - b.start)) {
remove = i + 1;
} else if ((a.end - a.start) < (b.end - b.start)) {
remove = i;
}
if (remove != -1) {
links.remove(remove);
len--;
continue;
}
}
i++;
}
}
}
class LinkSpec {
String url;
int start;
int end;
}

View File

@ -0,0 +1,198 @@
/*
* Copyright (C) 2008 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 android.text.util;
/**
* This class stores an RFC 822-like name, address, and comment,
* and provides methods to convert them to quoted strings.
*/
public class Rfc822Token {
private String mName, mAddress, mComment;
/**
* Creates a new Rfc822Token with the specified name, address,
* and comment.
*/
public Rfc822Token(String name, String address, String comment) {
mName = name;
mAddress = address;
mComment = comment;
}
/**
* Returns the name part.
*/
public String getName() {
return mName;
}
/**
* Returns the address part.
*/
public String getAddress() {
return mAddress;
}
/**
* Returns the comment part.
*/
public String getComment() {
return mComment;
}
/**
* Changes the name to the specified name.
*/
public void setName(String name) {
mName = name;
}
/**
* Changes the address to the specified address.
*/
public void setAddress(String address) {
mAddress = address;
}
/**
* Changes the comment to the specified comment.
*/
public void setComment(String comment) {
mComment = comment;
}
/**
* Returns the name (with quoting added if necessary),
* the comment (in parentheses), and the address (in angle brackets).
* This should be suitable for inclusion in an RFC 822 address list.
*/
public String toString() {
StringBuilder sb = new StringBuilder();
if (mName != null && mName.length() != 0) {
sb.append(quoteNameIfNecessary(mName));
sb.append(' ');
}
if (mComment != null && mComment.length() != 0) {
sb.append('(');
sb.append(quoteComment(mComment));
sb.append(") ");
}
if (mAddress != null && mAddress.length() != 0) {
sb.append('<');
sb.append(mAddress);
sb.append('>');
}
return sb.toString();
}
/**
* Returns the name, conservatively quoting it if there are any
* characters that are likely to cause trouble outside of a
* quoted string, or returning it literally if it seems safe.
*/
public static String quoteNameIfNecessary(String name) {
int len = name.length();
for (int i = 0; i < len; i++) {
char c = name.charAt(i);
if (! ((c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c == ' ') ||
(c >= '0' && c <= '9'))) {
return '"' + quoteName(name) + '"';
}
}
return name;
}
/**
* Returns the name, with internal backslashes and quotation marks
* preceded by backslashes. The outer quote marks themselves are not
* added by this method.
*/
public static String quoteName(String name) {
StringBuilder sb = new StringBuilder();
int len = name.length();
for (int i = 0; i < len; i++) {
char c = name.charAt(i);
if (c == '\\' || c == '"') {
sb.append('\\');
}
sb.append(c);
}
return sb.toString();
}
/**
* Returns the comment, with internal backslashes and parentheses
* preceded by backslashes. The outer parentheses themselves are
* not added by this method.
*/
public static String quoteComment(String comment) {
int len = comment.length();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++) {
char c = comment.charAt(i);
if (c == '(' || c == ')' || c == '\\') {
sb.append('\\');
}
sb.append(c);
}
return sb.toString();
}
public int hashCode() {
int result = 17;
if (mName != null) result = 31 * result + mName.hashCode();
if (mAddress != null) result = 31 * result + mAddress.hashCode();
if (mComment != null) result = 31 * result + mComment.hashCode();
return result;
}
private static boolean stringEquals(String a, String b) {
if (a == null) {
return (b == null);
} else {
return (a.equals(b));
}
}
public boolean equals(Object o) {
if (!(o instanceof Rfc822Token)) {
return false;
}
Rfc822Token other = (Rfc822Token) o;
return (stringEquals(mName, other.mName) &&
stringEquals(mAddress, other.mAddress) &&
stringEquals(mComment, other.mComment));
}
}

View File

@ -0,0 +1,314 @@
/*
* Copyright (C) 2008 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 android.text.util;
import android.widget.MultiAutoCompleteTextView;
import java.util.ArrayList;
import java.util.Collection;
/**
* This class works as a Tokenizer for MultiAutoCompleteTextView for
* address list fields, and also provides a method for converting
* a string of addresses (such as might be typed into such a field)
* into a series of Rfc822Tokens.
*/
public class Rfc822Tokenizer implements MultiAutoCompleteTextView.Tokenizer {
/**
* This constructor will try to take a string like
* "Foo Bar (something) &lt;foo\@google.com&gt;,
* blah\@google.com (something)"
* and convert it into one or more Rfc822Tokens, output into the supplied
* collection.
*
* It does *not* decode MIME encoded-words; charset conversion
* must already have taken place if necessary.
* It will try to be tolerant of broken syntax instead of
* returning an error.
*
*/
public static void tokenize(CharSequence text, Collection<Rfc822Token> out) {
StringBuilder name = new StringBuilder();
StringBuilder address = new StringBuilder();
StringBuilder comment = new StringBuilder();
int i = 0;
int cursor = text.length();
while (i < cursor) {
char c = text.charAt(i);
if (c == ',' || c == ';') {
i++;
while (i < cursor && text.charAt(i) == ' ') {
i++;
}
crunch(name);
if (address.length() > 0) {
out.add(new Rfc822Token(name.toString(),
address.toString(),
comment.toString()));
} else if (name.length() > 0) {
out.add(new Rfc822Token(null,
name.toString(),
comment.toString()));
}
name.setLength(0);
address.setLength(0);
comment.setLength(0);
} else if (c == '"') {
i++;
while (i < cursor) {
c = text.charAt(i);
if (c == '"') {
i++;
break;
} else if (c == '\\') {
if (i + 1 < cursor) {
name.append(text.charAt(i + 1));
}
i += 2;
} else {
name.append(c);
i++;
}
}
} else if (c == '(') {
int level = 1;
i++;
while (i < cursor && level > 0) {
c = text.charAt(i);
if (c == ')') {
if (level > 1) {
comment.append(c);
}
level--;
i++;
} else if (c == '(') {
comment.append(c);
level++;
i++;
} else if (c == '\\') {
if (i + 1 < cursor) {
comment.append(text.charAt(i + 1));
}
i += 2;
} else {
comment.append(c);
i++;
}
}
} else if (c == '<') {
i++;
while (i < cursor) {
c = text.charAt(i);
if (c == '>') {
i++;
break;
} else {
address.append(c);
i++;
}
}
} else if (c == ' ') {
name.append('\0');
i++;
} else {
name.append(c);
i++;
}
}
crunch(name);
if (address.length() > 0) {
out.add(new Rfc822Token(name.toString(),
address.toString(),
comment.toString()));
} else if (name.length() > 0) {
out.add(new Rfc822Token(null,
name.toString(),
comment.toString()));
}
}
/**
* This method will try to take a string like
* "Foo Bar (something) &lt;foo\@google.com&gt;,
* blah\@google.com (something)"
* and convert it into one or more Rfc822Tokens.
* It does *not* decode MIME encoded-words; charset conversion
* must already have taken place if necessary.
* It will try to be tolerant of broken syntax instead of
* returning an error.
*/
public static Rfc822Token[] tokenize(CharSequence text) {
ArrayList<Rfc822Token> out = new ArrayList<Rfc822Token>();
tokenize(text, out);
return out.toArray(new Rfc822Token[out.size()]);
}
private static void crunch(StringBuilder sb) {
int i = 0;
int len = sb.length();
while (i < len) {
char c = sb.charAt(i);
if (c == '\0') {
if (i == 0 || i == len - 1 ||
sb.charAt(i - 1) == ' ' ||
sb.charAt(i - 1) == '\0' ||
sb.charAt(i + 1) == ' ' ||
sb.charAt(i + 1) == '\0') {
sb.deleteCharAt(i);
len--;
} else {
i++;
}
} else {
i++;
}
}
for (i = 0; i < len; i++) {
if (sb.charAt(i) == '\0') {
sb.setCharAt(i, ' ');
}
}
}
/**
* {@inheritDoc}
*/
public int findTokenStart(CharSequence text, int cursor) {
/*
* It's hard to search backward, so search forward until
* we reach the cursor.
*/
int best = 0;
int i = 0;
while (i < cursor) {
i = findTokenEnd(text, i);
if (i < cursor) {
i++; // Skip terminating punctuation
while (i < cursor && text.charAt(i) == ' ') {
i++;
}
if (i < cursor) {
best = i;
}
}
}
return best;
}
/**
* {@inheritDoc}
*/
public int findTokenEnd(CharSequence text, int cursor) {
int len = text.length();
int i = cursor;
while (i < len) {
char c = text.charAt(i);
if (c == ',' || c == ';') {
return i;
} else if (c == '"') {
i++;
while (i < len) {
c = text.charAt(i);
if (c == '"') {
i++;
break;
} else if (c == '\\') {
i += 2;
} else {
i++;
}
}
} else if (c == '(') {
int level = 1;
i++;
while (i < len && level > 0) {
c = text.charAt(i);
if (c == ')') {
level--;
i++;
} else if (c == '(') {
level++;
i++;
} else if (c == '\\') {
i += 2;
} else {
i++;
}
}
} else if (c == '<') {
i++;
while (i < len) {
c = text.charAt(i);
if (c == '>') {
i++;
break;
} else {
i++;
}
}
} else {
i++;
}
}
return i;
}
/**
* Terminates the specified address with a comma and space.
* This assumes that the specified text already has valid syntax.
* The Adapter subclass's convertToString() method must make that
* guarantee.
*/
public CharSequence terminateToken(CharSequence text) {
return text + ", ";
}
}

View File

@ -0,0 +1,6 @@
<HTML>
<BODY>
Utilities for converting identifiable text strings into clickable links
and creating RFC 822-type message (SMTP) tokens.
</BODY>
</HTML>