• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 package android.view.contentcapture;
17 
18 import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
19 import static android.view.contentcapture.ContentCaptureManager.DEBUG;
20 import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID;
21 
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.SystemApi;
26 import android.graphics.Insets;
27 import android.os.Parcel;
28 import android.os.Parcelable;
29 import android.text.Selection;
30 import android.text.Spannable;
31 import android.text.SpannableString;
32 import android.util.Log;
33 import android.view.autofill.AutofillId;
34 import android.view.inputmethod.BaseInputConnection;
35 
36 import com.android.internal.util.Preconditions;
37 
38 import java.io.PrintWriter;
39 import java.lang.annotation.Retention;
40 import java.lang.annotation.RetentionPolicy;
41 import java.util.ArrayList;
42 import java.util.List;
43 
44 /** @hide */
45 @SystemApi
46 public final class ContentCaptureEvent implements Parcelable {
47 
48     private static final String TAG = ContentCaptureEvent.class.getSimpleName();
49 
50     /** @hide */
51     public static final int TYPE_SESSION_FINISHED = -2;
52     /** @hide */
53     public static final int TYPE_SESSION_STARTED = -1;
54 
55     /**
56      * Called when a node has been added to the screen and is visible to the user.
57      *
58      * <p>The metadata of the node is available through {@link #getViewNode()}.
59      */
60     public static final int TYPE_VIEW_APPEARED = 1;
61 
62     /**
63      * Called when one or more nodes have been removed from the screen and is not visible to the
64      * user anymore.
65      *
66      * <p>To get the id(s), first call {@link #getIds()} - if it returns {@code null}, then call
67      * {@link #getId()}.
68      */
69     public static final int TYPE_VIEW_DISAPPEARED = 2;
70 
71     /**
72      * Called when the text of a node has been changed.
73      *
74      * <p>The id of the node is available through {@link #getId()}, and the new text is
75      * available through {@link #getText()}.
76      */
77     public static final int TYPE_VIEW_TEXT_CHANGED = 3;
78 
79     /**
80      * Called before events (such as {@link #TYPE_VIEW_APPEARED} and/or
81      * {@link #TYPE_VIEW_DISAPPEARED}) representing a view hierarchy are sent.
82      *
83      * <p><b>NOTE</b>: there is no guarantee this event will be sent. For example, it's not sent
84      * if the initial view hierarchy doesn't initially have any view that's important for content
85      * capture.
86      */
87     public static final int TYPE_VIEW_TREE_APPEARING = 4;
88 
89     /**
90      * Called after events (such as {@link #TYPE_VIEW_APPEARED} and/or
91      * {@link #TYPE_VIEW_DISAPPEARED}) representing a view hierarchy were sent.
92      *
93      * <p><b>NOTE</b>: there is no guarantee this event will be sent. For example, it's not sent
94      * if the initial view hierarchy doesn't initially have any view that's important for content
95      * capture.
96      */
97     public static final int TYPE_VIEW_TREE_APPEARED = 5;
98 
99     /**
100      * Called after a call to
101      * {@link ContentCaptureSession#setContentCaptureContext(ContentCaptureContext)}.
102      *
103      * <p>The passed context is available through {@link #getContentCaptureContext()}.
104      */
105     public static final int TYPE_CONTEXT_UPDATED = 6;
106 
107     /**
108      * Called after the session is ready, typically after the activity resumed and the
109      * initial views appeared
110      */
111     public static final int TYPE_SESSION_RESUMED = 7;
112 
113     /**
114      * Called after the session is paused, typically after the activity paused and the
115      * views disappeared.
116      */
117     public static final int TYPE_SESSION_PAUSED = 8;
118 
119     /**
120      * Called when the view's insets are changed. The new insets associated with the
121      * event may then be retrieved by calling {@link #getInsets()}
122      */
123     public static final int TYPE_VIEW_INSETS_CHANGED = 9;
124 
125     /** @hide */
126     @IntDef(prefix = { "TYPE_" }, value = {
127             TYPE_VIEW_APPEARED,
128             TYPE_VIEW_DISAPPEARED,
129             TYPE_VIEW_TEXT_CHANGED,
130             TYPE_VIEW_TREE_APPEARING,
131             TYPE_VIEW_TREE_APPEARED,
132             TYPE_CONTEXT_UPDATED,
133             TYPE_SESSION_PAUSED,
134             TYPE_SESSION_RESUMED,
135             TYPE_VIEW_INSETS_CHANGED
136     })
137     @Retention(RetentionPolicy.SOURCE)
138     public @interface EventType{}
139 
140     /** @hide */
141     public static final int MAX_INVALID_VALUE = -1;
142 
143     private final int mSessionId;
144     private final int mType;
145     private final long mEventTime;
146     private @Nullable AutofillId mId;
147     private @Nullable ArrayList<AutofillId> mIds;
148     private @Nullable ViewNode mNode;
149     private @Nullable CharSequence mText;
150     private int mParentSessionId = NO_SESSION_ID;
151     private @Nullable ContentCaptureContext mClientContext;
152     private @Nullable Insets mInsets;
153 
154     private int mComposingStart = MAX_INVALID_VALUE;
155     private int mComposingEnd = MAX_INVALID_VALUE;
156     private int mSelectionStartIndex = MAX_INVALID_VALUE;
157     private int mSelectionEndIndex = MAX_INVALID_VALUE;
158 
159     /** Only used in the main Content Capture session, no need to parcel */
160     private boolean mTextHasComposingSpan;
161 
162     /** @hide */
ContentCaptureEvent(int sessionId, int type, long eventTime)163     public ContentCaptureEvent(int sessionId, int type, long eventTime) {
164         mSessionId = sessionId;
165         mType = type;
166         mEventTime = eventTime;
167     }
168 
169     /** @hide */
ContentCaptureEvent(int sessionId, int type)170     public ContentCaptureEvent(int sessionId, int type) {
171         this(sessionId, type, System.currentTimeMillis());
172     }
173 
174     /** @hide */
setAutofillId(@onNull AutofillId id)175     public ContentCaptureEvent setAutofillId(@NonNull AutofillId id) {
176         mId = Preconditions.checkNotNull(id);
177         return this;
178     }
179 
180     /** @hide */
setAutofillIds(@onNull ArrayList<AutofillId> ids)181     public ContentCaptureEvent setAutofillIds(@NonNull ArrayList<AutofillId> ids) {
182         mIds = Preconditions.checkNotNull(ids);
183         return this;
184     }
185 
186     /**
187      * Adds an autofill id to the this event, merging the single id into a list if necessary.
188      *
189      * @hide
190      */
addAutofillId(@onNull AutofillId id)191     public ContentCaptureEvent addAutofillId(@NonNull AutofillId id) {
192         Preconditions.checkNotNull(id);
193         if (mIds == null) {
194             mIds = new ArrayList<>();
195             if (mId == null) {
196                 Log.w(TAG, "addAutofillId(" + id + ") called without an initial id");
197             } else {
198                 mIds.add(mId);
199                 mId = null;
200             }
201         }
202         mIds.add(id);
203         return this;
204     }
205 
206     /**
207      * Used by {@link #TYPE_SESSION_STARTED} and {@link #TYPE_SESSION_FINISHED}.
208      *
209      * @hide
210      */
setParentSessionId(int parentSessionId)211     public ContentCaptureEvent setParentSessionId(int parentSessionId) {
212         mParentSessionId = parentSessionId;
213         return this;
214     }
215 
216     /**
217      * Used by {@link #TYPE_SESSION_STARTED} and {@link #TYPE_SESSION_FINISHED}.
218      *
219      * @hide
220      */
setClientContext(@onNull ContentCaptureContext clientContext)221     public ContentCaptureEvent setClientContext(@NonNull ContentCaptureContext clientContext) {
222         mClientContext = clientContext;
223         return this;
224     }
225 
226     /** @hide */
227     @NonNull
getSessionId()228     public int getSessionId() {
229         return mSessionId;
230     }
231 
232     /**
233      * Used by {@link #TYPE_SESSION_STARTED} and {@link #TYPE_SESSION_FINISHED}.
234      *
235      * @hide
236      */
237     @Nullable
getParentSessionId()238     public int getParentSessionId() {
239         return mParentSessionId;
240     }
241 
242     /**
243      * Gets the {@link ContentCaptureContext} set calls to
244      * {@link ContentCaptureSession#setContentCaptureContext(ContentCaptureContext)}.
245      *
246      * <p>Only set on {@link #TYPE_CONTEXT_UPDATED} events.
247      */
248     @Nullable
getContentCaptureContext()249     public ContentCaptureContext getContentCaptureContext() {
250         return mClientContext;
251     }
252 
253     /** @hide */
254     @NonNull
setViewNode(@onNull ViewNode node)255     public ContentCaptureEvent setViewNode(@NonNull ViewNode node) {
256         mNode = Preconditions.checkNotNull(node);
257         return this;
258     }
259 
260     /** @hide */
261     @NonNull
setText(@ullable CharSequence text)262     public ContentCaptureEvent setText(@Nullable CharSequence text) {
263         mText = text;
264         return this;
265     }
266 
267     /** @hide */
268     @NonNull
setComposingIndex(int start, int end)269     public ContentCaptureEvent setComposingIndex(int start, int end) {
270         mComposingStart = start;
271         mComposingEnd = end;
272         return this;
273     }
274 
275     /** @hide */
276     @NonNull
hasComposingSpan()277     public boolean hasComposingSpan() {
278         return mComposingStart > MAX_INVALID_VALUE;
279     }
280 
281     /** @hide */
282     @NonNull
setSelectionIndex(int start, int end)283     public ContentCaptureEvent setSelectionIndex(int start, int end) {
284         mSelectionStartIndex = start;
285         mSelectionEndIndex = end;
286         return this;
287     }
288 
hasSameComposingSpan(@onNull ContentCaptureEvent other)289     boolean hasSameComposingSpan(@NonNull ContentCaptureEvent other) {
290         return mComposingStart == other.mComposingStart && mComposingEnd == other.mComposingEnd;
291     }
292 
hasSameSelectionSpan(@onNull ContentCaptureEvent other)293     boolean hasSameSelectionSpan(@NonNull ContentCaptureEvent other) {
294         return mSelectionStartIndex == other.mSelectionStartIndex
295                 && mSelectionEndIndex == other.mSelectionEndIndex;
296     }
297 
getComposingStart()298     private int getComposingStart() {
299         return mComposingStart;
300     }
301 
getComposingEnd()302     private int getComposingEnd() {
303         return mComposingEnd;
304     }
305 
getSelectionStart()306     private int getSelectionStart() {
307         return mSelectionStartIndex;
308     }
309 
getSelectionEnd()310     private int getSelectionEnd() {
311         return mSelectionEndIndex;
312     }
313 
restoreComposingSpan()314     private void restoreComposingSpan() {
315         if (mComposingStart <= MAX_INVALID_VALUE
316                 || mComposingEnd <= MAX_INVALID_VALUE) {
317             return;
318         }
319         if (mText instanceof Spannable) {
320             BaseInputConnection.setComposingSpans((Spannable) mText, mComposingStart,
321                     mComposingEnd);
322         } else {
323             Log.w(TAG, "Text is not a Spannable.");
324         }
325     }
326 
restoreSelectionSpans()327     private void restoreSelectionSpans() {
328         if (mSelectionStartIndex <= MAX_INVALID_VALUE
329                 || mSelectionEndIndex <= MAX_INVALID_VALUE) {
330             return;
331         }
332 
333         if (mText instanceof SpannableString) {
334             SpannableString ss = (SpannableString) mText;
335             ss.setSpan(Selection.SELECTION_START, mSelectionStartIndex, mSelectionStartIndex, 0);
336             ss.setSpan(Selection.SELECTION_END, mSelectionEndIndex, mSelectionEndIndex, 0);
337         } else {
338             Log.w(TAG, "Text is not a SpannableString.");
339         }
340     }
341 
342     /** @hide */
343     @NonNull
setInsets(@onNull Insets insets)344     public ContentCaptureEvent setInsets(@NonNull Insets insets) {
345         mInsets = insets;
346         return this;
347     }
348 
349     /**
350      * Gets the type of the event.
351      *
352      * @return one of {@link #TYPE_VIEW_APPEARED}, {@link #TYPE_VIEW_DISAPPEARED},
353      * {@link #TYPE_VIEW_TEXT_CHANGED}, {@link #TYPE_VIEW_TREE_APPEARING},
354      * {@link #TYPE_VIEW_TREE_APPEARED}, {@link #TYPE_CONTEXT_UPDATED},
355      * {@link #TYPE_SESSION_RESUMED}, or {@link #TYPE_SESSION_PAUSED}.
356      */
getType()357     public @EventType int getType() {
358         return mType;
359     }
360 
361     /**
362      * Gets when the event was generated, in millis since epoch.
363      */
getEventTime()364     public long getEventTime() {
365         return mEventTime;
366     }
367 
368     /**
369      * Gets the whole metadata of the node associated with the event.
370      *
371      * <p>Only set on {@link #TYPE_VIEW_APPEARED} events.
372      */
373     @Nullable
getViewNode()374     public ViewNode getViewNode() {
375         return mNode;
376     }
377 
378     /**
379      * Gets the {@link AutofillId} of the node associated with the event.
380      *
381      * <p>Only set on {@link #TYPE_VIEW_DISAPPEARED} (when the event contains just one node - if
382      * it contains more than one, this method returns {@code null} and the actual ids should be
383      * retrived by {@link #getIds()}) and {@link #TYPE_VIEW_TEXT_CHANGED} events.
384      */
385     @Nullable
getId()386     public AutofillId getId() {
387         return mId;
388     }
389 
390     /**
391      * Gets the {@link AutofillId AutofillIds} of the nodes associated with the event.
392      *
393      * <p>Only set on {@link #TYPE_VIEW_DISAPPEARED}, when the event contains more than one node
394      * (if it contains just one node, it's returned by {@link #getId()} instead.
395      */
396     @Nullable
getIds()397     public List<AutofillId> getIds() {
398         return mIds;
399     }
400 
401     /**
402      * Gets the current text of the node associated with the event.
403      *
404      * <p>Only set on {@link #TYPE_VIEW_TEXT_CHANGED} events.
405      */
406     @Nullable
getText()407     public CharSequence getText() {
408         return mText;
409     }
410 
411     /**
412      * Gets the rectangle of the insets associated with the event. Valid insets will only be
413      * returned if the type of the event is {@link #TYPE_VIEW_INSETS_CHANGED}, otherwise they
414      * will be null.
415      */
416     @Nullable
getInsets()417     public Insets getInsets() {
418         return mInsets;
419     }
420 
421     /**
422      * Merges event of the same type, either {@link #TYPE_VIEW_TEXT_CHANGED}
423      * or {@link #TYPE_VIEW_DISAPPEARED}.
424      *
425      * @hide
426      */
mergeEvent(@onNull ContentCaptureEvent event)427     public void mergeEvent(@NonNull ContentCaptureEvent event) {
428         Preconditions.checkNotNull(event);
429         final int eventType = event.getType();
430         if (mType != eventType) {
431             Log.e(TAG, "mergeEvent(" + getTypeAsString(eventType) + ") cannot be merged "
432                     + "with different eventType=" + getTypeAsString(mType));
433             return;
434         }
435 
436         if (eventType == TYPE_VIEW_DISAPPEARED) {
437             final List<AutofillId> ids = event.getIds();
438             final AutofillId id = event.getId();
439             if (ids != null) {
440                 if (id != null) {
441                     Log.w(TAG, "got TYPE_VIEW_DISAPPEARED event with both id and ids: " + event);
442                 }
443                 for (int i = 0; i < ids.size(); i++) {
444                     addAutofillId(ids.get(i));
445                 }
446                 return;
447             }
448             if (id != null) {
449                 addAutofillId(id);
450                 return;
451             }
452             throw new IllegalArgumentException("mergeEvent(): got "
453                     + "TYPE_VIEW_DISAPPEARED event with neither id or ids: " + event);
454         } else if (eventType == TYPE_VIEW_TEXT_CHANGED) {
455             setText(event.getText());
456             setComposingIndex(event.getComposingStart(), event.getComposingEnd());
457             setSelectionIndex(event.getSelectionStart(), event.getSelectionEnd());
458         } else {
459             Log.e(TAG, "mergeEvent(" + getTypeAsString(eventType)
460                     + ") does not support this event type.");
461         }
462     }
463 
464     /** @hide */
dump(@onNull PrintWriter pw)465     public void dump(@NonNull PrintWriter pw) {
466         pw.print("type="); pw.print(getTypeAsString(mType));
467         pw.print(", time="); pw.print(mEventTime);
468         if (mId != null) {
469             pw.print(", id="); pw.print(mId);
470         }
471         if (mIds != null) {
472             pw.print(", ids="); pw.print(mIds);
473         }
474         if (mNode != null) {
475             pw.print(", mNode.id="); pw.print(mNode.getAutofillId());
476         }
477         if (mSessionId != NO_SESSION_ID) {
478             pw.print(", sessionId="); pw.print(mSessionId);
479         }
480         if (mParentSessionId != NO_SESSION_ID) {
481             pw.print(", parentSessionId="); pw.print(mParentSessionId);
482         }
483         if (mText != null) {
484             pw.print(", text="); pw.println(getSanitizedString(mText));
485         }
486         if (mClientContext != null) {
487             pw.print(", context="); mClientContext.dump(pw); pw.println();
488         }
489         if (mInsets != null) {
490             pw.print(", insets="); pw.println(mInsets);
491         }
492         if (mComposingStart > MAX_INVALID_VALUE) {
493             pw.print(", composing("); pw.print(mComposingStart);
494             pw.print(", "); pw.print(mComposingEnd); pw.print(")");
495         }
496         if (mSelectionStartIndex > MAX_INVALID_VALUE) {
497             pw.print(", selection("); pw.print(mSelectionStartIndex);
498             pw.print(", "); pw.print(mSelectionEndIndex); pw.print(")");
499         }
500     }
501 
502     @NonNull
503     @Override
toString()504     public String toString() {
505         final StringBuilder string = new StringBuilder("ContentCaptureEvent[type=")
506                 .append(getTypeAsString(mType));
507         string.append(", session=").append(mSessionId);
508         if (mType == TYPE_SESSION_STARTED && mParentSessionId != NO_SESSION_ID) {
509             string.append(", parent=").append(mParentSessionId);
510         }
511         if (mId != null) {
512             string.append(", id=").append(mId);
513         }
514         if (mIds != null) {
515             string.append(", ids=").append(mIds);
516         }
517         if (mNode != null) {
518             final String className = mNode.getClassName();
519             string.append(", class=").append(className);
520             string.append(", id=").append(mNode.getAutofillId());
521             if (mNode.getText() != null) {
522                 string.append(", text=")
523                         .append(DEBUG ? mNode.getText() : getSanitizedString(mNode.getText()));
524             }
525         }
526         if (mText != null) {
527             string.append(", text=")
528                     .append(DEBUG ? mText : getSanitizedString(mText));
529         }
530         if (mClientContext != null) {
531             string.append(", context=").append(mClientContext);
532         }
533         if (mInsets != null) {
534             string.append(", insets=").append(mInsets);
535         }
536         if (mComposingStart > MAX_INVALID_VALUE) {
537             string.append(", composing=[")
538                     .append(mComposingStart).append(",").append(mComposingEnd).append("]");
539         }
540         if (mSelectionStartIndex > MAX_INVALID_VALUE) {
541             string.append(", selection=[")
542                     .append(mSelectionStartIndex).append(",")
543                     .append(mSelectionEndIndex).append("]");
544         }
545         return string.append(']').toString();
546     }
547 
548     @Override
describeContents()549     public int describeContents() {
550         return 0;
551     }
552 
553     @Override
writeToParcel(Parcel parcel, int flags)554     public void writeToParcel(Parcel parcel, int flags) {
555         parcel.writeInt(mSessionId);
556         parcel.writeInt(mType);
557         parcel.writeLong(mEventTime);
558         parcel.writeParcelable(mId, flags);
559         parcel.writeTypedList(mIds);
560         ViewNode.writeToParcel(parcel, mNode, flags);
561         parcel.writeCharSequence(mText);
562         if (mType == TYPE_SESSION_STARTED || mType == TYPE_SESSION_FINISHED) {
563             parcel.writeInt(mParentSessionId);
564         }
565         if (mType == TYPE_SESSION_STARTED || mType == TYPE_CONTEXT_UPDATED) {
566             parcel.writeParcelable(mClientContext, flags);
567         }
568         if (mType == TYPE_VIEW_INSETS_CHANGED) {
569             parcel.writeParcelable(mInsets, flags);
570         }
571         if (mType == TYPE_VIEW_TEXT_CHANGED) {
572             parcel.writeInt(mComposingStart);
573             parcel.writeInt(mComposingEnd);
574             parcel.writeInt(mSelectionStartIndex);
575             parcel.writeInt(mSelectionEndIndex);
576         }
577     }
578 
579     public static final @android.annotation.NonNull Parcelable.Creator<ContentCaptureEvent> CREATOR =
580             new Parcelable.Creator<ContentCaptureEvent>() {
581 
582         @Override
583         @NonNull
584         public ContentCaptureEvent createFromParcel(Parcel parcel) {
585             final int sessionId = parcel.readInt();
586             final int type = parcel.readInt();
587             final long eventTime  = parcel.readLong();
588             final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, type, eventTime);
589             final AutofillId id = parcel.readParcelable(null);
590             if (id != null) {
591                 event.setAutofillId(id);
592             }
593             final ArrayList<AutofillId> ids = parcel.createTypedArrayList(AutofillId.CREATOR);
594             if (ids != null) {
595                 event.setAutofillIds(ids);
596             }
597             final ViewNode node = ViewNode.readFromParcel(parcel);
598             if (node != null) {
599                 event.setViewNode(node);
600             }
601             event.setText(parcel.readCharSequence());
602             if (type == TYPE_SESSION_STARTED || type == TYPE_SESSION_FINISHED) {
603                 event.setParentSessionId(parcel.readInt());
604             }
605             if (type == TYPE_SESSION_STARTED || type == TYPE_CONTEXT_UPDATED) {
606                 event.setClientContext(parcel.readParcelable(null));
607             }
608             if (type == TYPE_VIEW_INSETS_CHANGED) {
609                 event.setInsets(parcel.readParcelable(null));
610             }
611             if (type == TYPE_VIEW_TEXT_CHANGED) {
612                 event.setComposingIndex(parcel.readInt(), parcel.readInt());
613                 event.restoreComposingSpan();
614                 event.setSelectionIndex(parcel.readInt(), parcel.readInt());
615                 event.restoreSelectionSpans();
616             }
617             return event;
618         }
619 
620         @Override
621         @NonNull
622         public ContentCaptureEvent[] newArray(int size) {
623             return new ContentCaptureEvent[size];
624         }
625     };
626 
627     /** @hide */
getTypeAsString(@ventType int type)628     public static String getTypeAsString(@EventType int type) {
629         switch (type) {
630             case TYPE_SESSION_STARTED:
631                 return "SESSION_STARTED";
632             case TYPE_SESSION_FINISHED:
633                 return "SESSION_FINISHED";
634             case TYPE_SESSION_RESUMED:
635                 return "SESSION_RESUMED";
636             case TYPE_SESSION_PAUSED:
637                 return "SESSION_PAUSED";
638             case TYPE_VIEW_APPEARED:
639                 return "VIEW_APPEARED";
640             case TYPE_VIEW_DISAPPEARED:
641                 return "VIEW_DISAPPEARED";
642             case TYPE_VIEW_TEXT_CHANGED:
643                 return "VIEW_TEXT_CHANGED";
644             case TYPE_VIEW_TREE_APPEARING:
645                 return "VIEW_TREE_APPEARING";
646             case TYPE_VIEW_TREE_APPEARED:
647                 return "VIEW_TREE_APPEARED";
648             case TYPE_CONTEXT_UPDATED:
649                 return "CONTEXT_UPDATED";
650             case TYPE_VIEW_INSETS_CHANGED:
651                 return "VIEW_INSETS_CHANGED";
652             default:
653                 return "UKNOWN_TYPE: " + type;
654         }
655     }
656 }
657