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