Merge "Fine-tune the public APIs of allow rich content in Accessibility Settings for security." into rvc-dev

This commit is contained in:
PETER LIANG
2020-02-26 02:51:57 +00:00
committed by Android (Google) Code Review
2 changed files with 245 additions and 26 deletions

View File

@@ -22,6 +22,7 @@ import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
@@ -61,6 +62,7 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
/**
* This class describes an {@link AccessibilityService}. The system notifies an
@@ -552,6 +554,13 @@ public class AccessibilityServiceInfo implements Parcelable {
*/
private int mHtmlDescriptionRes;
// Used for html description of accessibility service. The <img> src tag must follow the
// prefix rule. e.g. <img src="R.drawable.fileName"/>
private static final String IMG_PREFIX = "R.drawable.";
private static final String ANCHOR_TAG = "a";
private static final List<String> UNSUPPORTED_TAG_LIST = new ArrayList<>(
Collections.singletonList(ANCHOR_TAG));
/**
* Creates a new instance.
*/
@@ -782,12 +791,10 @@ public class AccessibilityServiceInfo implements Parcelable {
}
/**
* The animated image resource id.
* <p>
* <strong>Statically set from
* {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
* </p>
* Gets the animated image resource id.
*
* @return The animated image resource id.
*
* @hide
*/
public int getAnimatedImageRes() {
@@ -797,10 +804,12 @@ public class AccessibilityServiceInfo implements Parcelable {
/**
* The animated image drawable.
* <p>
* Image can not exceed the screen size.
* <strong>Statically set from
* {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
* </p>
* @return The animated image drawable.
* @return The animated image drawable, or null if the resource is invalid or the image
* exceed the screen size.
*/
@Nullable
public Drawable loadAnimatedImage(@NonNull Context context) {
@@ -808,11 +817,8 @@ public class AccessibilityServiceInfo implements Parcelable {
return null;
}
final PackageManager packageManager = context.getPackageManager();
final String packageName = mComponentName.getPackageName();
final ApplicationInfo applicationInfo = mResolveInfo.serviceInfo.applicationInfo;
return packageManager.getDrawable(packageName, mAnimatedImageRes, applicationInfo);
return loadSafeAnimatedImage(context, mResolveInfo.serviceInfo.applicationInfo,
mAnimatedImageRes);
}
/**
@@ -924,16 +930,17 @@ public class AccessibilityServiceInfo implements Parcelable {
}
/**
* The localized html description of the accessibility service.
* The localized and restricted html description of the accessibility service.
* <p>
* Filters the <img> tag which do not meet the custom specification and the <a> tag.
* <strong>Statically set from
* {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
* </p>
* @return The localized html description.
* @return The localized and restricted html description.
*/
@Nullable
public String loadHtmlDescription(@NonNull PackageManager packageManager) {
if (mHtmlDescriptionRes == 0) {
if (mHtmlDescriptionRes == /* invalid */ 0) {
return null;
}
@@ -941,7 +948,7 @@ public class AccessibilityServiceInfo implements Parcelable {
final CharSequence htmlDescription = packageManager.getText(serviceInfo.packageName,
mHtmlDescriptionRes, serviceInfo.applicationInfo);
if (htmlDescription != null) {
return htmlDescription.toString().trim();
return getFilteredHtmlText(htmlDescription.toString().trim());
}
return null;
}
@@ -1414,4 +1421,103 @@ public class AccessibilityServiceInfo implements Parcelable {
return new AccessibilityServiceInfo[size];
}
};
/**
* Gets the filtered html string for
* {@link android.accessibilityservice.AccessibilityServiceInfo} and
* {@link android.accessibilityservice.AccessibilityShortcutInfo}. It filters
* the <img> tag which do not meet the custom specification and the <a> tag.
*
* @param text the target text is html format.
* @return the filtered html string.
*
* @hide
*/
public static @NonNull String getFilteredHtmlText(@NonNull String text) {
final String replacementStart = "<invalidtag ";
final String replacementEnd = "</invalidtag>";
for (String tag : UNSUPPORTED_TAG_LIST) {
final String regexStart = "(?i)<" + tag + "(\\s+|>)";
final String regexEnd = "(?i)</" + tag + "\\s*>";
text = Pattern.compile(regexStart).matcher(text).replaceAll(replacementStart);
text = Pattern.compile(regexEnd).matcher(text).replaceAll(replacementEnd);
}
final String regexInvalidImgTag = "(?i)<img\\s+(?!src\\s*=\\s*\"(?-i)" + IMG_PREFIX + ")";
text = Pattern.compile(regexInvalidImgTag).matcher(text).replaceAll(
replacementStart);
return text;
}
/**
* Loads the animated image for
* {@link android.accessibilityservice.AccessibilityServiceInfo} and
* {@link android.accessibilityservice.AccessibilityShortcutInfo}. It checks the resource
* whether to exceed the screen size.
*
* @param context the current context.
* @param applicationInfo the current application.
* @param resId the animated image resource id.
* @return the animated image which is safe.
*
* @hide
*/
@Nullable
public static Drawable loadSafeAnimatedImage(@NonNull Context context,
@NonNull ApplicationInfo applicationInfo, @StringRes int resId) {
if (resId == /* invalid */ 0) {
return null;
}
final PackageManager packageManager = context.getPackageManager();
final String packageName = applicationInfo.packageName;
final Drawable bannerDrawable = packageManager.getDrawable(packageName, resId,
applicationInfo);
if (bannerDrawable == null) {
return null;
}
final boolean isImageWidthOverScreenLength =
bannerDrawable.getIntrinsicWidth() > getScreenWidthPixels(context);
final boolean isImageHeightOverScreenLength =
bannerDrawable.getIntrinsicHeight() > getScreenHeightPixels(context);
return (isImageWidthOverScreenLength || isImageHeightOverScreenLength)
? null
: bannerDrawable;
}
/**
* Gets the width of the screen.
*
* @param context the current context.
* @return the width of the screen in term of pixels.
*
* @hide
*/
private static int getScreenWidthPixels(@NonNull Context context) {
final Resources resources = context.getResources();
final int screenWidthDp = resources.getConfiguration().screenWidthDp;
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenWidthDp,
resources.getDisplayMetrics()));
}
/**
* Gets the height of the screen.
*
* @param context the current context.
* @return the height of the screen in term of pixels.
*
* @hide
*/
private static int getScreenHeightPixels(@NonNull Context context) {
final Resources resources = context.getResources();
final int screenHeightDp = resources.getConfiguration().screenHeightDp;
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenHeightDp,
resources.getDisplayMetrics()));
}
}

