1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.accessibilityservice; 18 19 import static android.accessibilityservice.util.AccessibilityUtils.getFilteredHtmlText; 20 import static android.accessibilityservice.util.AccessibilityUtils.loadSafeAnimatedImage; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.ActivityInfo; 28 import android.content.pm.PackageManager; 29 import android.content.res.Resources; 30 import android.content.res.TypedArray; 31 import android.content.res.XmlResourceParser; 32 import android.graphics.drawable.Drawable; 33 import android.util.AttributeSet; 34 import android.util.Xml; 35 36 import org.xmlpull.v1.XmlPullParser; 37 import org.xmlpull.v1.XmlPullParserException; 38 39 import java.io.IOException; 40 41 /** 42 * Activities of interest to users with accessibility needs may request to be targets of the 43 * accessibility shortcut. These activities must handle the 44 * {@link Intent#ACTION_MAIN} intent with category 45 * {@link Intent#CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET}, which will be dispatched by the system 46 * when the user activates the shortcut when it is configured to point at this target. 47 * 48 * @see Intent#CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET 49 * 50 * @hide 51 */ 52 public final class AccessibilityShortcutInfo { 53 private static final String TAG_ACCESSIBILITY_SHORTCUT = "accessibility-shortcut-target"; 54 55 /** 56 * Name under which an activity component of the accessibility shortcut publishes information 57 * about itself. This meta-data must reference an XML resource containing an 58 * <code><accessibility-shortcut-target></code> tag. 59 */ 60 public static final String META_DATA = "android.accessibilityshortcut.target"; 61 62 /** 63 * The component name of the accessibility shortcut target. 64 */ 65 private final ComponentName mComponentName; 66 67 /** 68 * The activity info of the accessibility shortcut target. 69 */ 70 private final ActivityInfo mActivityInfo; 71 72 /** 73 * Resource id of the intro of the accessibility shortcut target. 74 */ 75 private final int mIntroResId; 76 77 /** 78 * Resource id of the summary of the accessibility shortcut target. 79 */ 80 private final int mSummaryResId; 81 82 /** 83 * Resource id of the description of the accessibility shortcut target. 84 */ 85 private final int mDescriptionResId; 86 87 /** 88 * Resource id of the animated image of the accessibility shortcut target. 89 */ 90 private final int mAnimatedImageRes; 91 92 /** 93 * Resource id of the html description of the accessibility shortcut target. 94 */ 95 private final int mHtmlDescriptionRes; 96 97 /** 98 * The accessibility shortcut target setting activity's name, used by the system 99 * settings to launch the setting activity of this accessibility shortcut target. 100 */ 101 private String mSettingsActivityName; 102 103 /** 104 * The name of {@link android.service.quicksettings.TileService} is associated with this 105 * accessibility shortcut target for one to one mapping. It is used by system settings to remind 106 * users this accessibility service has a {@link android.service.quicksettings.TileService}. 107 */ 108 private String mTileServiceName; 109 110 /** 111 * Creates a new instance. 112 * 113 * @param context Context for accessing resources. 114 * @param activityInfo The activity info. 115 * @throws XmlPullParserException If a XML parsing error occurs. 116 * @throws IOException If a XML parsing error occurs. 117 */ AccessibilityShortcutInfo(@onNull Context context, @NonNull ActivityInfo activityInfo)118 public AccessibilityShortcutInfo(@NonNull Context context, @NonNull ActivityInfo activityInfo) 119 throws XmlPullParserException, IOException { 120 final PackageManager packageManager = context.getPackageManager(); 121 mComponentName = activityInfo.getComponentName(); 122 mActivityInfo = activityInfo; 123 124 try (XmlResourceParser parser = mActivityInfo.loadXmlMetaData( 125 packageManager, META_DATA)) { 126 if (parser == null) { 127 throw new XmlPullParserException("Meta-data " 128 + TAG_ACCESSIBILITY_SHORTCUT + " does not exist"); 129 } 130 131 int type = 0; 132 while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { 133 type = parser.next(); 134 } 135 136 final String nodeName = parser.getName(); 137 if (!TAG_ACCESSIBILITY_SHORTCUT.equals(nodeName)) { 138 throw new XmlPullParserException("Meta-data does not start with" 139 + TAG_ACCESSIBILITY_SHORTCUT + " tag"); 140 } 141 142 final AttributeSet allAttributes = Xml.asAttributeSet(parser); 143 final Resources resources = packageManager.getResourcesForApplication( 144 mActivityInfo.applicationInfo); 145 final TypedArray asAttributes = resources.obtainAttributes(allAttributes, 146 com.android.internal.R.styleable.AccessibilityShortcutTarget); 147 148 // Gets description 149 mDescriptionResId = asAttributes.getResourceId( 150 com.android.internal.R.styleable.AccessibilityShortcutTarget_description, 0); 151 // Gets summary 152 mSummaryResId = asAttributes.getResourceId( 153 com.android.internal.R.styleable.AccessibilityShortcutTarget_summary, 0); 154 // Gets animated image 155 mAnimatedImageRes = asAttributes.getResourceId( 156 com.android.internal.R.styleable 157 .AccessibilityShortcutTarget_animatedImageDrawable, /* defValue= */ 0); 158 // Gets html description 159 mHtmlDescriptionRes = asAttributes.getResourceId( 160 com.android.internal.R.styleable.AccessibilityShortcutTarget_htmlDescription, 161 0); 162 // Get settings activity name 163 mSettingsActivityName = asAttributes.getString( 164 com.android.internal.R.styleable.AccessibilityShortcutTarget_settingsActivity); 165 // Get tile service class name 166 mTileServiceName = asAttributes.getString( 167 com.android.internal.R.styleable.AccessibilityShortcutTarget_tileService); 168 // Gets intro 169 mIntroResId = asAttributes.getResourceId( 170 com.android.internal.R.styleable.AccessibilityShortcutTarget_intro, 0); 171 asAttributes.recycle(); 172 } catch (PackageManager.NameNotFoundException e) { 173 throw new XmlPullParserException("Unable to create context for: " 174 + mActivityInfo.packageName); 175 } 176 } 177 178 /** 179 * The {@link ActivityInfo} of accessibility shortcut target. 180 * 181 * @return The activity info. 182 */ 183 @NonNull getActivityInfo()184 public ActivityInfo getActivityInfo() { 185 return mActivityInfo; 186 } 187 188 /** 189 * The {@link ComponentName} of the accessibility shortcut target. 190 * 191 * @return The component name 192 */ 193 @NonNull getComponentName()194 public ComponentName getComponentName() { 195 return mComponentName; 196 } 197 198 /** 199 * The localized summary of the accessibility shortcut target. 200 * 201 * @return The localized summary if available, and {@code null} if a summary 202 * has not been provided. 203 */ 204 @Nullable loadSummary(@onNull PackageManager packageManager)205 public String loadSummary(@NonNull PackageManager packageManager) { 206 return loadResourceString(packageManager, mActivityInfo, mSummaryResId); 207 } 208 209 /** 210 * The localized intro of the accessibility shortcut target. 211 * 212 * @return The localized intro. 213 */ 214 @Nullable loadIntro(@onNull PackageManager packageManager)215 public String loadIntro(@NonNull PackageManager packageManager) { 216 return loadResourceString(packageManager, mActivityInfo, mIntroResId); 217 } 218 219 /** 220 * The localized description of the accessibility shortcut target. 221 * 222 * @return The localized description. 223 */ 224 @Nullable loadDescription(@onNull PackageManager packageManager)225 public String loadDescription(@NonNull PackageManager packageManager) { 226 return loadResourceString(packageManager, mActivityInfo, mDescriptionResId); 227 } 228 229 /** 230 * Gets the animated image resource id. 231 * 232 * @return The animated image resource id. 233 * 234 * @hide 235 */ getAnimatedImageRes()236 public int getAnimatedImageRes() { 237 return mAnimatedImageRes; 238 } 239 240 /** 241 * The animated image drawable of the accessibility shortcut target. 242 * 243 * @return The animated image drawable, or null if the resource is invalid or the image 244 * exceed the screen size. 245 * 246 * @hide 247 */ 248 @Nullable loadAnimatedImage(@onNull Context context)249 public Drawable loadAnimatedImage(@NonNull Context context) { 250 if (mAnimatedImageRes == /* invalid */ 0) { 251 return null; 252 } 253 254 return loadSafeAnimatedImage(context, mActivityInfo.applicationInfo, mAnimatedImageRes); 255 } 256 257 /** 258 * The localized and restricted html description of the accessibility shortcut target. 259 * It filters the <img> tag which do not meet the custom specification and the <a> tag. 260 * 261 * @return The localized and restricted html description. 262 * 263 * @hide 264 */ 265 @Nullable loadHtmlDescription(@onNull PackageManager packageManager)266 public String loadHtmlDescription(@NonNull PackageManager packageManager) { 267 final String htmlDescription = loadResourceString(packageManager, mActivityInfo, 268 mHtmlDescriptionRes); 269 if (htmlDescription != null) { 270 return getFilteredHtmlText(htmlDescription); 271 } 272 return null; 273 } 274 275 /** 276 * The settings activity name. 277 * 278 * @return The settings activity name. 279 */ 280 @Nullable getSettingsActivityName()281 public String getSettingsActivityName() { 282 return mSettingsActivityName; 283 } 284 285 /** 286 * Gets the name of {@link android.service.quicksettings.TileService} is associated with 287 * this accessibility shortcut target. 288 * 289 * @return The class name of {@link android.service.quicksettings.TileService}. 290 */ 291 @Nullable getTileServiceName()292 public String getTileServiceName() { 293 return mTileServiceName; 294 } 295 296 /** 297 * Gets string resource by the given activity and resource id. 298 */ 299 @Nullable loadResourceString(@onNull PackageManager packageManager, @NonNull ActivityInfo activityInfo, int resId)300 private String loadResourceString(@NonNull PackageManager packageManager, 301 @NonNull ActivityInfo activityInfo, int resId) { 302 if (resId == 0) { 303 return null; 304 } 305 final CharSequence text = packageManager.getText(activityInfo.packageName, 306 resId, activityInfo.applicationInfo); 307 if (text != null) { 308 return text.toString().trim(); 309 } 310 return null; 311 } 312 313 /** 314 * {@inheritDoc} 315 */ 316 @Override hashCode()317 public int hashCode() { 318 return 31 * 1 + ((mComponentName == null) ? 0 : mComponentName.hashCode()); 319 } 320 321 /** 322 * {@inheritDoc} 323 */ 324 @Override equals(@ullable Object obj)325 public boolean equals(@Nullable Object obj) { 326 if (this == obj) { 327 return true; 328 } 329 if (obj == null) { 330 return false; 331 } 332 if (getClass() != obj.getClass()) { 333 return false; 334 } 335 final AccessibilityShortcutInfo other = (AccessibilityShortcutInfo) obj; 336 if (mComponentName == null) { 337 if (other.mComponentName != null) { 338 return false; 339 } 340 } else if (!mComponentName.equals(other.mComponentName)) { 341 return false; 342 } 343 return true; 344 } 345 346 /** 347 * {@inheritDoc} 348 */ 349 @Override toString()350 public String toString() { 351 StringBuilder stringBuilder = new StringBuilder(); 352 stringBuilder.append("AccessibilityShortcutInfo["); 353 stringBuilder.append("activityInfo: ").append(mActivityInfo); 354 stringBuilder.append("]"); 355 return stringBuilder.toString(); 356 } 357 } 358