• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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 android.content.ComponentName;
20 import android.content.Context;
21 import android.content.pm.PackageManager;
22 import android.content.pm.PackageManager.NameNotFoundException;
23 import android.content.pm.ResolveInfo;
24 import android.content.pm.ServiceInfo;
25 import android.content.res.Resources;
26 import android.content.res.TypedArray;
27 import android.content.res.XmlResourceParser;
28 import android.os.Build;
29 import android.os.Parcel;
30 import android.os.Parcelable;
31 import android.util.AttributeSet;
32 import android.util.TypedValue;
33 import android.util.Xml;
34 import android.view.View;
35 import android.view.accessibility.AccessibilityEvent;
36 
37 import org.xmlpull.v1.XmlPullParser;
38 import org.xmlpull.v1.XmlPullParserException;
39 
40 import java.io.IOException;
41 
42 /**
43  * This class describes an {@link AccessibilityService}. The system notifies an
44  * {@link AccessibilityService} for {@link android.view.accessibility.AccessibilityEvent}s
45  * according to the information encapsulated in this class.
46  *
47  * <div class="special reference">
48  * <h3>Developer Guides</h3>
49  * <p>For more information about creating AccessibilityServices, read the
50  * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
51  * developer guide.</p>
52  * </div>
53  *
54  * @see AccessibilityService
55  * @see android.view.accessibility.AccessibilityEvent
56  * @see android.view.accessibility.AccessibilityManager
57  */
58 public class AccessibilityServiceInfo implements Parcelable {
59 
60     private static final String TAG_ACCESSIBILITY_SERVICE = "accessibility-service";
61 
62     /**
63      * Denotes spoken feedback.
64      */
65     public static final int FEEDBACK_SPOKEN = 0x0000001;
66 
67     /**
68      * Denotes haptic feedback.
69      */
70     public static final int FEEDBACK_HAPTIC =  0x0000002;
71 
72     /**
73      * Denotes audible (not spoken) feedback.
74      */
75     public static final int FEEDBACK_AUDIBLE = 0x0000004;
76 
77     /**
78      * Denotes visual feedback.
79      */
80     public static final int FEEDBACK_VISUAL = 0x0000008;
81 
82     /**
83      * Denotes generic feedback.
84      */
85     public static final int FEEDBACK_GENERIC = 0x0000010;
86 
87     /**
88      * Mask for all feedback types.
89      *
90      * @see #FEEDBACK_SPOKEN
91      * @see #FEEDBACK_HAPTIC
92      * @see #FEEDBACK_AUDIBLE
93      * @see #FEEDBACK_VISUAL
94      * @see #FEEDBACK_GENERIC
95      */
96     public static final int FEEDBACK_ALL_MASK = 0xFFFFFFFF;
97 
98     /**
99      * If an {@link AccessibilityService} is the default for a given type.
100      * Default service is invoked only if no package specific one exists. In case of
101      * more than one package specific service only the earlier registered is notified.
102      */
103     public static final int DEFAULT = 0x0000001;
104 
105     /**
106      * If this flag is set the system will regard views that are not important
107      * for accessibility in addition to the ones that are important for accessibility.
108      * That is, views that are marked as not important for accessibility via
109      * {@link View#IMPORTANT_FOR_ACCESSIBILITY_NO} and views that are marked as
110      * potentially important for accessibility via
111      * {@link View#IMPORTANT_FOR_ACCESSIBILITY_AUTO} for which the system has determined
112      * that are not important for accessibility, are both reported while querying the
113      * window content and also the accessibility service will receive accessibility events
114      * from them.
115      * <p>
116      * <strong>Note:</strong> For accessibility services targeting API version
117      * {@link Build.VERSION_CODES#JELLY_BEAN} or higher this flag has to be explicitly
118      * set for the system to regard views that are not important for accessibility. For
119      * accessibility services targeting API version lower than
120      * {@link Build.VERSION_CODES#JELLY_BEAN} this flag is ignored and all views are
121      * regarded for accessibility purposes.
122      * </p>
123      * <p>
124      * Usually views not important for accessibility are layout managers that do not
125      * react to user actions, do not draw any content, and do not have any special
126      * semantics in the context of the screen content. For example, a three by three
127      * grid can be implemented as three horizontal linear layouts and one vertical,
128      * or three vertical linear layouts and one horizontal, or one grid layout, etc.
129      * In this context the actual layout mangers used to achieve the grid configuration
130      * are not important, rather it is important that there are nine evenly distributed
131      * elements.
132      * </p>
133      */
134     public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x0000002;
135 
136     /**
137      * This flag requests that the system gets into touch exploration mode.
138      * In this mode a single finger moving on the screen behaves as a mouse
139      * pointer hovering over the user interface. The system will also detect
140      * certain gestures performed on the touch screen and notify this service.
141      * The system will enable touch exploration mode if there is at least one
142      * accessibility service that has this flag set. Hence, clearing this
143      * flag does not guarantee that the device will not be in touch exploration
144      * mode since there may be another enabled service that requested it.
145      */
146     public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE= 0x0000004;
147 
148     /**
149      * The event types an {@link AccessibilityService} is interested in.
150      * <p>
151      *   <strong>Can be dynamically set at runtime.</strong>
152      * </p>
153      * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED
154      * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_LONG_CLICKED
155      * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_FOCUSED
156      * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SELECTED
157      * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED
158      * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED
159      * @see android.view.accessibility.AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED
160      * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START
161      * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END
162      * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_ENTER
163      * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_EXIT
164      * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SCROLLED
165      * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED
166      * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED
167      */
168     public int eventTypes;
169 
170     /**
171      * The package names an {@link AccessibilityService} is interested in. Setting
172      * to <code>null</code> is equivalent to all packages.
173      * <p>
174      *   <strong>Can be dynamically set at runtime.</strong>
175      * </p>
176      */
177     public String[] packageNames;
178 
179     /**
180      * The feedback type an {@link AccessibilityService} provides.
181      * <p>
182      *   <strong>Can be dynamically set at runtime.</strong>
183      * </p>
184      * @see #FEEDBACK_AUDIBLE
185      * @see #FEEDBACK_GENERIC
186      * @see #FEEDBACK_HAPTIC
187      * @see #FEEDBACK_SPOKEN
188      * @see #FEEDBACK_VISUAL
189      */
190     public int feedbackType;
191 
192     /**
193      * The timeout after the most recent event of a given type before an
194      * {@link AccessibilityService} is notified.
195      * <p>
196      *   <strong>Can be dynamically set at runtime.</strong>.
197      * </p>
198      * <p>
199      * <strong>Note:</strong> The event notification timeout is useful to avoid propagating
200      *       events to the client too frequently since this is accomplished via an expensive
201      *       interprocess call. One can think of the timeout as a criteria to determine when
202      *       event generation has settled down.
203      */
204     public long notificationTimeout;
205 
206     /**
207      * This field represents a set of flags used for configuring an
208      * {@link AccessibilityService}.
209      * <p>
210      *   <strong>Can be dynamically set at runtime.</strong>
211      * </p>
212      * @see #DEFAULT
213      * @see #FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
214      * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
215      */
216     public int flags;
217 
218     /**
219      * The unique string Id to identify the accessibility service.
220      */
221     private String mId;
222 
223     /**
224      * The Service that implements this accessibility service component.
225      */
226     private ResolveInfo mResolveInfo;
227 
228     /**
229      * The accessibility service setting activity's name, used by the system
230      * settings to launch the setting activity of this accessibility service.
231      */
232     private String mSettingsActivityName;
233 
234     /**
235      * Flag whether this accessibility service can retrieve window content.
236      */
237     private boolean mCanRetrieveWindowContent;
238 
239     /**
240      * Resource id of the description of the accessibility service.
241      */
242     private int mDescriptionResId;
243 
244     /**
245      * Non localized description of the accessibility service.
246      */
247     private String mNonLocalizedDescription;
248 
249     /**
250      * Creates a new instance.
251      */
AccessibilityServiceInfo()252     public AccessibilityServiceInfo() {
253         /* do nothing */
254     }
255 
256     /**
257      * Creates a new instance.
258      *
259      * @param resolveInfo The service resolve info.
260      * @param context Context for accessing resources.
261      * @throws XmlPullParserException If a XML parsing error occurs.
262      * @throws IOException If a XML parsing error occurs.
263      *
264      * @hide
265      */
AccessibilityServiceInfo(ResolveInfo resolveInfo, Context context)266     public AccessibilityServiceInfo(ResolveInfo resolveInfo, Context context)
267             throws XmlPullParserException, IOException {
268         ServiceInfo serviceInfo = resolveInfo.serviceInfo;
269         mId = new ComponentName(serviceInfo.packageName, serviceInfo.name).flattenToShortString();
270         mResolveInfo = resolveInfo;
271 
272         XmlResourceParser parser = null;
273 
274         try {
275             PackageManager packageManager = context.getPackageManager();
276             parser = serviceInfo.loadXmlMetaData(packageManager,
277                     AccessibilityService.SERVICE_META_DATA);
278             if (parser == null) {
279                 return;
280             }
281 
282             int type = 0;
283             while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
284                 type = parser.next();
285             }
286 
287             String nodeName = parser.getName();
288             if (!TAG_ACCESSIBILITY_SERVICE.equals(nodeName)) {
289                 throw new XmlPullParserException( "Meta-data does not start with"
290                         + TAG_ACCESSIBILITY_SERVICE + " tag");
291             }
292 
293             AttributeSet allAttributes = Xml.asAttributeSet(parser);
294             Resources resources = packageManager.getResourcesForApplication(
295                     serviceInfo.applicationInfo);
296             TypedArray asAttributes = resources.obtainAttributes(allAttributes,
297                     com.android.internal.R.styleable.AccessibilityService);
298             eventTypes = asAttributes.getInt(
299                     com.android.internal.R.styleable.AccessibilityService_accessibilityEventTypes,
300                     0);
301             String packageNamez = asAttributes.getString(
302                     com.android.internal.R.styleable.AccessibilityService_packageNames);
303             if (packageNamez != null) {
304                 packageNames = packageNamez.split("(\\s)*,(\\s)*");
305             }
306             feedbackType = asAttributes.getInt(
307                     com.android.internal.R.styleable.AccessibilityService_accessibilityFeedbackType,
308                     0);
309             notificationTimeout = asAttributes.getInt(
310                     com.android.internal.R.styleable.AccessibilityService_notificationTimeout,
311                     0);
312             flags = asAttributes.getInt(
313                     com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0);
314             mSettingsActivityName = asAttributes.getString(
315                     com.android.internal.R.styleable.AccessibilityService_settingsActivity);
316             mCanRetrieveWindowContent = asAttributes.getBoolean(
317                     com.android.internal.R.styleable.AccessibilityService_canRetrieveWindowContent,
318                     false);
319             TypedValue peekedValue = asAttributes.peekValue(
320                     com.android.internal.R.styleable.AccessibilityService_description);
321             if (peekedValue != null) {
322                 mDescriptionResId = peekedValue.resourceId;
323                 CharSequence nonLocalizedDescription = peekedValue.coerceToString();
324                 if (nonLocalizedDescription != null) {
325                     mNonLocalizedDescription = nonLocalizedDescription.toString().trim();
326                 }
327             }
328             asAttributes.recycle();
329         } catch (NameNotFoundException e) {
330             throw new XmlPullParserException( "Unable to create context for: "
331                     + serviceInfo.packageName);
332         } finally {
333             if (parser != null) {
334                 parser.close();
335             }
336         }
337     }
338 
339     /**
340      * Updates the properties that an AccessibilitySerivice can change dynamically.
341      *
342      * @param other The info from which to update the properties.
343      *
344      * @hide
345      */
updateDynamicallyConfigurableProperties(AccessibilityServiceInfo other)346     public void updateDynamicallyConfigurableProperties(AccessibilityServiceInfo other) {
347         eventTypes = other.eventTypes;
348         packageNames = other.packageNames;
349         feedbackType = other.feedbackType;
350         notificationTimeout = other.notificationTimeout;
351         flags = other.flags;
352     }
353 
354     /**
355      * The accessibility service id.
356      * <p>
357      *   <strong>Generated by the system.</strong>
358      * </p>
359      * @return The id.
360      */
getId()361     public String getId() {
362         return mId;
363     }
364 
365     /**
366      * The service {@link ResolveInfo}.
367      * <p>
368      *   <strong>Generated by the system.</strong>
369      * </p>
370      * @return The info.
371      */
getResolveInfo()372     public ResolveInfo getResolveInfo() {
373         return mResolveInfo;
374     }
375 
376     /**
377      * The settings activity name.
378      * <p>
379      *    <strong>Statically set from
380      *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
381      * </p>
382      * @return The settings activity name.
383      */
getSettingsActivityName()384     public String getSettingsActivityName() {
385         return mSettingsActivityName;
386     }
387 
388     /**
389      * Whether this service can retrieve the current window's content.
390      * <p>
391      *    <strong>Statically set from
392      *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
393      * </p>
394      * @return True if window content can be retrieved.
395      */
getCanRetrieveWindowContent()396     public boolean getCanRetrieveWindowContent() {
397         return mCanRetrieveWindowContent;
398     }
399 
400     /**
401      * Gets the non-localized description of the accessibility service.
402      * <p>
403      *    <strong>Statically set from
404      *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
405      * </p>
406      * @return The description.
407      *
408      * @deprecated Use {@link #loadDescription(PackageManager)}.
409      */
getDescription()410     public String getDescription() {
411         return mNonLocalizedDescription;
412     }
413 
414     /**
415      * The localized description of the accessibility service.
416      * <p>
417      *    <strong>Statically set from
418      *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
419      * </p>
420      * @return The localized description.
421      */
loadDescription(PackageManager packageManager)422     public String loadDescription(PackageManager packageManager) {
423         if (mDescriptionResId == 0) {
424             return mNonLocalizedDescription;
425         }
426         ServiceInfo serviceInfo = mResolveInfo.serviceInfo;
427         CharSequence description = packageManager.getText(serviceInfo.packageName,
428                 mDescriptionResId, serviceInfo.applicationInfo);
429         if (description != null) {
430             return description.toString().trim();
431         }
432         return null;
433     }
434 
435     /**
436      * {@inheritDoc}
437      */
describeContents()438     public int describeContents() {
439         return 0;
440     }
441 
writeToParcel(Parcel parcel, int flagz)442     public void writeToParcel(Parcel parcel, int flagz) {
443         parcel.writeInt(eventTypes);
444         parcel.writeStringArray(packageNames);
445         parcel.writeInt(feedbackType);
446         parcel.writeLong(notificationTimeout);
447         parcel.writeInt(flags);
448         parcel.writeString(mId);
449         parcel.writeParcelable(mResolveInfo, 0);
450         parcel.writeString(mSettingsActivityName);
451         parcel.writeInt(mCanRetrieveWindowContent ? 1 : 0);
452         parcel.writeInt(mDescriptionResId);
453         parcel.writeString(mNonLocalizedDescription);
454     }
455 
initFromParcel(Parcel parcel)456     private void initFromParcel(Parcel parcel) {
457         eventTypes = parcel.readInt();
458         packageNames = parcel.readStringArray();
459         feedbackType = parcel.readInt();
460         notificationTimeout = parcel.readLong();
461         flags = parcel.readInt();
462         mId = parcel.readString();
463         mResolveInfo = parcel.readParcelable(null);
464         mSettingsActivityName = parcel.readString();
465         mCanRetrieveWindowContent = (parcel.readInt() == 1);
466         mDescriptionResId = parcel.readInt();
467         mNonLocalizedDescription = parcel.readString();
468     }
469 
470     @Override
toString()471     public String toString() {
472         StringBuilder stringBuilder = new StringBuilder();
473         appendEventTypes(stringBuilder, eventTypes);
474         stringBuilder.append(", ");
475         appendPackageNames(stringBuilder, packageNames);
476         stringBuilder.append(", ");
477         appendFeedbackTypes(stringBuilder, feedbackType);
478         stringBuilder.append(", ");
479         stringBuilder.append("notificationTimeout: ").append(notificationTimeout);
480         stringBuilder.append(", ");
481         appendFlags(stringBuilder, flags);
482         stringBuilder.append(", ");
483         stringBuilder.append("id: ").append(mId);
484         stringBuilder.append(", ");
485         stringBuilder.append("resolveInfo: ").append(mResolveInfo);
486         stringBuilder.append(", ");
487         stringBuilder.append("settingsActivityName: ").append(mSettingsActivityName);
488         stringBuilder.append(", ");
489         stringBuilder.append("retrieveScreenContent: ").append(mCanRetrieveWindowContent);
490         return stringBuilder.toString();
491     }
492 
appendFeedbackTypes(StringBuilder stringBuilder, int feedbackTypes)493     private static void appendFeedbackTypes(StringBuilder stringBuilder, int feedbackTypes) {
494         stringBuilder.append("feedbackTypes:");
495         stringBuilder.append("[");
496         while (feedbackTypes != 0) {
497             final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackTypes));
498             stringBuilder.append(feedbackTypeToString(feedbackTypeBit));
499             feedbackTypes &= ~feedbackTypeBit;
500             if (feedbackTypes != 0) {
501                 stringBuilder.append(", ");
502             }
503         }
504         stringBuilder.append("]");
505     }
506 
appendPackageNames(StringBuilder stringBuilder, String[] packageNames)507     private static void appendPackageNames(StringBuilder stringBuilder, String[] packageNames) {
508         stringBuilder.append("packageNames:");
509         stringBuilder.append("[");
510         if (packageNames != null) {
511             final int packageNameCount = packageNames.length;
512             for (int i = 0; i < packageNameCount; i++) {
513                 stringBuilder.append(packageNames[i]);
514                 if (i < packageNameCount - 1) {
515                     stringBuilder.append(", ");
516                 }
517             }
518         }
519         stringBuilder.append("]");
520     }
521 
appendEventTypes(StringBuilder stringBuilder, int eventTypes)522     private static void appendEventTypes(StringBuilder stringBuilder, int eventTypes) {
523         stringBuilder.append("eventTypes:");
524         stringBuilder.append("[");
525         while (eventTypes != 0) {
526             final int eventTypeBit = (1 << Integer.numberOfTrailingZeros(eventTypes));
527             stringBuilder.append(AccessibilityEvent.eventTypeToString(eventTypeBit));
528             eventTypes &= ~eventTypeBit;
529             if (eventTypes != 0) {
530                 stringBuilder.append(", ");
531             }
532         }
533         stringBuilder.append("]");
534     }
535 
appendFlags(StringBuilder stringBuilder, int flags)536     private static void appendFlags(StringBuilder stringBuilder, int flags) {
537         stringBuilder.append("flags:");
538         stringBuilder.append("[");
539         while (flags != 0) {
540             final int flagBit = (1 << Integer.numberOfTrailingZeros(flags));
541             stringBuilder.append(flagToString(flagBit));
542             flags &= ~flagBit;
543             if (flags != 0) {
544                 stringBuilder.append(", ");
545             }
546         }
547         stringBuilder.append("]");
548     }
549 
550     /**
551      * Returns the string representation of a feedback type. For example,
552      * {@link #FEEDBACK_SPOKEN} is represented by the string FEEDBACK_SPOKEN.
553      *
554      * @param feedbackType The feedback type.
555      * @return The string representation.
556      */
feedbackTypeToString(int feedbackType)557     public static String feedbackTypeToString(int feedbackType) {
558         StringBuilder builder = new StringBuilder();
559         builder.append("[");
560         while (feedbackType != 0) {
561             final int feedbackTypeFlag = 1 << Integer.numberOfTrailingZeros(feedbackType);
562             feedbackType &= ~feedbackTypeFlag;
563             switch (feedbackTypeFlag) {
564                 case FEEDBACK_AUDIBLE:
565                     if (builder.length() > 1) {
566                         builder.append(", ");
567                     }
568                     builder.append("FEEDBACK_AUDIBLE");
569                     break;
570                 case FEEDBACK_HAPTIC:
571                     if (builder.length() > 1) {
572                         builder.append(", ");
573                     }
574                     builder.append("FEEDBACK_HAPTIC");
575                     break;
576                 case FEEDBACK_GENERIC:
577                     if (builder.length() > 1) {
578                         builder.append(", ");
579                     }
580                     builder.append("FEEDBACK_GENERIC");
581                     break;
582                 case FEEDBACK_SPOKEN:
583                     if (builder.length() > 1) {
584                         builder.append(", ");
585                     }
586                     builder.append("FEEDBACK_SPOKEN");
587                     break;
588                 case FEEDBACK_VISUAL:
589                     if (builder.length() > 1) {
590                         builder.append(", ");
591                     }
592                     builder.append("FEEDBACK_VISUAL");
593                     break;
594             }
595         }
596         builder.append("]");
597         return builder.toString();
598     }
599 
600     /**
601      * Returns the string representation of a flag. For example,
602      * {@link #DEFAULT} is represented by the string DEFAULT.
603      *
604      * @param flag The flag.
605      * @return The string representation.
606      */
flagToString(int flag)607     public static String flagToString(int flag) {
608         switch (flag) {
609             case DEFAULT:
610                 return "DEFAULT";
611             case FLAG_INCLUDE_NOT_IMPORTANT_VIEWS:
612                 return "FLAG_INCLUDE_NOT_IMPORTANT_VIEWS";
613             case FLAG_REQUEST_TOUCH_EXPLORATION_MODE:
614                 return "FLAG_REQUEST_TOUCH_EXPLORATION_MODE";
615             default:
616                 return null;
617         }
618     }
619 
620     /**
621      * @see Parcelable.Creator
622      */
623     public static final Parcelable.Creator<AccessibilityServiceInfo> CREATOR =
624             new Parcelable.Creator<AccessibilityServiceInfo>() {
625         public AccessibilityServiceInfo createFromParcel(Parcel parcel) {
626             AccessibilityServiceInfo info = new AccessibilityServiceInfo();
627             info.initFromParcel(parcel);
628             return info;
629         }
630 
631         public AccessibilityServiceInfo[] newArray(int size) {
632             return new AccessibilityServiceInfo[size];
633         }
634     };
635 }
636