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