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.IntRange;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.annotation.StringRes;
import android.compat.annotation.ChangeId; import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter; import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage; import android.compat.annotation.UnsupportedAppUsage;
@@ -61,6 +62,7 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.regex.Pattern;
/** /**
* This class describes an {@link AccessibilityService}. The system notifies an * This class describes an {@link AccessibilityService}. The system notifies an
@@ -552,6 +554,13 @@ public class AccessibilityServiceInfo implements Parcelable {
*/ */
private int mHtmlDescriptionRes; 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. * Creates a new instance.
*/ */
@@ -782,12 +791,10 @@ public class AccessibilityServiceInfo implements Parcelable {
} }
/** /**
* The animated image resource id. * Gets the animated image resource id.
* <p> *
* <strong>Statically set from
* {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
* </p>
* @return The animated image resource id. * @return The animated image resource id.
*
* @hide * @hide
*/ */
public int getAnimatedImageRes() { public int getAnimatedImageRes() {
@@ -797,10 +804,12 @@ public class AccessibilityServiceInfo implements Parcelable {
/** /**
* The animated image drawable. * The animated image drawable.
* <p> * <p>
* Image can not exceed the screen size.
* <strong>Statically set from * <strong>Statically set from
* {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
* </p> * </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 @Nullable
public Drawable loadAnimatedImage(@NonNull Context context) { public Drawable loadAnimatedImage(@NonNull Context context) {
@@ -808,11 +817,8 @@ public class AccessibilityServiceInfo implements Parcelable {
return null; return null;
} }
final PackageManager packageManager = context.getPackageManager(); return loadSafeAnimatedImage(context, mResolveInfo.serviceInfo.applicationInfo,
final String packageName = mComponentName.getPackageName(); mAnimatedImageRes);
final ApplicationInfo applicationInfo = mResolveInfo.serviceInfo.applicationInfo;
return packageManager.getDrawable(packageName, mAnimatedImageRes, applicationInfo);
} }
/** /**
@@ -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> * <p>
* Filters the <img> tag which do not meet the custom specification and the <a> tag.
* <strong>Statically set from * <strong>Statically set from
* {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
* </p> * </p>
* @return The localized html description. * @return The localized and restricted html description.
*/ */
@Nullable @Nullable
public String loadHtmlDescription(@NonNull PackageManager packageManager) { public String loadHtmlDescription(@NonNull PackageManager packageManager) {
if (mHtmlDescriptionRes == 0) { if (mHtmlDescriptionRes == /* invalid */ 0) {
return null; return null;
} }
@@ -941,7 +948,7 @@ public class AccessibilityServiceInfo implements Parcelable {
final CharSequence htmlDescription = packageManager.getText(serviceInfo.packageName, final CharSequence htmlDescription = packageManager.getText(serviceInfo.packageName,
mHtmlDescriptionRes, serviceInfo.applicationInfo); mHtmlDescriptionRes, serviceInfo.applicationInfo);
if (htmlDescription != null) { if (htmlDescription != null) {
return htmlDescription.toString().trim(); return getFilteredHtmlText(htmlDescription.toString().trim());
} }
return null; return null;
} }
@@ -1414,4 +1421,103 @@ public class AccessibilityServiceInfo implements Parcelable {
return new AccessibilityServiceInfo[size]; 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.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.annotation.StringRes;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -28,13 +29,19 @@ import android.content.res.Resources;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.content.res.XmlResourceParser; import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.TypedValue;
import android.util.Xml; import android.util.Xml;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException; 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 * 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; 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. * Creates a new instance.
* *
@@ -134,7 +148,7 @@ public final class AccessibilityShortcutInfo {
// Gets animated image // Gets animated image
mAnimatedImageRes = asAttributes.getResourceId( mAnimatedImageRes = asAttributes.getResourceId(
com.android.internal.R.styleable com.android.internal.R.styleable
.AccessibilityShortcutTarget_animatedImageDrawable, 0); .AccessibilityShortcutTarget_animatedImageDrawable, /* defValue= */ 0);
// Gets html description // Gets html description
mHtmlDescriptionRes = asAttributes.getResourceId( mHtmlDescriptionRes = asAttributes.getResourceId(
com.android.internal.R.styleable.AccessibilityShortcutTarget_htmlDescription, 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. * @return The animated image resource id.
* *
@@ -205,7 +219,8 @@ public final class AccessibilityShortcutInfo {
/** /**
* The animated image drawable of the accessibility shortcut target. * 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 @Nullable
public Drawable loadAnimatedImage(@NonNull Context context) { public Drawable loadAnimatedImage(@NonNull Context context) {
@@ -213,21 +228,20 @@ public final class AccessibilityShortcutInfo {
return null; return null;
} }
final PackageManager packageManager = context.getPackageManager(); return loadSafeAnimatedImage(context, mActivityInfo.applicationInfo, mAnimatedImageRes);
final String packageName = mComponentName.getPackageName();
final ApplicationInfo applicationInfo = mActivityInfo.applicationInfo;
return packageManager.getDrawable(packageName, mAnimatedImageRes, applicationInfo);
} }
/** /**
* 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 @Nullable
public String loadHtmlDescription(@NonNull PackageManager packageManager) { 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("]"); stringBuilder.append("]");
return stringBuilder.toString(); 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()));
}
} }