/* * Copyright (C) 2019 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.accessibilityservice; import static android.accessibilityservice.util.AccessibilityUtils.getFilteredHtmlText; import static android.accessibilityservice.util.AccessibilityUtils.loadSafeAnimatedImage; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; /** * Activities of interest to users with accessibility needs may request to be targets of the * accessibility shortcut. These activities must handle the * {@link Intent#ACTION_MAIN} intent with category * {@link Intent#CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET}, which will be dispatched by the system * when the user activates the shortcut when it is configured to point at this target. * * @see Intent#CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET * * @hide */ public final class AccessibilityShortcutInfo { private static final String TAG_ACCESSIBILITY_SHORTCUT = "accessibility-shortcut-target"; /** * Name under which an activity component of the accessibility shortcut publishes information * about itself. This meta-data must reference an XML resource containing an * <accessibility-shortcut-target> tag. */ public static final String META_DATA = "android.accessibilityshortcut.target"; /** * The component name of the accessibility shortcut target. */ private final ComponentName mComponentName; /** * The activity info of the accessibility shortcut target. */ private final ActivityInfo mActivityInfo; /** * Resource id of the intro of the accessibility shortcut target. */ private final int mIntroResId; /** * Resource id of the summary of the accessibility shortcut target. */ private final int mSummaryResId; /** * Resource id of the description of the accessibility shortcut target. */ private final int mDescriptionResId; /** * Resource id of the animated image of the accessibility shortcut target. */ private final int mAnimatedImageRes; /** * Resource id of the html description of the accessibility shortcut target. */ private final int mHtmlDescriptionRes; /** * The accessibility shortcut target setting activity's name, used by the system * settings to launch the setting activity of this accessibility shortcut target. */ private String mSettingsActivityName; /** * The name of {@link android.service.quicksettings.TileService} is associated with this * accessibility shortcut target for one to one mapping. It is used by system settings to remind * users this accessibility service has a {@link android.service.quicksettings.TileService}. */ private String mTileServiceName; /** * Creates a new instance. * * @param context Context for accessing resources. * @param activityInfo The activity info. * @throws XmlPullParserException If a XML parsing error occurs. * @throws IOException If a XML parsing error occurs. */ public AccessibilityShortcutInfo(@NonNull Context context, @NonNull ActivityInfo activityInfo) throws XmlPullParserException, IOException { final PackageManager packageManager = context.getPackageManager(); mComponentName = activityInfo.getComponentName(); mActivityInfo = activityInfo; try (XmlResourceParser parser = mActivityInfo.loadXmlMetaData( packageManager, META_DATA)) { if (parser == null) { throw new XmlPullParserException("Meta-data " + TAG_ACCESSIBILITY_SHORTCUT + " does not exist"); } int type = 0; while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { type = parser.next(); } final String nodeName = parser.getName(); if (!TAG_ACCESSIBILITY_SHORTCUT.equals(nodeName)) { throw new XmlPullParserException("Meta-data does not start with" + TAG_ACCESSIBILITY_SHORTCUT + " tag"); } final AttributeSet allAttributes = Xml.asAttributeSet(parser); final Resources resources = packageManager.getResourcesForApplication( mActivityInfo.applicationInfo); final TypedArray asAttributes = resources.obtainAttributes(allAttributes, com.android.internal.R.styleable.AccessibilityShortcutTarget); // Gets description mDescriptionResId = asAttributes.getResourceId( com.android.internal.R.styleable.AccessibilityShortcutTarget_description, 0); // Gets summary mSummaryResId = asAttributes.getResourceId( com.android.internal.R.styleable.AccessibilityShortcutTarget_summary, 0); // Gets animated image mAnimatedImageRes = asAttributes.getResourceId( com.android.internal.R.styleable .AccessibilityShortcutTarget_animatedImageDrawable, /* defValue= */ 0); // Gets html description mHtmlDescriptionRes = asAttributes.getResourceId( com.android.internal.R.styleable.AccessibilityShortcutTarget_htmlDescription, 0); // Get settings activity name mSettingsActivityName = asAttributes.getString( com.android.internal.R.styleable.AccessibilityShortcutTarget_settingsActivity); // Get tile service class name mTileServiceName = asAttributes.getString( com.android.internal.R.styleable.AccessibilityShortcutTarget_tileService); // Gets intro mIntroResId = asAttributes.getResourceId( com.android.internal.R.styleable.AccessibilityShortcutTarget_intro, 0); asAttributes.recycle(); } catch (PackageManager.NameNotFoundException e) { throw new XmlPullParserException("Unable to create context for: " + mActivityInfo.packageName); } } /** * The {@link ActivityInfo} of accessibility shortcut target. * * @return The activity info. */ @NonNull public ActivityInfo getActivityInfo() { return mActivityInfo; } /** * The {@link ComponentName} of the accessibility shortcut target. * * @return The component name */ @NonNull public ComponentName getComponentName() { return mComponentName; } /** * The localized summary of the accessibility shortcut target. * * @return The localized summary if available, and {@code null} if a summary * has not been provided. */ @Nullable public String loadSummary(@NonNull PackageManager packageManager) { return loadResourceString(packageManager, mActivityInfo, mSummaryResId); } /** * The localized intro of the accessibility shortcut target. * * @return The localized intro. */ @Nullable public String loadIntro(@NonNull PackageManager packageManager) { return loadResourceString(packageManager, mActivityInfo, mIntroResId); } /** * The localized description of the accessibility shortcut target. * * @return The localized description. */ @Nullable public String loadDescription(@NonNull PackageManager packageManager) { return loadResourceString(packageManager, mActivityInfo, mDescriptionResId); } /** * Gets the animated image resource id. * * @return The animated image resource id. * * @hide */ public int getAnimatedImageRes() { return mAnimatedImageRes; } /** * The animated image drawable of the accessibility shortcut target. * * @return The animated image drawable, or null if the resource is invalid or the image * exceed the screen size. * * @hide */ @Nullable public Drawable loadAnimatedImage(@NonNull Context context) { if (mAnimatedImageRes == /* invalid */ 0) { return null; } return loadSafeAnimatedImage(context, mActivityInfo.applicationInfo, mAnimatedImageRes); } /** * The localized and restricted html description of the accessibility shortcut target. * It filters the tag which do not meet the custom specification and the tag. * * @return The localized and restricted html description. * * @hide */ @Nullable public String loadHtmlDescription(@NonNull PackageManager packageManager) { final String htmlDescription = loadResourceString(packageManager, mActivityInfo, mHtmlDescriptionRes); if (htmlDescription != null) { return getFilteredHtmlText(htmlDescription); } return null; } /** * The settings activity name. * * @return The settings activity name. */ @Nullable public String getSettingsActivityName() { return mSettingsActivityName; } /** * Gets the name of {@link android.service.quicksettings.TileService} is associated with * this accessibility shortcut target. * * @return The class name of {@link android.service.quicksettings.TileService}. */ @Nullable public String getTileServiceName() { return mTileServiceName; } /** * Gets string resource by the given activity and resource id. */ @Nullable private String loadResourceString(@NonNull PackageManager packageManager, @NonNull ActivityInfo activityInfo, int resId) { if (resId == 0) { return null; } final CharSequence text = packageManager.getText(activityInfo.packageName, resId, activityInfo.applicationInfo); if (text != null) { return text.toString().trim(); } return null; } /** * {@inheritDoc} */ @Override public int hashCode() { return 31 * 1 + ((mComponentName == null) ? 0 : mComponentName.hashCode()); } /** * {@inheritDoc} */ @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final AccessibilityShortcutInfo other = (AccessibilityShortcutInfo) obj; if (mComponentName == null) { if (other.mComponentName != null) { return false; } } else if (!mComponentName.equals(other.mComponentName)) { return false; } return true; } /** * {@inheritDoc} */ @Override public String toString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("AccessibilityShortcutInfo["); stringBuilder.append("activityInfo: ").append(mActivityInfo); stringBuilder.append("]"); return stringBuilder.toString(); } }