• 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.ContentCaptureManager.NO_SESSION_ID;
19 
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.SystemApi;
24 import android.app.TaskInfo;
25 import android.app.assist.ActivityId;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.LocusId;
29 import android.os.Bundle;
30 import android.os.Parcel;
31 import android.os.Parcelable;
32 import android.view.Display;
33 import android.view.View;
34 
35 import com.android.internal.util.Preconditions;
36 
37 import java.io.PrintWriter;
38 import java.lang.annotation.Retention;
39 import java.lang.annotation.RetentionPolicy;
40 import java.util.Objects;
41 
42 /**
43  * Context associated with a {@link ContentCaptureSession} - see {@link ContentCaptureManager} for
44  * more info.
45  */
46 public final class ContentCaptureContext implements Parcelable {
47 
48     /*
49      * IMPLEMENTATION NOTICE:
50      *
51      * This object contains both the info that's explicitly added by apps (hence it's public), but
52      * it also contains info injected by the server (and are accessible through @SystemApi methods).
53      */
54 
55     /**
56      * Flag used to indicate that the app explicitly disabled content capture for the activity
57      * (using {@link ContentCaptureManager#setContentCaptureEnabled(boolean)}),
58      * in which case the service will just receive activity-level events.
59      *
60      * @hide
61      */
62     @SystemApi
63     public static final int FLAG_DISABLED_BY_APP = 0x1;
64 
65     /**
66      * Flag used to indicate that the activity's window is tagged with
67      * {@link android.view.Display#FLAG_SECURE}, in which case the service will just receive
68      * activity-level events.
69      *
70      * @hide
71      */
72     @SystemApi
73     public static final int FLAG_DISABLED_BY_FLAG_SECURE = 0x2;
74 
75     /**
76      * Flag used when the event is sent because the Android System reconnected to the service (for
77      * example, after its process died).
78      *
79      * @hide
80      */
81     @SystemApi
82     public static final int FLAG_RECONNECTED = 0x4;
83 
84     /** @hide */
85     @IntDef(flag = true, prefix = { "FLAG_" }, value = {
86             FLAG_DISABLED_BY_APP,
87             FLAG_DISABLED_BY_FLAG_SECURE,
88             FLAG_RECONNECTED
89     })
90     @Retention(RetentionPolicy.SOURCE)
91     @interface ContextCreationFlags{}
92 
93     /**
94      * Flag indicating if this object has the app-provided context (which is set on
95      * {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}).
96      */
97     private final boolean mHasClientContext;
98 
99     // Fields below are set by app on Builder
100     private final @Nullable Bundle mExtras;
101     private final @Nullable LocusId mId;
102 
103     // Fields below are set by server when the session starts
104     private final @Nullable ComponentName mComponentName;
105     private final int mFlags;
106     private final int mDisplayId;
107     private final ActivityId mActivityId;
108 
109     // Fields below are set by the service upon "delivery" and are not marshalled in the parcel
110     private int mParentSessionId = NO_SESSION_ID;
111 
112     /** @hide */
ContentCaptureContext(@ullable ContentCaptureContext clientContext, @NonNull ActivityId activityId, @NonNull ComponentName componentName, int displayId, int flags)113     public ContentCaptureContext(@Nullable ContentCaptureContext clientContext,
114             @NonNull ActivityId activityId, @NonNull ComponentName componentName, int displayId,
115             int flags) {
116         if (clientContext != null) {
117             mHasClientContext = true;
118             mExtras = clientContext.mExtras;
119             mId = clientContext.mId;
120         } else {
121             mHasClientContext = false;
122             mExtras = null;
123             mId = null;
124         }
125         mComponentName = Objects.requireNonNull(componentName);
126         mFlags = flags;
127         mDisplayId = displayId;
128         mActivityId = activityId;
129     }
130 
ContentCaptureContext(@onNull Builder builder)131     private ContentCaptureContext(@NonNull Builder builder) {
132         mHasClientContext = true;
133         mExtras = builder.mExtras;
134         mId = builder.mId;
135 
136         mComponentName  = null;
137         mFlags = 0;
138         mDisplayId = Display.INVALID_DISPLAY;
139         mActivityId = null;
140     }
141 
142     /** @hide */
ContentCaptureContext(@ullable ContentCaptureContext original, int extraFlags)143     public ContentCaptureContext(@Nullable ContentCaptureContext original, int extraFlags) {
144         mHasClientContext = original.mHasClientContext;
145         mExtras = original.mExtras;
146         mId = original.mId;
147         mComponentName = original.mComponentName;
148         mFlags = original.mFlags | extraFlags;
149         mDisplayId = original.mDisplayId;
150         mActivityId = original.mActivityId;
151     }
152 
153     /**
154      * Gets the (optional) extras set by the app (through {@link Builder#setExtras(Bundle)}).
155      *
156      * <p>It can be used to provide vendor-specific data that can be modified and examined.
157      */
158     @Nullable
getExtras()159     public Bundle getExtras() {
160         return mExtras;
161     }
162 
163     /**
164      * Gets the context id.
165      */
166     @Nullable
getLocusId()167     public LocusId getLocusId() {
168         return mId;
169     }
170 
171     /**
172      * Gets the id of the {@link TaskInfo task} associated with this context.
173      *
174      * @hide
175      */
176     @SystemApi
getTaskId()177     public int getTaskId() {
178         return mHasClientContext ? 0 : mActivityId.getTaskId();
179     }
180 
181     /**
182      * Gets the activity associated with this context, or {@code null} when it is a child session.
183      *
184      * @hide
185      */
186     @SystemApi
getActivityComponent()187     public @Nullable ComponentName getActivityComponent() {
188         return mComponentName;
189     }
190 
191     /**
192      * Gets the Activity id information associated with this context, or {@code null} when it is a
193      * child session.
194      *
195      * @hide
196      */
197     @SystemApi
198     @Nullable
getActivityId()199     public ActivityId getActivityId() {
200         return mHasClientContext ? null : mActivityId;
201     }
202 
203     /**
204      * Gets the id of the session that originated this session (through
205      * {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}),
206      * or {@code null} if this is the main session associated with the Activity's {@link Context}.
207      *
208      * @hide
209      */
210     @SystemApi
getParentSessionId()211     public @Nullable ContentCaptureSessionId getParentSessionId() {
212         return mParentSessionId == NO_SESSION_ID ? null
213                 : new ContentCaptureSessionId(mParentSessionId);
214     }
215 
216     /** @hide */
setParentSessionId(int parentSessionId)217     public void setParentSessionId(int parentSessionId) {
218         mParentSessionId = parentSessionId;
219     }
220 
221     /**
222      * Gets the ID of the display associated with this context, as defined by
223      * {G android.hardware.display.DisplayManager#getDisplay(int) DisplayManager.getDisplay()}.
224      *
225      * @hide
226      */
227     @SystemApi
getDisplayId()228     public int getDisplayId() {
229         return mDisplayId;
230     }
231 
232     /**
233      * Gets the flags associated with this context.
234      *
235      * @return any combination of {@link #FLAG_DISABLED_BY_FLAG_SECURE},
236      * {@link #FLAG_DISABLED_BY_APP} and {@link #FLAG_RECONNECTED}.
237      *
238      * @hide
239      */
240     @SystemApi
getFlags()241     public @ContextCreationFlags int getFlags() {
242         return mFlags;
243     }
244 
245     /**
246      * Helper that creates a {@link ContentCaptureContext} associated with the given {@code id}.
247      */
248     @NonNull
forLocusId(@onNull String id)249     public static ContentCaptureContext forLocusId(@NonNull String id) {
250         return new Builder(new LocusId(id)).build();
251     }
252 
253     /**
254      * Builder for {@link ContentCaptureContext} objects.
255      */
256     public static final class Builder {
257         private Bundle mExtras;
258         private final LocusId mId;
259         private boolean mDestroyed;
260 
261         /**
262          * Creates a new builder.
263          *
264          * <p>The context must have an id, which is usually one of the following:
265          *
266          * <ul>
267          *   <li>A URL representing a web page (or {@code IFRAME}) that's being rendered by the
268          *   activity (See {@link View#setContentCaptureSession(ContentCaptureSession)} for an
269          *   example).
270          *   <li>A unique identifier of the application state (for example, a conversation between
271          *   2 users in a chat app).
272          * </ul>
273          *
274          * <p>See {@link ContentCaptureManager} for more info about the content capture context.
275          *
276          * @param id id associated with this context.
277          */
Builder(@onNull LocusId id)278         public Builder(@NonNull LocusId id) {
279             mId = Preconditions.checkNotNull(id);
280         }
281 
282         /**
283          * Sets extra options associated with this context.
284          *
285          * <p>It can be used to provide vendor-specific data that can be modified and examined.
286          *
287          * @param extras extra options.
288          * @return this builder.
289          *
290          * @throws IllegalStateException if {@link #build()} was already called.
291          */
292         @NonNull
setExtras(@onNull Bundle extras)293         public Builder setExtras(@NonNull Bundle extras) {
294             mExtras = Preconditions.checkNotNull(extras);
295             throwIfDestroyed();
296             return this;
297         }
298 
299         /**
300          * Builds the {@link ContentCaptureContext}.
301          *
302          * @throws IllegalStateException if {@link #build()} was already called.
303          *
304          * @return the built {@code ContentCaptureContext}
305          */
306         @NonNull
build()307         public ContentCaptureContext build() {
308             throwIfDestroyed();
309             mDestroyed = true;
310             return new ContentCaptureContext(this);
311         }
312 
throwIfDestroyed()313         private void throwIfDestroyed() {
314             Preconditions.checkState(!mDestroyed, "Already called #build()");
315         }
316     }
317 
318     /**
319      * @hide
320      */
321     // TODO(b/111276913): dump to proto as well
dump(PrintWriter pw)322     public void dump(PrintWriter pw) {
323         if (mComponentName != null) {
324             pw.print("activity="); pw.print(mComponentName.flattenToShortString());
325         }
326         if (mId != null) {
327             pw.print(", id="); mId.dump(pw);
328         }
329         pw.print(", activityId="); pw.print(mActivityId);
330         pw.print(", displayId="); pw.print(mDisplayId);
331         if (mParentSessionId != NO_SESSION_ID) {
332             pw.print(", parentId="); pw.print(mParentSessionId);
333         }
334         if (mFlags > 0) {
335             pw.print(", flags="); pw.print(mFlags);
336         }
337         if (mExtras != null) {
338             // NOTE: cannot dump because it could contain PII
339             pw.print(", hasExtras");
340         }
341     }
342 
fromServer()343     private boolean fromServer() {
344         return mComponentName != null;
345     }
346 
347     @Override
toString()348     public String toString() {
349         final StringBuilder builder = new StringBuilder("Context[");
350 
351         if (fromServer()) {
352             builder.append("act=").append(ComponentName.flattenToShortString(mComponentName))
353                 .append(", activityId=").append(mActivityId)
354                 .append(", displayId=").append(mDisplayId)
355                 .append(", flags=").append(mFlags);
356         } else {
357             builder.append("id=").append(mId);
358             if (mExtras != null) {
359                 // NOTE: cannot print because it could contain PII
360                 builder.append(", hasExtras");
361             }
362         }
363         if (mParentSessionId != NO_SESSION_ID) {
364             builder.append(", parentId=").append(mParentSessionId);
365         }
366         return builder.append(']').toString();
367     }
368 
369     @Override
describeContents()370     public int describeContents() {
371         return 0;
372     }
373 
374     @Override
writeToParcel(Parcel parcel, int flags)375     public void writeToParcel(Parcel parcel, int flags) {
376         parcel.writeInt(mHasClientContext ? 1 : 0);
377         if (mHasClientContext) {
378             parcel.writeParcelable(mId, flags);
379             parcel.writeBundle(mExtras);
380         }
381         parcel.writeParcelable(mComponentName, flags);
382         if (fromServer()) {
383             parcel.writeInt(mDisplayId);
384             parcel.writeInt(mFlags);
385             mActivityId.writeToParcel(parcel, flags);
386         }
387     }
388 
389     public static final @android.annotation.NonNull Parcelable.Creator<ContentCaptureContext> CREATOR =
390             new Parcelable.Creator<ContentCaptureContext>() {
391 
392         @Override
393         @NonNull
394         public ContentCaptureContext createFromParcel(Parcel parcel) {
395             final boolean hasClientContext = parcel.readInt() == 1;
396 
397             final ContentCaptureContext clientContext;
398             if (hasClientContext) {
399                 // Must reconstruct the client context using the Builder API
400                 final LocusId id = parcel.readParcelable(null);
401                 final Bundle extras = parcel.readBundle();
402                 final Builder builder = new Builder(id);
403                 if (extras != null) builder.setExtras(extras);
404                 clientContext = new ContentCaptureContext(builder);
405             } else {
406                 clientContext = null;
407             }
408             final ComponentName componentName = parcel.readParcelable(null);
409             if (componentName == null) {
410                 // Client-state only
411                 return clientContext;
412             } else {
413                 final int displayId = parcel.readInt();
414                 final int flags = parcel.readInt();
415                 final ActivityId activityId = new ActivityId(parcel);
416 
417                 return new ContentCaptureContext(clientContext, activityId, componentName,
418                         displayId, flags);
419             }
420         }
421 
422         @Override
423         @NonNull
424         public ContentCaptureContext[] newArray(int size) {
425             return new ContentCaptureContext[size];
426         }
427     };
428 }
429