blob: 06ab426c6698eedc16183ce108d1cb29151b61cd [file] [log] [blame]
/*
* 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 com.android.server.contentcapture;
import static android.service.contentcapture.ContentCaptureService.setClientState;
import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID;
import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE;
import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_TRUE;
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;
import static android.view.contentcapture.ContentCaptureSession.STATE_SERVICE_UPDATING;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.service.contentcapture.ContentCaptureService;
import android.service.contentcapture.SnapshotData;
import android.util.LocalLog;
import android.util.Slog;
import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureSessionId;
import android.view.contentcapture.MainContentCaptureSession;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.Preconditions;
import java.io.PrintWriter;
final class ContentCaptureServerSession {
private static final String TAG = ContentCaptureServerSession.class.getSimpleName();
final IBinder mActivityToken;
private final ContentCapturePerUserService mService;
// NOTE: this is the "internal" context (like package and taskId), not the explicit content
// set by apps - those are only send to the ContentCaptureService.
private final ContentCaptureContext mContentCaptureContext;
/**
* Reference to the binder object help at the client-side process and used to set its state.
*/
@NonNull
private final IResultReceiver mSessionStateReceiver;
/**
* Canonical session id.
*/
private final int mId;
/**
* UID of the app whose contents is being captured.
*/
private final int mUid;
private final Object mLock;
public final ComponentName appComponentName;
ContentCaptureServerSession(@NonNull Object lock, @NonNull IBinder activityToken,
@NonNull ContentCapturePerUserService service, @NonNull ComponentName appComponentName,
@NonNull IResultReceiver sessionStateReceiver, int taskId, int displayId, int sessionId,
int uid, int flags) {
Preconditions.checkArgument(sessionId != NO_SESSION_ID);
mLock = lock;
mActivityToken = activityToken;
this.appComponentName = appComponentName;
mService = service;
mId = sessionId;
mUid = uid;
mContentCaptureContext = new ContentCaptureContext(/* clientContext= */ null,
appComponentName, taskId, displayId, flags);
mSessionStateReceiver = sessionStateReceiver;
try {
sessionStateReceiver.asBinder().linkToDeath(() -> onClientDeath(), 0);
} catch (Exception e) {
Slog.w(TAG, "could not register DeathRecipient for " + activityToken);
}
}
/**
* Returns whether this session is for the given activity.
*/
boolean isActivitySession(@NonNull IBinder activityToken) {
return mActivityToken.equals(activityToken);
}
/**
* Notifies the {@link ContentCaptureService} that the service started.
*/
@GuardedBy("mLock")
public void notifySessionStartedLocked(@NonNull IResultReceiver clientReceiver) {
if (mService.mRemoteService == null) {
Slog.w(TAG, "notifySessionStartedLocked(): no remote service");
return;
}
mService.mRemoteService.onSessionStarted(mContentCaptureContext, mId, mUid, clientReceiver,
STATE_ACTIVE);
}
/**
* Changes the {@link ContentCaptureService} enabled state.
*/
@GuardedBy("mLock")
public void setContentCaptureEnabledLocked(boolean enabled) {
try {
final Bundle extras = new Bundle();
extras.putBoolean(MainContentCaptureSession.EXTRA_ENABLED_STATE, true);
mSessionStateReceiver.send(enabled ? RESULT_CODE_TRUE : RESULT_CODE_FALSE, extras);
} catch (RemoteException e) {
Slog.w(TAG, "Error async reporting result to client: " + e);
}
}
/**
* Notifies the {@link ContentCaptureService} of a snapshot of an activity.
*/
@GuardedBy("mLock")
public void sendActivitySnapshotLocked(@NonNull SnapshotData snapshotData) {
final LocalLog logHistory = mService.getMaster().mRequestsHistory;
if (logHistory != null) {
logHistory.log("snapshot: id=" + mId);
}
if (mService.mRemoteService == null) {
Slog.w(TAG, "sendActivitySnapshotLocked(): no remote service");
return;
}
mService.mRemoteService.onActivitySnapshotRequest(mId, snapshotData);
}
/**
* Cleans up the session and removes it from the service.
*
* @param notifyRemoteService whether it should trigger a {@link
* ContentCaptureService#onDestroyContentCaptureSession(ContentCaptureSessionId)}
* request.
*/
@GuardedBy("mLock")
public void removeSelfLocked(boolean notifyRemoteService) {
try {
destroyLocked(notifyRemoteService);
} finally {
mService.removeSessionLocked(mId);
}
}
/**
* Cleans up the session, but not removes it from the service.
*
* @param notifyRemoteService whether it should trigger a {@link
* ContentCaptureService#onDestroyContentCaptureSession(ContentCaptureSessionId)}
* request.
*/
@GuardedBy("mLock")
public void destroyLocked(boolean notifyRemoteService) {
if (mService.isVerbose()) {
Slog.v(TAG, "destroy(notifyRemoteService=" + notifyRemoteService + ")");
}
// TODO(b/111276913): must call client to set session as FINISHED_BY_SERVER
if (notifyRemoteService) {
if (mService.mRemoteService == null) {
Slog.w(TAG, "destroyLocked(): no remote service");
return;
}
mService.mRemoteService.onSessionFinished(mId);
}
}
/**
* Called to restore the active state of a session that was paused while the service died.
*/
@GuardedBy("mLock")
public void resurrectLocked() {
final RemoteContentCaptureService remoteService = mService.mRemoteService;
if (remoteService == null) {
Slog.w(TAG, "destroyLocked(: no remote service");
return;
}
if (mService.isVerbose()) {
Slog.v(TAG, "resurrecting " + mActivityToken + " on " + remoteService);
}
remoteService.onSessionStarted(new ContentCaptureContext(mContentCaptureContext,
ContentCaptureContext.FLAG_RECONNECTED), mId, mUid, mSessionStateReceiver,
STATE_ACTIVE | STATE_SERVICE_RESURRECTED);
}
/**
* Called to pause the session while the service is being updated.
*/
@GuardedBy("mLock")
public void pauseLocked() {
if (mService.isVerbose()) Slog.v(TAG, "pausing " + mActivityToken);
setClientState(mSessionStateReceiver, STATE_DISABLED | STATE_SERVICE_UPDATING,
/* binder= */ null);
}
/**
* Called when the session client binder object died - typically when its process was killed
* and the activity was not properly destroyed.
*/
private void onClientDeath() {
if (mService.isVerbose()) {
Slog.v(TAG, "onClientDeath(" + mActivityToken + "): removing session " + mId);
}
synchronized (mLock) {
removeSelfLocked(/* notifyRemoteService= */ true);
}
}
@GuardedBy("mLock")
public void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
pw.print(prefix); pw.print("id: "); pw.print(mId); pw.println();
pw.print(prefix); pw.print("uid: "); pw.print(mUid); pw.println();
pw.print(prefix); pw.print("context: "); mContentCaptureContext.dump(pw); pw.println();
pw.print(prefix); pw.print("activity token: "); pw.println(mActivityToken);
pw.print(prefix); pw.print("app component: "); pw.println(appComponentName);
pw.print(prefix); pw.print("has autofill callback: ");
}
String toShortString() {
return mId + ":" + mActivityToken;
}
@Override
public String toString() {
return "ContentCaptureSession[id=" + mId + ", act=" + mActivityToken + "]";
}
}