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