1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
|
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view.contentcapture;
import static android.view.contentcapture.ContentCaptureSession.NO_SESSION_ID;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.LocusId;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.Display;
import android.view.View;
import com.android.internal.util.Preconditions;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Context associated with a {@link ContentCaptureSession} - see {@link ContentCaptureManager} for
* more info.
*/
public final class ContentCaptureContext implements Parcelable {
/*
* IMPLEMENTATION NOTICE:
*
* This object contains both the info that's explicitly added by apps (hence it's public), but
* it also contains info injected by the server (and are accessible through @SystemApi methods).
*/
/**
* Flag used to indicate that the app explicitly disabled content capture for the activity
* (using {@link ContentCaptureManager#setContentCaptureEnabled(boolean)}),
* in which case the service will just receive activity-level events.
*
* @hide
*/
@SystemApi
@TestApi
public static final int FLAG_DISABLED_BY_APP = 0x1;
/**
* Flag used to indicate that the activity's window is tagged with
* {@link android.view.Display#FLAG_SECURE}, in which case the service will just receive
* activity-level events.
*
* @hide
*/
@SystemApi
@TestApi
public static final int FLAG_DISABLED_BY_FLAG_SECURE = 0x2;
/**
* Flag used when the event is sent because the Android System reconnected to the service (for
* example, after its process died).
*
* @hide
*/
@SystemApi
@TestApi
public static final int FLAG_RECONNECTED = 0x4;
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
FLAG_DISABLED_BY_APP,
FLAG_DISABLED_BY_FLAG_SECURE,
FLAG_RECONNECTED
})
@Retention(RetentionPolicy.SOURCE)
@interface ContextCreationFlags{}
/**
* Flag indicating if this object has the app-provided context (which is set on
* {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}).
*/
private final boolean mHasClientContext;
// Fields below are set by app on Builder
private final @Nullable Bundle mExtras;
private final @Nullable LocusId mId;
// Fields below are set by server when the session starts
private final @Nullable ComponentName mComponentName;
private final int mTaskId;
private final int mFlags;
private final int mDisplayId;
// Fields below are set by the service upon "delivery" and are not marshalled in the parcel
private int mParentSessionId = NO_SESSION_ID;
/** @hide */
public ContentCaptureContext(@Nullable ContentCaptureContext clientContext,
@NonNull ComponentName componentName, int taskId, int displayId, int flags) {
if (clientContext != null) {
mHasClientContext = true;
mExtras = clientContext.mExtras;
mId = clientContext.mId;
} else {
mHasClientContext = false;
mExtras = null;
mId = null;
}
mComponentName = Preconditions.checkNotNull(componentName);
mTaskId = taskId;
mDisplayId = displayId;
mFlags = flags;
}
private ContentCaptureContext(@NonNull Builder builder) {
mHasClientContext = true;
mExtras = builder.mExtras;
mId = builder.mId;
mComponentName = null;
mTaskId = mFlags = 0;
mDisplayId = Display.INVALID_DISPLAY;
}
/** @hide */
public ContentCaptureContext(@Nullable ContentCaptureContext original, int extraFlags) {
mHasClientContext = original.mHasClientContext;
mExtras = original.mExtras;
mId = original.mId;
mComponentName = original.mComponentName;
mTaskId = original.mTaskId;
mFlags = original.mFlags | extraFlags;
mDisplayId = original.mDisplayId;
}
/**
* Gets the (optional) extras set by the app (through {@link Builder#setExtras(Bundle)}).
*
* <p>It can be used to provide vendor-specific data that can be modified and examined.
*/
@Nullable
public Bundle getExtras() {
return mExtras;
}
/**
* Gets the context id.
*/
@Nullable
public LocusId getLocusId() {
return mId;
}
/**
* Gets the id of the {@link TaskInfo task} associated with this context.
*
* @hide
*/
@SystemApi
@TestApi
public int getTaskId() {
return mTaskId;
}
/**
* Gets the activity associated with this context, or {@code null} when it is a child session.
*
* @hide
*/
@SystemApi
@TestApi
public @Nullable ComponentName getActivityComponent() {
return mComponentName;
}
/**
* Gets the id of the session that originated this session (through
* {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}),
* or {@code null} if this is the main session associated with the Activity's {@link Context}.
*
* @hide
*/
@SystemApi
@TestApi
public @Nullable ContentCaptureSessionId getParentSessionId() {
return mParentSessionId == NO_SESSION_ID ? null
: new ContentCaptureSessionId(mParentSessionId);
}
/** @hide */
public void setParentSessionId(int parentSessionId) {
mParentSessionId = parentSessionId;
}
/**
* Gets the ID of the display associated with this context, as defined by
* {G android.hardware.display.DisplayManager#getDisplay(int) DisplayManager.getDisplay()}.
*
* @hide
*/
@SystemApi
@TestApi
public int getDisplayId() {
return mDisplayId;
}
/**
* Gets the flags associated with this context.
*
* @return any combination of {@link #FLAG_DISABLED_BY_FLAG_SECURE},
* {@link #FLAG_DISABLED_BY_APP} and {@link #FLAG_RECONNECTED}.
*
* @hide
*/
@SystemApi
@TestApi
public @ContextCreationFlags int getFlags() {
return mFlags;
}
/**
* Helper that creates a {@link ContentCaptureContext} associated with the given {@code id}.
*/
@NonNull
public static ContentCaptureContext forLocusId(@NonNull String id) {
return new Builder(new LocusId(id)).build();
}
/**
* Builder for {@link ContentCaptureContext} objects.
*/
public static final class Builder {
private Bundle mExtras;
private final LocusId mId;
private boolean mDestroyed;
/**
* Creates a new builder.
*
* <p>The context must have an id, which is usually one of the following:
*
* <ul>
* <li>A URL representing a web page (or {@code IFRAME}) that's being rendered by the
* activity (See {@link View#setContentCaptureSession(ContentCaptureSession)} for an
* example).
* <li>A unique identifier of the application state (for example, a conversation between
* 2 users in a chat app).
* </ul>
*
* <p>See {@link ContentCaptureManager} for more info about the content capture context.
*
* @param id id associated with this context.
*/
public Builder(@NonNull LocusId id) {
mId = Preconditions.checkNotNull(id);
}
/**
* Sets extra options associated with this context.
*
* <p>It can be used to provide vendor-specific data that can be modified and examined.
*
* @param extras extra options.
* @return this builder.
*
* @throws IllegalStateException if {@link #build()} was already called.
*/
@NonNull
public Builder setExtras(@NonNull Bundle extras) {
mExtras = Preconditions.checkNotNull(extras);
throwIfDestroyed();
return this;
}
/**
* Builds the {@link ContentCaptureContext}.
*
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return the built {@code ContentCaptureContext}
*/
@NonNull
public ContentCaptureContext build() {
throwIfDestroyed();
mDestroyed = true;
return new ContentCaptureContext(this);
}
private void throwIfDestroyed() {
Preconditions.checkState(!mDestroyed, "Already called #build()");
}
}
/**
* @hide
*/
// TODO(b/111276913): dump to proto as well
public void dump(PrintWriter pw) {
if (mComponentName != null) {
pw.print("activity="); pw.print(mComponentName.flattenToShortString());
}
if (mId != null) {
pw.print(", id="); mId.dump(pw);
}
pw.print(", taskId="); pw.print(mTaskId);
pw.print(", displayId="); pw.print(mDisplayId);
if (mParentSessionId != NO_SESSION_ID) {
pw.print(", parentId="); pw.print(mParentSessionId);
}
if (mFlags > 0) {
pw.print(", flags="); pw.print(mFlags);
}
if (mExtras != null) {
// NOTE: cannot dump because it could contain PII
pw.print(", hasExtras");
}
}
private boolean fromServer() {
return mComponentName != null;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder("Context[");
if (fromServer()) {
builder.append("act=").append(ComponentName.flattenToShortString(mComponentName))
.append(", taskId=").append(mTaskId)
.append(", displayId=").append(mDisplayId)
.append(", flags=").append(mFlags);
} else {
builder.append("id=").append(mId);
if (mExtras != null) {
// NOTE: cannot print because it could contain PII
builder.append(", hasExtras");
}
}
if (mParentSessionId != NO_SESSION_ID) {
builder.append(", parentId=").append(mParentSessionId);
}
return builder.append(']').toString();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mHasClientContext ? 1 : 0);
if (mHasClientContext) {
parcel.writeParcelable(mId, flags);
parcel.writeBundle(mExtras);
}
parcel.writeParcelable(mComponentName, flags);
if (fromServer()) {
parcel.writeInt(mTaskId);
parcel.writeInt(mDisplayId);
parcel.writeInt(mFlags);
}
}
public static final @android.annotation.NonNull Parcelable.Creator<ContentCaptureContext> CREATOR =
new Parcelable.Creator<ContentCaptureContext>() {
@Override
@NonNull
public ContentCaptureContext createFromParcel(Parcel parcel) {
final boolean hasClientContext = parcel.readInt() == 1;
final ContentCaptureContext clientContext;
if (hasClientContext) {
// Must reconstruct the client context using the Builder API
final LocusId id = parcel.readParcelable(null);
final Bundle extras = parcel.readBundle();
final Builder builder = new Builder(id);
if (extras != null) builder.setExtras(extras);
clientContext = new ContentCaptureContext(builder);
} else {
clientContext = null;
}
final ComponentName componentName = parcel.readParcelable(null);
if (componentName == null) {
// Client-state only
return clientContext;
} else {
final int taskId = parcel.readInt();
final int displayId = parcel.readInt();
final int flags = parcel.readInt();
return new ContentCaptureContext(clientContext, componentName, taskId, displayId,
flags);
}
}
@Override
@NonNull
public ContentCaptureContext[] newArray(int size) {
return new ContentCaptureContext[size];
}
};
}
|