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