Skip to content

Commit

Permalink
Merge pull request #7 from google/talkback6
Browse files Browse the repository at this point in the history
TalkBack 6.1 release
  • Loading branch information
zork authored Feb 1, 2018
2 parents 12bdf20 + 24c2d63 commit e69d473
Show file tree
Hide file tree
Showing 1,814 changed files with 120,441 additions and 134,035 deletions.
7 changes: 7 additions & 0 deletions accessibility_utils/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* For building open-source release of accessibility services. */

apply plugin: 'com.android.library'

android {
compileSdkVersion "android-27"
}
5 changes: 5 additions & 0 deletions accessibility_utils/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.libraries.accessibility.utils">
</manifest>
50 changes: 50 additions & 0 deletions accessibility_utils/src/main/java/QualifiedComponentName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.google.android.libraries.accessibility.utils;

import android.content.ComponentName;

import java.util.regex.Pattern;

/**
* A class to create {@link ComponentName}s that always hold the fully qualified class name.
*/
public class QualifiedComponentName {

private static final Pattern QUALIFIED_CLASS_PATTERN = Pattern.compile(
// starts with 1+ lowercase alphanumerics starting with a letter ending with a '.'
"^([a-z][a-z0-9_]*\\.)+"
+ "[A-Z][a-zA-Z0-9_]*$"); // ends with alphanumeric beginning with a capital letter

private static final Pattern RELATIVE_CLASS_PATTERN = Pattern.compile(
// starts with '.' then 0+ lowercase alphanumerics starting with a letter ending with a '.'
"^\\.([a-z][a-z0-9_]*\\.)*"
+ "[A-Z][a-zA-Z0-9_]*$"); // ends with alphanumeric beginning with a capital letter

private static final Pattern SIMPLE_CLASS_PATTERN = Pattern.compile(
"^[A-Z][a-zA-Z0-9_]*$"); // ends with alphanumeric beginning with a capital letter

/**
* Returns a {@link ComponentName} corresponding to the same component as the given
* {@link ComponentName}, but with a fully qualified class name. If the class or package
* name are empty or do not follow java naming scheme, this will return null.
*/
public static ComponentName fromComponentName(ComponentName componentName) {
String packageName = componentName.getPackageName();
String className = componentName.getClassName();
if (QUALIFIED_CLASS_PATTERN.matcher(className).matches()) {
return componentName;
} else if (RELATIVE_CLASS_PATTERN.matcher(className).matches()) {
return new ComponentName(packageName, packageName + className);
} else if (SIMPLE_CLASS_PATTERN.matcher(className).matches()) {
return new ComponentName(packageName, packageName + "." + className);
}
return null;
}

public static ComponentName unflattenFromString(String str) {
ComponentName nonqualifiedComponentName = ComponentName.unflattenFromString(str);
if (nonqualifiedComponentName == null) {
return null;
}
return fromComponentName(nonqualifiedComponentName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.google.android.libraries.accessibility.utils;

import android.os.Handler;
import android.os.Looper;

import android.util.Log;

import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.CountDownLatch;

/**
* An {@link UncaughtExceptionHandler} for use in Android services to allow for removal of UI
* upon an uncaught exception from any thread. This should be set before any UI is displayed.
*/
public abstract class ServiceUncaughtExceptionHandler implements UncaughtExceptionHandler {

private static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER =
Thread.getDefaultUncaughtExceptionHandler();
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final CountDownLatch mUncaughtExceptionLatch = new CountDownLatch(1);
private final String mTag;

/**
* Create a {@code ServiceUncaughtExceptionHandler} with the default logcat tag.
*/
public ServiceUncaughtExceptionHandler() {
this(null);
}

/**
* Create a {@code ServiceUncaughtExceptionHandler} with a custom logcat tag.
*
* @param tag The tag to use to log the uncaught exception to logcat. If {@code null}, this class'
* simple name will be used.
*/
public ServiceUncaughtExceptionHandler(String tag) {
mTag = (tag == null)
? ServiceUncaughtExceptionHandler.class.getSimpleName()
: tag;
}

/**
* Logs the caught {@link Throwable} to logcat, calls {@link #shutdown()} on the main thread, and
* then re-throws the exception to the previous default {@link UncaughtExceptionHandler}.
*/
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
// Not all Android versions will print the stack trace automatically
Log.e(mTag, "Uncaught exception thrown from thread: " + thread.getName(), throwable);

if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
// Can't post to the main handler, as that looper is no longer looping due to the exception
try {
shutdown();
} catch (Throwable t) {
// If we don't catch all here, we will just infinitely recurse uncaughtException()
}
} else {
mHandler.post(new Runnable() {
@Override
public void run() {
try {
shutdown();
} catch (Throwable t) {
// If we don't catch all here, we will just infinitely recurse uncaughtException()
} finally {
mUncaughtExceptionLatch.countDown();
}
}
});
while (mUncaughtExceptionLatch.getCount() > 0) {
try {
mUncaughtExceptionLatch.await();
} catch (InterruptedException e) {
// Spurious wakeup, loop and await() again
}
}
}

if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) {
// re-throw the exception so that the android system knows the app has crashed
DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
}
}

/**
* This is called on the main thread before rethrowing the uncaught exception. All UI should be
* removed in this method.
*/
public abstract void shutdown();

}
221 changes: 221 additions & 0 deletions accessibility_utils/src/main/java/StringUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
package com.google.android.libraries.accessibility.utils;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Library for string manipulations. Contains regular expressions and common operation helper
* methods.
*/
public final class StringUtils {

/**
* A regex pattern to group a single section of a string based on camel case. This will need to be
* used in a loop to extract all sections of the string. This will work for Unicode character
* blocks. Here are some examples:
*
* <ul>
* <li>"thisIsCamelCase" => ["this", "Is", "Camel", "Case"]
* <li>"30daysInJune" => ["30", "days", "In", "June"]
* <li>"25DaysSinceJune10!!!" => ["25", "Days", "Since", "June", "10", "!!!"]
* </ul>
*
* <p>
* <br>
* This regex has four categories of characters: upper case letters, lower case letters, numbers,
* and symbols (including punctuation). All of these categories are grouped separately from one
* another with the exception of upper case letters. A group cannot have more than one upper case
* letter and can contain any number of lower case letters.
*
* <p>
* <br>
* Here are the sections of the regex:
*
* <br>
* 1. "\\p{Ll}+" -> One or more lower case letters
*
* <br>
* 2. "\\p{N}+" -> One or more numbers
*
* <br>
* 3. "[\\p{S}\\p{P}]+" -> One or more symbols/punctuations
*
* <br>
* 4. "\\p{Lu}\\p{Ll}*" -> Exactly one upper case letter followed by zero or more lower case
* letters
*/
public static final Pattern CAMEL_CASE_PATTERN =
Pattern.compile("(\\p{Ll}+|\\p{N}+|[\\p{S}\\p{P}]+|\\p{Lu}\\p{Ll}*)");

/**
* A regex pattern to match a single whitespace or invisible separator. Supports Unicode character
* blocks.
*/
private static final Pattern WHITESPACE_BLOCK_PATTERN = Pattern.compile("\\p{Z}+");

/**
* A regex pattern to match a group of non-whitespace characters. Supports Unicode blocks.
*/
private static final Pattern NOT_WHITESPACE_GROUP_PATTERN = Pattern.compile("(\\P{Z}+)");

private static final String SPACE_STRING = " ";

private StringUtils() {}

/**
* Splits the input {@link String} based on whitespace and camel case. The resulting split
* substrings will not contain any whitespace and will also maintain the original capitalization.
* See {@value #CAMEL_CASE_PATTERN} for specific rules on camel case splitting and
* {@link #splitOnSpace} for specific rules on whitespace splitting.
*
* @param str The {@link String} to be split
* @return The substrings based off camel case and whitespace. If the input is empty (no
* non-whitespace characters) or {@code null} then the result will be an immutable empty
* list
*/
public static List<String> splitOnCamelCase(String str) {
if (isEmpty(str)) {
return Collections.emptyList();
}

List<String> result = new LinkedList<>();
for (final String word : splitOnSpace(str)) {
final Matcher camelCaseMatcher = CAMEL_CASE_PATTERN.matcher(word);
if (camelCaseMatcher.find()) {
do {
result.add(camelCaseMatcher.group());
} while (camelCaseMatcher.find());
} else {
result.add(word);
}
}
return result;
}

/**
* Equivalent to {@code splitOnSpace(str, 0)}
*/
public static List<String> splitOnSpace(String str) {
return splitOnSpace(str, 0);
}

/**
* Splits the input {@link String} based on whitespace. The resulting split substrings will not
* contain any whitespace and will also maintain the original capitalization.
*
* @param str The {@link String} to be split
* @param limit Determines the maximum number of entries in the resulting array, and the treatment
* of trailing empty strings
* <ul>
* <li>For n > 0, the resulting {@link List} contains at most n entries. If this is fewer
* than the number of matches, the final entry will contain all remaining input
* <li>For n <= 0, the length of the resulting {@link List} is exactly the number of words
* in the input
* </ul>
* @return The substrings based off whitespace. If the input is empty (no non-whitespace
* characters) or {@code null} then the result will be an immutable empty list
*/
public static List<String> splitOnSpace(String str, int limit) {
if (str == null) {
return Collections.emptyList();
}
List<String> words = new LinkedList<>();
Matcher matcher = NOT_WHITESPACE_GROUP_PATTERN.matcher(str);
int count = 0;
while (matcher.find()) {
words.add(matcher.group());
if (++count == limit) {
break;
}
}
return words;
}

/**
* Checks the input {@link String} to see if it contains any non-whitespace characters.
*
* @param str The {@link String} to be checked
* @return {@code true} if the input is null or consists of zero or more whitespace characters;
* {@code false} otherwise
*/
public static boolean isEmpty(CharSequence cs) {
if ((cs == null) || (cs.length() == 0)) {
return true;
}
for (int i = 0; i < cs.length(); i++) {
if (!Character.isWhitespace(cs.charAt(i))) {
return false;
}
}
return true;
}

/**
* Collapses multiple spaces into a single space and removes leading/trailing whitespace.
*
* @param str The input {@link String} to normalize spaces
* @return The input {@link String} with unnecessary whitespace removed or {@code null} if the
* input is {@code null}
*/
public static String normalizeSpaces(CharSequence str) {
return (str == null)
? null
: WHITESPACE_BLOCK_PATTERN.matcher(str).replaceAll(SPACE_STRING).trim();
}

/**
* Capitalize the first letter of a string, in the specified locale. Supports Unicode.
*
* @param str The input {@link String} for which to capitalize the first letter
* @param locale The {@link Locale} to use when capitalizing
* @return The input {@link String} with the first letter capitalized
*/
public static String capitalizeFirstLetter(String str, Locale locale) {
if (isEmpty(str)) {
return str;
}
return Character.isUpperCase(str.charAt(0))
? str
: str.substring(0, 1).toUpperCase(locale) + str.substring(1);
}

/**
* Capitalize the first letter of a string, using the default locale. Supports Unicode.
*
* @param str The input {@link String} for which to capitalize the first letter
* @return The input {@link String} with the first letter capitalized
*/
public static String capitalizeFirstLetter(String str) {
return capitalizeFirstLetter(str, Locale.getDefault());
}

/**
* Lowercase the first letter of a string, in the specified locale. Supports Unicode.
*
* @param str The input {@link String} for which to lowercase the first letter
* @param locale The {@link Locale} to use when lowercasing
* @return The input {@link String} with the first letter lowercased
*/
public static String lowercaseFirstLetter(String str, Locale locale) {
if (isEmpty(str)) {
return str;
}
return Character.isLowerCase(str.charAt(0))
? str
: str.substring(0, 1).toLowerCase(locale) + str.substring(1);
}

/**
* Lowercase the first letter of a string, using the default locale. Supports Unicode.
*
* @param str The input {@link String} for which to lowercase the first letter
* @return The input {@link String} with the first letter lowercased
*/
public static String lowercaseFirstLetter(String str) {
return lowercaseFirstLetter(str, Locale.getDefault());
}
}
Loading

0 comments on commit e69d473

Please sign in to comment.