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