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