View File

@@ -18,6 +18,7 @@ package android.accessibilityservice;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -28,13 +29,19 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
/**
* Activities of interest to users with accessibility needs may request to be targets of the
@@ -87,6 +94,13 @@ public final class AccessibilityShortcutInfo {
*/
private final int mHtmlDescriptionRes;
// Used for html description of accessibility service. The <img> src tag must follow the
// prefix rule. e.g. <img src="R.drawable.fileName"/>
private static final String IMG_PREFIX = "R.drawable.";
private static final String ANCHOR_TAG = "a";
private static final List<String> UNSUPPORTED_TAG_LIST = new ArrayList<>(
Collections.singletonList(ANCHOR_TAG));
/**
* Creates a new instance.
*
@@ -134,7 +148,7 @@ public final class AccessibilityShortcutInfo {
// Gets animated image
mAnimatedImageRes = asAttributes.getResourceId(
com.android.internal.R.styleable
.AccessibilityShortcutTarget_animatedImageDrawable, 0);
.AccessibilityShortcutTarget_animatedImageDrawable, /* defValue= */ 0);
// Gets html description
mHtmlDescriptionRes = asAttributes.getResourceId(
com.android.internal.R.styleable.AccessibilityShortcutTarget_htmlDescription,
@@ -192,7 +206,7 @@ public final class AccessibilityShortcutInfo {
}
/**
* The animated image resource id of the accessibility shortcut target.
* Gets the animated image resource id.
*
* @return The animated image resource id.
*
@@ -205,7 +219,8 @@ public final class AccessibilityShortcutInfo {
/**
* The animated image drawable of the accessibility shortcut target.
*
* @return The animated image drawable.
* @return The animated image drawable, or null if the resource is invalid or the image
* exceed the screen size.
*/
@Nullable
public Drawable loadAnimatedImage(@NonNull Context context) {
@@ -213,21 +228,20 @@ public final class AccessibilityShortcutInfo {
return null;
}
final PackageManager packageManager = context.getPackageManager();
final String packageName = mComponentName.getPackageName();
final ApplicationInfo applicationInfo = mActivityInfo.applicationInfo;
return packageManager.getDrawable(packageName, mAnimatedImageRes, applicationInfo);
return loadSafeAnimatedImage(context, mActivityInfo.applicationInfo, mAnimatedImageRes);
}
/**
* The localized html description of the accessibility shortcut target.
* The localized and restricted html description of the accessibility shortcut target.
* It filters the <img> tag which do not meet the custom specification and the <a> tag.
*
* @return The localized html description.
* @return The localized and restricted html description.
*/
@Nullable
public String loadHtmlDescription(@NonNull PackageManager packageManager) {
return loadResourceString(packageManager, mActivityInfo, mHtmlDescriptionRes);
final String htmlDescription = loadResourceString(packageManager, mActivityInfo,
mHtmlDescriptionRes);
return TextUtils.isEmpty(htmlDescription) ? null : getFilteredHtmlText(htmlDescription);
}
/**
@@ -291,4 +305,103 @@ public final class AccessibilityShortcutInfo {
stringBuilder.append("]");
return stringBuilder.toString();
}
/**
* Gets the filtered html string for
* {@link android.accessibilityservice.AccessibilityServiceInfo} and
* {@link android.accessibilityservice.AccessibilityShortcutInfo}. It filters
* the <img> tag which do not meet the custom specification and the <a> tag.
*
* @param text the target text is html format.
* @return the filtered html string.
*
* @hide
*/
public static @NonNull String getFilteredHtmlText(@NonNull String text) {
final String replacementStart = "<invalidtag ";
final String replacementEnd = "</invalidtag>";
for (String tag : UNSUPPORTED_TAG_LIST) {
final String regexStart = "(?i)<" + tag + "(\\s+|>)";
final String regexEnd = "(?i)</" + tag + "\\s*>";
text = Pattern.compile(regexStart).matcher(text).replaceAll(replacementStart);
text = Pattern.compile(regexEnd).matcher(text).replaceAll(replacementEnd);
}
final String regexInvalidImgTag = "(?i)<img\\s+(?!src\\s*=\\s*\"(?-i)" + IMG_PREFIX + ")";
text = Pattern.compile(regexInvalidImgTag).matcher(text).replaceAll(
replacementStart);
return text;
}
/**
* Loads the animated image for
* {@link android.accessibilityservice.AccessibilityServiceInfo} and
* {@link android.accessibilityservice.AccessibilityShortcutInfo}. It checks the resource
* whether to exceed the screen size.
*
* @param context the current context.
* @param applicationInfo the current application.
* @param resId the animated image resource id.
* @return the animated image which is safe.
*
* @hide
*/
@Nullable
public static Drawable loadSafeAnimatedImage(@NonNull Context context,
@NonNull ApplicationInfo applicationInfo, @StringRes int resId) {
if (resId == /* invalid */ 0) {
return null;
}
final PackageManager packageManager = context.getPackageManager();
final String packageName = applicationInfo.packageName;
final Drawable bannerDrawable = packageManager.getDrawable(packageName, resId,
applicationInfo);
if (bannerDrawable == null) {
return null;
}
final boolean isImageWidthOverScreenLength =
bannerDrawable.getIntrinsicWidth() > getScreenWidthPixels(context);
final boolean isImageHeightOverScreenLength =
bannerDrawable.getIntrinsicHeight() > getScreenHeightPixels(context);
return (isImageWidthOverScreenLength || isImageHeightOverScreenLength)
? null
: bannerDrawable;
}
/**
* Gets the width of the screen.
*
* @param context the current context.
* @return the width of the screen in term of pixels.
*
* @hide
*/
private static int getScreenWidthPixels(@NonNull Context context) {
final Resources resources = context.getResources();
final int screenWidthDp = resources.getConfiguration().screenWidthDp;
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenWidthDp,
resources.getDisplayMetrics()));
}
/**
* Gets the height of the screen.
*
* @param context the current context.
* @return the height of the screen in term of pixels.
*
* @hide
*/
private static int getScreenHeightPixels(@NonNull Context context) {
final Resources resources = context.getResources();
final int screenHeightDp = resources.getConfiguration().screenHeightDp;
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenHeightDp,
resources.getDisplayMetrics()));
}
}