import static android.view.contentcapture.ContentCaptureHelper.sDebug;
import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
+import static android.view.contentcapture.ContentCaptureSession.NO_SESSION_ID;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.os.Looper;
import android.os.RemoteException;
import android.service.autofill.AutofillService;
-import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseIntArray;
import android.view.contentcapture.ContentCaptureCondition;
import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureEvent;
}
@Override
- public void onSessionStarted(ContentCaptureContext context, String sessionId, int uid,
+ public void onSessionStarted(ContentCaptureContext context, int sessionId, int uid,
IResultReceiver clientReceiver, int initialState) {
mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnCreateSession,
ContentCaptureService.this, context, sessionId, uid, clientReceiver,
}
@Override
- public void onActivitySnapshot(String sessionId, SnapshotData snapshotData) {
+ public void onActivitySnapshot(int sessionId, SnapshotData snapshotData) {
mHandler.sendMessage(
obtainMessage(ContentCaptureService::handleOnActivitySnapshot,
ContentCaptureService.this, sessionId, snapshotData));
}
@Override
- public void onSessionFinished(String sessionId) {
+ public void onSessionFinished(int sessionId) {
mHandler.sendMessage(obtainMessage(ContentCaptureService::handleFinishSession,
ContentCaptureService.this, sessionId));
}
* <p>This map is populated when an session is started, which is called by the system server
* and can be trusted. Then subsequent calls made by the app are verified against this map.
*/
- private final ArrayMap<String, Integer> mSessionUids = new ArrayMap<>();
+ private final SparseIntArray mSessionUids = new SparseIntArray();
@CallSuper
@Override
// so we don't need to create a temporary InteractionSessionId for each event.
private void handleOnCreateSession(@NonNull ContentCaptureContext context,
- @NonNull String sessionId, int uid, IResultReceiver clientReceiver, int initialState) {
+ int sessionId, int uid, IResultReceiver clientReceiver, int initialState) {
mSessionUids.put(sessionId, uid);
onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId));
// Most events belong to the same session, so we can keep a reference to the last one
// to avoid creating too many ContentCaptureSessionId objects
- String lastSessionId = null;
+ int lastSessionId = NO_SESSION_ID;
ContentCaptureSessionId sessionId = null;
final List<ContentCaptureEvent> events = parceledEvents.getList();
for (int i = 0; i < events.size(); i++) {
final ContentCaptureEvent event = events.get(i);
if (!handleIsRightCallerFor(event, uid)) continue;
- String sessionIdString = event.getSessionId();
- if (!sessionIdString.equals(lastSessionId)) {
- sessionId = new ContentCaptureSessionId(sessionIdString);
- lastSessionId = sessionIdString;
+ int sessionIdInt = event.getSessionId();
+ if (sessionIdInt != lastSessionId) {
+ sessionId = new ContentCaptureSessionId(sessionIdInt);
+ lastSessionId = sessionIdInt;
}
switch (event.getType()) {
case ContentCaptureEvent.TYPE_SESSION_STARTED:
final ContentCaptureContext clientContext = event.getContentCaptureContext();
clientContext.setParentSessionId(event.getParentSessionId());
- mSessionUids.put(sessionIdString, uid);
+ mSessionUids.put(sessionIdInt, uid);
onCreateContentCaptureSession(clientContext, sessionId);
break;
case ContentCaptureEvent.TYPE_SESSION_FINISHED:
- mSessionUids.remove(sessionIdString);
+ mSessionUids.delete(sessionIdInt);
onDestroyContentCaptureSession(sessionId);
break;
default:
}
}
- private void handleOnActivitySnapshot(@NonNull String sessionId,
- @NonNull SnapshotData snapshotData) {
+ private void handleOnActivitySnapshot(int sessionId, @NonNull SnapshotData snapshotData) {
onActivitySnapshot(new ContentCaptureSessionId(sessionId), snapshotData);
}
- private void handleFinishSession(@NonNull String sessionId) {
- mSessionUids.remove(sessionId);
+ private void handleFinishSession(int sessionId) {
+ mSessionUids.delete(sessionId);
onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId));
}
* Checks if the given {@code uid} owns the session associated with the event.
*/
private boolean handleIsRightCallerFor(@NonNull ContentCaptureEvent event, int uid) {
- final String sessionId;
+ final int sessionId;
switch (event.getType()) {
case ContentCaptureEvent.TYPE_SESSION_STARTED:
case ContentCaptureEvent.TYPE_SESSION_FINISHED:
default:
sessionId = event.getSessionId();
}
- final Integer rightUid = mSessionUids.get(sessionId);
- if (rightUid == null) {
+ if (mSessionUids.indexOfKey(sessionId) < 0) {
if (sVerbose) {
Log.v(TAG, "handleIsRightCallerFor(" + event + "): no session for " + sessionId
+ ": " + mSessionUids);
// Just ignore, as the session could have been finished already
return false;
}
+ final int rightUid = mSessionUids.get(sessionId);
if (rightUid != uid) {
Log.e(TAG, "invalid call from UID " + uid + ": session " + sessionId + " belongs to "
+ rightUid);
oneway interface IContentCaptureService {
void onConnected(IBinder callback, boolean verbose, boolean debug);
void onDisconnected();
- void onSessionStarted(in ContentCaptureContext context, String sessionId, int uid,
+ void onSessionStarted(in ContentCaptureContext context, int sessionId, int uid,
in IResultReceiver clientReceiver, int initialState);
- void onSessionFinished(String sessionId);
- void onActivitySnapshot(String sessionId, in SnapshotData snapshotData);
+ void onSessionFinished(int sessionId);
+ void onActivitySnapshot(int sessionId, in SnapshotData snapshotData);
void onUserDataRemovalRequest(in UserDataRemovalRequest request);
void onActivityEvent(in ActivityEvent event);
}
import android.sysprop.DisplayProperties;
import android.text.InputType;
import android.text.TextUtils;
-import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.LayoutDirection;
* hierarchy is traversed: value is either the view itself for appearead events, or its
* autofill id for disappeared.
*/
- // TODO(b/121197119): use SparseArray once session id becomes integer
- ArrayMap<String, ArrayList<Object>> mContentCaptureEvents;
+ SparseArray<ArrayList<Object>> mContentCaptureEvents;
/**
* Cached reference to the {@link ContentCaptureManager}.
@NonNull View view, boolean appeared) {
if (mContentCaptureEvents == null) {
// Most of the time there will be just one session, so intial capacity is 1
- mContentCaptureEvents = new ArrayMap<>(1);
+ mContentCaptureEvents = new SparseArray<>(1);
}
- String sessionId = session.getId();
+ int sessionId = session.getId();
// TODO: life would be much easier if we provided a MultiMap implementation somwhere...
ArrayList<Object> events = mContentCaptureEvents.get(sessionId);
if (events == null) {
MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
.getMainContentCaptureSession();
for (int i = 0; i < mAttachInfo.mContentCaptureEvents.size(); i++) {
- String sessionId = mAttachInfo.mContentCaptureEvents
- .keyAt(i);
+ int sessionId = mAttachInfo.mContentCaptureEvents.keyAt(i);
mainSession.notifyViewTreeEvent(sessionId, /* started= */ true);
ArrayList<Object> events = mAttachInfo.mContentCaptureEvents
.valueAt(i);
Log.w(mTag, "no content capture session on view: " + view);
continue for_each_event;
}
- String actualId = session.getId().toString();
- if (!actualId.equals(sessionId)) {
+ int actualId = session.getId();
+ if (actualId != sessionId) {
Log.w(mTag, "content capture session mismatch for view (" + view
+ "): was " + sessionId + " before, it's " + actualId + " now");
continue for_each_event;
*/
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 java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-
/**
* Context associated with a {@link ContentCaptureSession}.
*/
private final int mDisplayId;
// Fields below are set by the service upon "delivery" and are not marshalled in the parcel
- private @Nullable String mParentSessionId;
+ private int mParentSessionId = NO_SESSION_ID;
/** @hide */
public ContentCaptureContext(@Nullable ContentCaptureContext clientContext,
@SystemApi
@TestApi
public @Nullable ContentCaptureSessionId getParentSessionId() {
- return mParentSessionId == null ? null : new ContentCaptureSessionId(mParentSessionId);
+ return mParentSessionId == NO_SESSION_ID ? null
+ : new ContentCaptureSessionId(mParentSessionId);
}
/** @hide */
- public void setParentSessionId(@NonNull String parentSessionId) {
+ public void setParentSessionId(int parentSessionId) {
mParentSessionId = parentSessionId;
}
}
pw.print(", taskId="); pw.print(mTaskId);
pw.print(", displayId="); pw.print(mDisplayId);
- if (mParentSessionId != null) {
+ if (mParentSessionId != NO_SESSION_ID) {
pw.print(", parentId="); pw.print(mParentSessionId);
}
if (mFlags > 0) {
builder.append(", hasExtras");
}
}
- if (mParentSessionId != null) {
+ if (mParentSessionId != NO_SESSION_ID) {
builder.append(", parentId=").append(mParentSessionId);
}
return builder.append(']').toString();
package android.view.contentcapture;
import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
+import static android.view.contentcapture.ContentCaptureSession.NO_SESSION_ID;
import android.annotation.IntDef;
import android.annotation.NonNull;
@Retention(RetentionPolicy.SOURCE)
public @interface EventType{}
- private final @NonNull String mSessionId;
+ private final int mSessionId;
private final int mType;
private final long mEventTime;
private @Nullable AutofillId mId;
private @Nullable ArrayList<AutofillId> mIds;
private @Nullable ViewNode mNode;
private @Nullable CharSequence mText;
- private @Nullable String mParentSessionId;
+ private int mParentSessionId = NO_SESSION_ID;
private @Nullable ContentCaptureContext mClientContext;
/** @hide */
- public ContentCaptureEvent(@NonNull String sessionId, int type, long eventTime) {
+ public ContentCaptureEvent(int sessionId, int type, long eventTime) {
mSessionId = sessionId;
mType = type;
mEventTime = eventTime;
}
/** @hide */
- public ContentCaptureEvent(@NonNull String sessionId, int type) {
+ public ContentCaptureEvent(int sessionId, int type) {
this(sessionId, type, System.currentTimeMillis());
}
*
* @hide
*/
- public ContentCaptureEvent setParentSessionId(@NonNull String parentSessionId) {
+ public ContentCaptureEvent setParentSessionId(int parentSessionId) {
mParentSessionId = parentSessionId;
return this;
}
/** @hide */
@NonNull
- public String getSessionId() {
+ public int getSessionId() {
return mSessionId;
}
* @hide
*/
@Nullable
- public String getParentSessionId() {
+ public int getParentSessionId() {
return mParentSessionId;
}
if (mNode != null) {
pw.print(", mNode.id="); pw.print(mNode.getAutofillId());
}
- if (mSessionId != null) {
+ if (mSessionId != NO_SESSION_ID) {
pw.print(", sessionId="); pw.print(mSessionId);
}
- if (mParentSessionId != null) {
+ if (mParentSessionId != NO_SESSION_ID) {
pw.print(", parentSessionId="); pw.print(mParentSessionId);
}
if (mText != null) {
final StringBuilder string = new StringBuilder("ContentCaptureEvent[type=")
.append(getTypeAsString(mType));
string.append(", session=").append(mSessionId);
- if (mType == TYPE_SESSION_STARTED && mParentSessionId != null) {
+ if (mType == TYPE_SESSION_STARTED && mParentSessionId != NO_SESSION_ID) {
string.append(", parent=").append(mParentSessionId);
}
if (mId != null) {
@Override
public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeString(mSessionId);
+ parcel.writeInt(mSessionId);
parcel.writeInt(mType);
parcel.writeLong(mEventTime);
parcel.writeParcelable(mId, flags);
ViewNode.writeToParcel(parcel, mNode, flags);
parcel.writeCharSequence(mText);
if (mType == TYPE_SESSION_STARTED || mType == TYPE_SESSION_FINISHED) {
- parcel.writeString(mParentSessionId);
+ parcel.writeInt(mParentSessionId);
}
if (mType == TYPE_SESSION_STARTED || mType == TYPE_CONTEXT_UPDATED) {
parcel.writeParcelable(mClientContext, flags);
@Override
@NonNull
public ContentCaptureEvent createFromParcel(Parcel parcel) {
- final String sessionId = parcel.readString();
+ final int sessionId = parcel.readInt();
final int type = parcel.readInt();
final long eventTime = parcel.readLong();
final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, type, eventTime);
}
event.setText(parcel.readCharSequence());
if (type == TYPE_SESSION_STARTED || type == TYPE_SESSION_FINISHED) {
- event.setParentSessionId(parcel.readString());
+ event.setParentSessionId(parcel.readInt());
}
if (type == TYPE_SESSION_STARTED || type == TYPE_CONTEXT_UPDATED) {
event.setClientContext(parcel.readParcelable(null));
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.UUID;
+import java.util.Random;
/**
* Session used to notify a system-provided Content Capture service about events associated with
private static final String TAG = ContentCaptureSession.class.getSimpleName();
+ private static final Random sIdGenerator = new Random();
+
+ /** @hide */
+ public static final int NO_SESSION_ID = 0;
+
/**
* Initial state, when there is no session.
*
/** @hide */
@Nullable
- protected final String mId;
+ protected final int mId;
private int mState = UNKNOWN_STATE;
/** @hide */
protected ContentCaptureSession() {
- this(UUID.randomUUID().toString());
+ this(getRandomSessionId());
}
/** @hide */
@VisibleForTesting
- public ContentCaptureSession(@NonNull String id) {
- mId = Preconditions.checkNotNull(id);
+ public ContentCaptureSession(int id) {
+ Preconditions.checkArgument(id != NO_SESSION_ID);
+ mId = id;
}
// Used by ChildCOntentCaptureSession
}
/** @hide */
- @VisibleForTesting
- public int getIdAsInt() {
- // TODO(b/121197119): use sessionId instead of hashcode once it's changed to int
- return mId.hashCode();
- }
-
- /** @hide */
@NonNull
- public String getId() {
+ public int getId() {
return mId;
}
// TODO(b/123036895): use a internalNotifyViewsDisappeared that optimizes how the event is
// parcelized
for (long id : virtualIds) {
- internalNotifyViewDisappeared(new AutofillId(hostId, id, getIdAsInt()));
+ internalNotifyViewDisappeared(new AutofillId(hostId, id, mId));
}
}
public @NonNull AutofillId newAutofillId(@NonNull AutofillId hostId, long virtualChildId) {
Preconditions.checkNotNull(hostId);
Preconditions.checkArgument(hostId.isNonVirtual(), "hostId cannot be virtual: %s", hostId);
- return new AutofillId(hostId, virtualChildId, getIdAsInt());
+ return new AutofillId(hostId, virtualChildId, mId);
}
/**
@NonNull
public final ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId,
long virtualId) {
- return new ViewNode.ViewStructureImpl(parentId, virtualId, getIdAsInt());
+ return new ViewNode.ViewStructureImpl(parentId, virtualId, mId);
}
boolean isContentCaptureEnabled() {
@Override
public String toString() {
- return mId;
+ return Integer.toString(mId);
}
/** @hide */
return "UNKOWN-" + reason;
}
}
+
+ private static int getRandomSessionId() {
+ int id;
+ do {
+ id = sIdGenerator.nextInt();
+ } while (id == NO_SESSION_ID);
+ return id;
+ }
}
*/
public final class ContentCaptureSessionId implements Parcelable {
- private final @NonNull String mValue;
+ private final @NonNull int mValue;
/**
* Creates a new instance.
*
* @hide
*/
- public ContentCaptureSessionId(@NonNull String value) {
+ public ContentCaptureSessionId(@NonNull int value) {
mValue = value;
}
/**
* @hide
*/
- public String getValue() {
+ public int getValue() {
return mValue;
}
public int hashCode() {
final int prime = 31;
int result = 1;
- result = prime * result + ((mValue == null) ? 0 : mValue.hashCode());
+ result = prime * result + mValue;
return result;
}
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
final ContentCaptureSessionId other = (ContentCaptureSessionId) obj;
- if (mValue == null) {
- if (other.mValue != null) return false;
- } else if (!mValue.equals(other.mValue)) {
- return false;
- }
+ if (mValue != other.mValue) return false;
return true;
}
*/
@Override
public String toString() {
- return mValue;
+ return Integer.toString(mValue);
}
+
/** @hide */
// TODO(b/111276913): dump to proto as well
public void dump(PrintWriter pw) {
@Override
public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeString(mValue);
+ parcel.writeInt(mValue);
}
- public static final @android.annotation.NonNull Parcelable.Creator<ContentCaptureSessionId> CREATOR =
+ public static final @NonNull Parcelable.Creator<ContentCaptureSessionId> CREATOR =
new Parcelable.Creator<ContentCaptureSessionId>() {
@Override
@NonNull
public ContentCaptureSessionId createFromParcel(Parcel parcel) {
- return new ContentCaptureSessionId(parcel.readString());
+ return new ContentCaptureSessionId(parcel.readInt());
}
@Override
* {@link IContentCaptureContext#flags}).
*/
void startSession(IBinder activityToken, in ComponentName componentName,
- String sessionId, int flags, in IResultReceiver result);
+ int sessionId, int flags, in IResultReceiver result);
/**
* Marks the end of a session for the calling user identified by
* the corresponding {@code startSession}'s {@code sessionId}.
*/
- void finishSession(String sessionId);
+ void finishSession(int sessionId);
/**
* Returns the content capture service's component name (if enabled and
if (!mEvents.isEmpty() && eventType == TYPE_VIEW_DISAPPEARED) {
final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1);
if (lastEvent.getType() == TYPE_VIEW_DISAPPEARED
- && event.getSessionId().equals(lastEvent.getSessionId())) {
+ && event.getSessionId() == lastEvent.getSessionId()) {
if (sVerbose) {
Log.v(TAG, "Buffering TYPE_VIEW_DISAPPEARED events for session "
+ lastEvent.getSessionId());
// TODO(b/122454205): refactor "notifyXXXX" methods below to a common "Buffer" object that is
// shared between ActivityContentCaptureSession and ChildContentCaptureSession objects. Such
// change should also get get rid of the "internalNotifyXXXX" methods above
- void notifyChildSessionStarted(@NonNull String parentSessionId,
- @NonNull String childSessionId, @NonNull ContentCaptureContext clientContext) {
+ void notifyChildSessionStarted(int parentSessionId, int childSessionId,
+ @NonNull ContentCaptureContext clientContext) {
sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
.setParentSessionId(parentSessionId).setClientContext(clientContext),
FORCE_FLUSH);
}
- void notifyChildSessionFinished(@NonNull String parentSessionId,
- @NonNull String childSessionId) {
+ void notifyChildSessionFinished(int parentSessionId, int childSessionId) {
sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
.setParentSessionId(parentSessionId), FORCE_FLUSH);
}
- void notifyViewAppeared(@NonNull String sessionId, @NonNull ViewStructureImpl node) {
+ void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) {
sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
.setViewNode(node.mNode));
}
/** Public because is also used by ViewRootImpl */
- public void notifyViewDisappeared(@NonNull String sessionId, @NonNull AutofillId id) {
+ public void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) {
sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id));
}
- void notifyViewTextChanged(@NonNull String sessionId, @NonNull AutofillId id,
- @Nullable CharSequence text) {
+ void notifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) {
sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED).setAutofillId(id)
.setText(text));
}
/** Public because is also used by ViewRootImpl */
- public void notifyViewTreeEvent(@NonNull String sessionId, boolean started) {
+ public void notifyViewTreeEvent(int sessionId, boolean started) {
final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED;
sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH);
}
sendEvent(new ContentCaptureEvent(mId, type), FORCE_FLUSH);
}
- void notifyContextUpdated(@NonNull String sessionId,
- @Nullable ContentCaptureContext context) {
+ void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
.setClientContext(context));
}
private static final LocusId ID = new LocusId("WHATEVER");
+ private static final int NO_SESSION_ID = 0;
+
// Not using @Mock because it's final - no need to be fancy here....
private final ContentCaptureContext mClientContext =
new ContentCaptureContext.Builder(ID).build();
@Test
public void testSetAutofillId_null() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED);
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED);
assertThrows(NullPointerException.class, () -> event.setAutofillId(null));
assertThat(event.getId()).isNull();
@Test
public void testSetAutofillIds_null() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED);
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED);
assertThrows(NullPointerException.class, () -> event.setAutofillIds(null));
assertThat(event.getId()).isNull();
@Test
public void testAddAutofillId_null() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED);
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED);
assertThrows(NullPointerException.class, () -> event.addAutofillId(null));
assertThat(event.getId()).isNull();
@Test
public void testSetAutofillId() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED);
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED);
final AutofillId id = new AutofillId(108);
event.setAutofillId(id);
@Test
public void testSetAutofillIds() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED);
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED);
final AutofillId id = new AutofillId(108);
final ArrayList<AutofillId> ids = new ArrayList<>(1);
@Test
public void testAddAutofillId() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED);
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED);
final AutofillId id1 = new AutofillId(108);
event.addAutofillId(id1);
@Test
public void testAddAutofillId_afterSetId() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED);
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED);
final AutofillId id1 = new AutofillId(108);
event.setAutofillId(id1);
@Test
public void testAddAutofillId_afterSetIds() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED);
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED);
final AutofillId id1 = new AutofillId(108);
final ArrayList<AutofillId> ids = new ArrayList<>(1);
}
private ContentCaptureEvent newEventForSessionStarted() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_SESSION_STARTED)
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_SESSION_STARTED)
.setClientContext(mClientContext)
- .setParentSessionId("108");
+ .setParentSessionId(108);
assertThat(event).isNotNull();
return event;
}
private void assertSessionStartedEvent(ContentCaptureEvent event) {
assertThat(event.getType()).isEqualTo(TYPE_SESSION_STARTED);
assertThat(event.getEventTime()).isAtLeast(MY_EPOCH);
- assertThat(event.getSessionId()).isEqualTo("42");
- assertThat(event.getParentSessionId()).isEqualTo("108");
+ assertThat(event.getSessionId()).isEqualTo(42);
+ assertThat(event.getParentSessionId()).isEqualTo(108);
assertThat(event.getId()).isNull();
assertThat(event.getIds()).isNull();
assertThat(event.getText()).isNull();
@Test
public void testSessionFinished_directly() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_SESSION_FINISHED)
- .setParentSessionId("108");
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_SESSION_FINISHED)
+ .setParentSessionId(108);
assertThat(event).isNotNull();
assertSessionFinishedEvent(event);
}
@Test
public void testSessionFinished_throughParcel() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_SESSION_FINISHED)
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_SESSION_FINISHED)
.setClientContext(mClientContext) // should not be writting to parcel
- .setParentSessionId("108");
+ .setParentSessionId(108);
assertThat(event).isNotNull();
final ContentCaptureEvent clone = cloneThroughParcel(event);
assertSessionFinishedEvent(clone);
private void assertSessionFinishedEvent(ContentCaptureEvent event) {
assertThat(event.getType()).isEqualTo(TYPE_SESSION_FINISHED);
assertThat(event.getEventTime()).isAtLeast(MY_EPOCH);
- assertThat(event.getSessionId()).isEqualTo("42");
- assertThat(event.getParentSessionId()).isEqualTo("108");
+ assertThat(event.getSessionId()).isEqualTo(42);
+ assertThat(event.getParentSessionId()).isEqualTo(108);
assertThat(event.getId()).isNull();
assertThat(event.getIds()).isNull();
assertThat(event.getText()).isNull();
@Test
public void testContextUpdated_directly() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_CONTEXT_UPDATED)
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_CONTEXT_UPDATED)
.setClientContext(mClientContext);
assertThat(event).isNotNull();
assertContextUpdatedEvent(event);
@Test
public void testContextUpdated_throughParcel() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_CONTEXT_UPDATED)
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_CONTEXT_UPDATED)
.setClientContext(mClientContext);
assertThat(event).isNotNull();
final ContentCaptureEvent clone = cloneThroughParcel(event);
@Test
public void testMergeEvent_typeViewTextChanged() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_TEXT_CHANGED)
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_TEXT_CHANGED)
.setText("test");
- final ContentCaptureEvent event2 = new ContentCaptureEvent("43", TYPE_VIEW_TEXT_CHANGED)
+ final ContentCaptureEvent event2 = new ContentCaptureEvent(43, TYPE_VIEW_TEXT_CHANGED)
.setText("empty");
event.mergeEvent(event2);
@Test
public void testMergeEvent_typeViewDisappeared() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED)
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED)
.setAutofillId(new AutofillId(1));
- final ContentCaptureEvent event2 = new ContentCaptureEvent("43", TYPE_VIEW_DISAPPEARED)
+ final ContentCaptureEvent event2 = new ContentCaptureEvent(43, TYPE_VIEW_DISAPPEARED)
.setAutofillId(new AutofillId(2));
final ArrayList<AutofillId> autofillIds = new ArrayList<>();
autofillIds.add(new AutofillId(3));
autofillIds.add(new AutofillId(4));
- final ContentCaptureEvent event3 = new ContentCaptureEvent("17", TYPE_VIEW_DISAPPEARED)
+ final ContentCaptureEvent event3 = new ContentCaptureEvent(17, TYPE_VIEW_DISAPPEARED)
.setAutofillIds(autofillIds);
event.mergeEvent(event2);
@Test
public void testMergeEvent_typeViewDisappeared_noIds() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED)
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED)
.setAutofillId(new AutofillId(1));
- final ContentCaptureEvent event2 = new ContentCaptureEvent("43", TYPE_VIEW_DISAPPEARED);
+ final ContentCaptureEvent event2 = new ContentCaptureEvent(43, TYPE_VIEW_DISAPPEARED);
assertThrows(IllegalArgumentException.class, () -> event.mergeEvent(event2));
}
@Test
public void testMergeEvent_nullArgument() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED);
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED);
assertThrows(NullPointerException.class, () -> event.mergeEvent(null));
}
@Test
public void testMergeEvent_differentEventTypes() {
- final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED)
+ final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED)
.setText("test").setAutofillId(new AutofillId(1));
- final ContentCaptureEvent event2 = new ContentCaptureEvent("17", TYPE_VIEW_TEXT_CHANGED)
+ final ContentCaptureEvent event2 = new ContentCaptureEvent(17, TYPE_VIEW_TEXT_CHANGED)
.setText("empty").setAutofillId(new AutofillId(2));
event.mergeEvent(event2);
private void assertContextUpdatedEvent(ContentCaptureEvent event) {
assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_UPDATED);
assertThat(event.getEventTime()).isAtLeast(MY_EPOCH);
- assertThat(event.getSessionId()).isEqualTo("42");
- assertThat(event.getParentSessionId()).isNull();
+ assertThat(event.getSessionId()).isEqualTo(42);
+ assertThat(event.getParentSessionId()).isEqualTo(NO_SESSION_ID);
assertThat(event.getId()).isNull();
assertThat(event.getIds()).isNull();
assertThat(event.getText()).isNull();
@RunWith(MockitoJUnitRunner.class)
public class ContentCaptureSessionTest {
- private ContentCaptureSession mSession1 = new MyContentCaptureSession("111");
+ private ContentCaptureSession mSession1 = new MyContentCaptureSession(111);
- private ContentCaptureSession mSession2 = new MyContentCaptureSession("2222");
+ private ContentCaptureSession mSession2 = new MyContentCaptureSession(2222);
@Mock
private View mMockView;
assertThat(childId.getViewId()).isEqualTo(42);
assertThat(childId.getVirtualChildLongId()).isEqualTo(108L);
assertThat(childId.getVirtualChildIntId()).isEqualTo(View.NO_ID);
- assertThat(childId.getSessionId()).isEqualTo(mSession1.getIdAsInt());
+ assertThat(childId.getSessionId()).isEqualTo(mSession1.getId());
}
@Test
public void testNewAutofillId_differentSessions() {
- assertThat(mSession1.getIdAsInt()).isNotSameAs(mSession2.getIdAsInt()); //sanity check
+ assertThat(mSession1.getId()).isNotEqualTo(mSession2.getId()); //sanity check
final AutofillId parentId = new AutofillId(42);
final AutofillId childId1 = mSession1.newAutofillId(parentId, 108L);
final AutofillId childId2 = mSession2.newAutofillId(parentId, 108L);
// Cannot use @Spy because we need to pass the session id on constructor
private class MyContentCaptureSession extends ContentCaptureSession {
- private MyContentCaptureSession(String id) {
+ private MyContentCaptureSession(int id) {
super(id);
}
public ContentCaptureManagerService(@NonNull Context context) {
super(context, new FrameworkResourcesServiceNameResolver(context,
com.android.internal.R.string.config_defaultContentCaptureService),
- UserManager.DISALLOW_CONTENT_CAPTURE, /* refreshServiceOnPackageUpdate=*/ false);
+ UserManager.DISALLOW_CONTENT_CAPTURE, /* refreshServiceOnPackageUpdate= */ false);
DeviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
ActivityThread.currentApplication().getMainExecutor(),
(namespace, key, value) -> onDeviceConfigChange(key, value));
return false;
}
- final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+ final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
if (serviceComponent == null) {
Slog.w(mTag, methodName + ": invalid service name: " + serviceName);
return false;
@Override
public void startSession(@NonNull IBinder activityToken,
- @NonNull ComponentName componentName, @NonNull String sessionId, int flags,
+ @NonNull ComponentName componentName, int sessionId, int flags,
@NonNull IResultReceiver result) {
Preconditions.checkNotNull(activityToken);
Preconditions.checkNotNull(sessionId);
}
@Override
- public void finishSession(@NonNull String sessionId) {
+ public void finishSession(int sessionId) {
Preconditions.checkNotNull(sessionId);
final int userId = UserHandle.getCallingUserId();
boolean showHistory = true;
if (args != null) {
for (String arg : args) {
- switch(arg) {
+ switch (arg) {
case "--no-history":
showHistory = false;
break;
package com.android.server.contentcapture;
import static android.service.contentcapture.ContentCaptureService.setClientState;
+import static android.view.contentcapture.ContentCaptureSession.NO_SESSION_ID;
import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED;
import static android.view.contentcapture.ContentCaptureSession.STATE_DUPLICATED_ID;
import static android.view.contentcapture.ContentCaptureSession.STATE_INTERNAL_ERROR;
import android.service.contentcapture.ContentCaptureServiceInfo;
import android.service.contentcapture.IContentCaptureServiceCallback;
import android.service.contentcapture.SnapshotData;
-import android.util.ArrayMap;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.contentcapture.UserDataRemovalRequest;
import com.android.internal.annotations.GuardedBy;
private static final String TAG = ContentCapturePerUserService.class.getSimpleName();
@GuardedBy("mLock")
- private final ArrayMap<String, ContentCaptureServerSession> mSessions =
- new ArrayMap<>();
+ private final SparseArray<ContentCaptureServerSession> mSessions = new SparseArray<>();
/**
* Reference to the remote service.
// TODO(b/119613670): log metrics
@GuardedBy("mLock")
public void startSessionLocked(@NonNull IBinder activityToken,
- @NonNull ActivityPresentationInfo activityPresentationInfo,
- @NonNull String sessionId, int uid, int flags,
- @NonNull IResultReceiver clientReceiver) {
+ @NonNull ActivityPresentationInfo activityPresentationInfo, int sessionId, int uid,
+ int flags, @NonNull IResultReceiver clientReceiver) {
if (activityPresentationInfo == null) {
Slog.w(TAG, "basic activity info is null");
setClientState(clientReceiver, STATE_DISABLED | STATE_INTERNAL_ERROR,
// TODO(b/119613670): log metrics
@GuardedBy("mLock")
- public void finishSessionLocked(@NonNull String sessionId) {
+ public void finishSessionLocked(int sessionId) {
if (!isEnabledLocked()) {
return;
}
@GuardedBy("mLock")
public boolean sendActivityAssistDataLocked(@NonNull IBinder activityToken,
@NonNull Bundle data) {
- final String id = getSessionId(activityToken);
- if (id != null) {
+ final int id = getSessionId(activityToken);
+ if (id != NO_SESSION_ID) {
final ContentCaptureServerSession session = mSessions.get(id);
final Bundle assistData = data.getBundle(ASSIST_KEY_DATA);
final AssistStructure assistStructure = data.getParcelable(ASSIST_KEY_STRUCTURE);
}
@GuardedBy("mLock")
- public void removeSessionLocked(@NonNull String sessionId) {
+ public void removeSessionLocked(int sessionId) {
mSessions.remove(sessionId);
}
mRemoteService.dump(prefix2, pw);
}
- if (mSessions.isEmpty()) {
+ if (mSessions.size() == 0) {
pw.print(prefix); pw.println("no sessions");
} else {
final int sessionsSize = mSessions.size();
* Returns the session id associated with the given activity.
*/
@GuardedBy("mLock")
- private String getSessionId(@NonNull IBinder activityToken) {
+ private int getSessionId(@NonNull IBinder activityToken) {
for (int i = 0; i < mSessions.size(); i++) {
ContentCaptureServerSession session = mSessions.valueAt(i);
if (session.isActivitySession(activityToken)) {
return mSessions.keyAt(i);
}
}
- return null;
+ return NO_SESSION_ID;
}
/**
package com.android.server.contentcapture;
import static android.service.contentcapture.ContentCaptureService.setClientState;
+import static android.view.contentcapture.ContentCaptureSession.NO_SESSION_ID;
import static android.view.contentcapture.ContentCaptureSession.STATE_ACTIVE;
import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED;
import static android.view.contentcapture.ContentCaptureSession.STATE_SERVICE_RESURRECTED;
/**
* Canonical session id.
*/
- private final String mId;
+ private final int mId;
/**
* UID of the app whose contents is being captured.
ContentCaptureServerSession(@NonNull IBinder activityToken,
@NonNull ContentCapturePerUserService service, @NonNull ComponentName appComponentName,
- @NonNull IResultReceiver sessionStateReceiver,
- int taskId, int displayId, @NonNull String sessionId, int uid, int flags) {
+ @NonNull IResultReceiver sessionStateReceiver, int taskId, int displayId, int sessionId,
+ int uid, int flags) {
+ Preconditions.checkArgument(sessionId != NO_SESSION_ID);
mActivityToken = activityToken;
mService = service;
- mId = Preconditions.checkNotNull(sessionId);
+ mId = sessionId;
mUid = uid;
mContentCaptureContext = new ContentCaptureContext(/* clientContext= */ null,
appComponentName, taskId, displayId, flags);
* Called by {@link ContentCaptureServerSession} to generate a call to the
* {@link RemoteContentCaptureService} to indicate the session was created.
*/
- public void onSessionStarted(@Nullable ContentCaptureContext context,
- @NonNull String sessionId, int uid, @NonNull IResultReceiver clientReceiver,
- int initialState) {
+ public void onSessionStarted(@Nullable ContentCaptureContext context, int sessionId, int uid,
+ @NonNull IResultReceiver clientReceiver, int initialState) {
scheduleAsyncRequest(
(s) -> s.onSessionStarted(context, sessionId, uid, clientReceiver, initialState));
}
* Called by {@link ContentCaptureServerSession} to generate a call to the
* {@link RemoteContentCaptureService} to indicate the session was finished.
*/
- public void onSessionFinished(@NonNull String sessionId) {
+ public void onSessionFinished(int sessionId) {
scheduleAsyncRequest((s) -> s.onSessionFinished(sessionId));
}
/**
* Called by {@link ContentCaptureServerSession} to send snapshot data to the service.
*/
- public void onActivitySnapshotRequest(@NonNull String sessionId,
- @NonNull SnapshotData snapshotData) {
+ public void onActivitySnapshotRequest(int sessionId, @NonNull SnapshotData snapshotData) {
scheduleAsyncRequest((s) -> s.onActivitySnapshot(sessionId, snapshotData));
}