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>&lt;accessibility-shortcut-target&gt;</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