diff options
| author | Peter_Liang <peterliang@google.com> | 2020-02-18 13:20:05 +0800 |
|---|---|---|
| committer | PETER LIANG <peterliang@google.com> | 2020-02-25 09:05:12 +0000 |
| commit | 193da543915c8674da7448a5b4b21ce15edb0987 (patch) | |
| tree | b28dfc30878353e5376b185f58d162e4e781f84a /core/java/android | |
| parent | fb7bda97345466cdd2ac1b9e7dcbd788d05190ee (diff) | |
Fine-tune the public APIs of allow rich content in Accessibility Settings for security.
1. Add some checkers and java doc:
- loadHtmlDescription
It filters the <img> tag which do not meet the custom specification and the <a> tag.
- loadAnimatedImage
It checks the resource whether to exceed the screen size.
Bug: 149516547
Test: atest AccessibilityShortcutInfoTest & manual test
Change-Id: I8ecad186fc9e4bb6799ded296805cdc5353c5010
Merged-In: I8ecad186fc9e4bb6799ded296805cdc5353c5010
(cherry picked from commit c6e383691591cccd1f98cdf59c55bca31af0cf87)
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/accessibilityservice/AccessibilityServiceInfo.java | 136 | ||||
| -rw-r--r-- | core/java/android/accessibilityservice/AccessibilityShortcutInfo.java | 135 |
2 files changed, 245 insertions, 26 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 3b0667d1e377..316a018ef214 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -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())); + } } diff --git a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java index 62096792d764..a812f29a8bc7 100644 --- a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java @@ -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())); + } } |
