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 com.android.server.contentcapture; 17 18 import static android.service.contentcapture.ContentCaptureService.setClientState; 19 import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID; 20 import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE; 21 import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_TRUE; 22 import static android.view.contentcapture.ContentCaptureSession.STATE_ACTIVE; 23 import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED; 24 import static android.view.contentcapture.ContentCaptureSession.STATE_SERVICE_RESURRECTED; 25 import static android.view.contentcapture.ContentCaptureSession.STATE_SERVICE_UPDATING; 26 27 import android.annotation.NonNull; 28 import android.app.assist.ActivityId; 29 import android.content.ComponentName; 30 import android.os.Bundle; 31 import android.os.IBinder; 32 import android.os.RemoteException; 33 import android.service.contentcapture.ContentCaptureService; 34 import android.service.contentcapture.SnapshotData; 35 import android.util.LocalLog; 36 import android.util.Slog; 37 import android.view.contentcapture.ContentCaptureContext; 38 import android.view.contentcapture.ContentCaptureSessionId; 39 import android.view.contentcapture.MainContentCaptureSession; 40 41 import com.android.internal.annotations.GuardedBy; 42 import com.android.internal.os.IResultReceiver; 43 import com.android.internal.util.Preconditions; 44 45 import java.io.PrintWriter; 46 47 final class ContentCaptureServerSession { 48 49 private static final String TAG = ContentCaptureServerSession.class.getSimpleName(); 50 51 final IBinder mActivityToken; 52 private final ContentCapturePerUserService mService; 53 54 // NOTE: this is the "internal" context (like package and taskId), not the explicit content 55 // set by apps - those are only send to the ContentCaptureService. 56 private final ContentCaptureContext mContentCaptureContext; 57 58 /** 59 * Reference to the binder object help at the client-side process and used to set its state. 60 */ 61 @NonNull 62 private final IResultReceiver mSessionStateReceiver; 63 64 /** 65 * Canonical session id. 66 */ 67 private final int mId; 68 69 /** 70 * UID of the app whose contents is being captured. 71 */ 72 private final int mUid; 73 74 private final Object mLock; 75 76 public final ComponentName appComponentName; 77 ContentCaptureServerSession(@onNull Object lock, @NonNull IBinder activityToken, @NonNull ActivityId activityId, @NonNull ContentCapturePerUserService service, @NonNull ComponentName appComponentName, @NonNull IResultReceiver sessionStateReceiver, int taskId, int displayId, int sessionId, int uid, int flags)78 ContentCaptureServerSession(@NonNull Object lock, @NonNull IBinder activityToken, 79 @NonNull ActivityId activityId, @NonNull ContentCapturePerUserService service, 80 @NonNull ComponentName appComponentName, @NonNull IResultReceiver sessionStateReceiver, 81 int taskId, int displayId, int sessionId, int uid, int flags) { 82 Preconditions.checkArgument(sessionId != NO_SESSION_ID); 83 mLock = lock; 84 mActivityToken = activityToken; 85 this.appComponentName = appComponentName; 86 mService = service; 87 mId = sessionId; 88 mUid = uid; 89 mContentCaptureContext = new ContentCaptureContext(/* clientContext= */ null, 90 activityId, appComponentName, displayId, flags); 91 mSessionStateReceiver = sessionStateReceiver; 92 try { 93 sessionStateReceiver.asBinder().linkToDeath(() -> onClientDeath(), 0); 94 } catch (Exception e) { 95 Slog.w(TAG, "could not register DeathRecipient for " + activityToken); 96 } 97 } 98 99 /** 100 * Returns whether this session is for the given activity. 101 */ isActivitySession(@onNull IBinder activityToken)102 boolean isActivitySession(@NonNull IBinder activityToken) { 103 return mActivityToken.equals(activityToken); 104 } 105 106 /** 107 * Notifies the {@link ContentCaptureService} that the service started. 108 */ 109 @GuardedBy("mLock") notifySessionStartedLocked(@onNull IResultReceiver clientReceiver)110 public void notifySessionStartedLocked(@NonNull IResultReceiver clientReceiver) { 111 if (mService.mRemoteService == null) { 112 Slog.w(TAG, "notifySessionStartedLocked(): no remote service"); 113 return; 114 } 115 mService.mRemoteService.onSessionStarted(mContentCaptureContext, mId, mUid, clientReceiver, 116 STATE_ACTIVE); 117 } 118 119 /** 120 * Changes the {@link ContentCaptureService} enabled state. 121 */ 122 @GuardedBy("mLock") setContentCaptureEnabledLocked(boolean enabled)123 public void setContentCaptureEnabledLocked(boolean enabled) { 124 try { 125 final Bundle extras = new Bundle(); 126 extras.putBoolean(MainContentCaptureSession.EXTRA_ENABLED_STATE, true); 127 mSessionStateReceiver.send(enabled ? RESULT_CODE_TRUE : RESULT_CODE_FALSE, extras); 128 } catch (RemoteException e) { 129 Slog.w(TAG, "Error async reporting result to client: " + e); 130 } 131 } 132 133 /** 134 * Notifies the {@link ContentCaptureService} of a snapshot of an activity. 135 */ 136 @GuardedBy("mLock") sendActivitySnapshotLocked(@onNull SnapshotData snapshotData)137 public void sendActivitySnapshotLocked(@NonNull SnapshotData snapshotData) { 138 final LocalLog logHistory = mService.getMaster().mRequestsHistory; 139 if (logHistory != null) { 140 logHistory.log("snapshot: id=" + mId); 141 } 142 143 if (mService.mRemoteService == null) { 144 Slog.w(TAG, "sendActivitySnapshotLocked(): no remote service"); 145 return; 146 } 147 mService.mRemoteService.onActivitySnapshotRequest(mId, snapshotData); 148 } 149 150 /** 151 * Cleans up the session and removes it from the service. 152 * 153 * @param notifyRemoteService whether it should trigger a {@link 154 * ContentCaptureService#onDestroyContentCaptureSession(ContentCaptureSessionId)} 155 * request. 156 */ 157 @GuardedBy("mLock") removeSelfLocked(boolean notifyRemoteService)158 public void removeSelfLocked(boolean notifyRemoteService) { 159 try { 160 destroyLocked(notifyRemoteService); 161 } finally { 162 mService.removeSessionLocked(mId); 163 } 164 } 165 166 /** 167 * Cleans up the session, but not removes it from the service. 168 * 169 * @param notifyRemoteService whether it should trigger a {@link 170 * ContentCaptureService#onDestroyContentCaptureSession(ContentCaptureSessionId)} 171 * request. 172 */ 173 @GuardedBy("mLock") destroyLocked(boolean notifyRemoteService)174 public void destroyLocked(boolean notifyRemoteService) { 175 if (mService.isVerbose()) { 176 Slog.v(TAG, "destroy(notifyRemoteService=" + notifyRemoteService + ")"); 177 } 178 // TODO(b/111276913): must call client to set session as FINISHED_BY_SERVER 179 if (notifyRemoteService) { 180 if (mService.mRemoteService == null) { 181 Slog.w(TAG, "destroyLocked(): no remote service"); 182 return; 183 } 184 mService.mRemoteService.onSessionFinished(mId); 185 } 186 } 187 188 /** 189 * Called to restore the active state of a session that was paused while the service died. 190 */ 191 @GuardedBy("mLock") resurrectLocked()192 public void resurrectLocked() { 193 final RemoteContentCaptureService remoteService = mService.mRemoteService; 194 if (remoteService == null) { 195 Slog.w(TAG, "destroyLocked(: no remote service"); 196 return; 197 } 198 if (mService.isVerbose()) { 199 Slog.v(TAG, "resurrecting " + mActivityToken + " on " + remoteService); 200 } 201 remoteService.onSessionStarted(new ContentCaptureContext(mContentCaptureContext, 202 ContentCaptureContext.FLAG_RECONNECTED), mId, mUid, mSessionStateReceiver, 203 STATE_ACTIVE | STATE_SERVICE_RESURRECTED); 204 } 205 206 /** 207 * Called to pause the session while the service is being updated. 208 */ 209 @GuardedBy("mLock") pauseLocked()210 public void pauseLocked() { 211 if (mService.isVerbose()) Slog.v(TAG, "pausing " + mActivityToken); 212 setClientState(mSessionStateReceiver, STATE_DISABLED | STATE_SERVICE_UPDATING, 213 /* binder= */ null); 214 } 215 216 /** 217 * Called when the session client binder object died - typically when its process was killed 218 * and the activity was not properly destroyed. 219 */ onClientDeath()220 private void onClientDeath() { 221 if (mService.isVerbose()) { 222 Slog.v(TAG, "onClientDeath(" + mActivityToken + "): removing session " + mId); 223 } 224 synchronized (mLock) { 225 removeSelfLocked(/* notifyRemoteService= */ true); 226 } 227 } 228 229 @GuardedBy("mLock") dumpLocked(@onNull String prefix, @NonNull PrintWriter pw)230 public void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) { 231 pw.print(prefix); pw.print("id: "); pw.print(mId); pw.println(); 232 pw.print(prefix); pw.print("uid: "); pw.print(mUid); pw.println(); 233 pw.print(prefix); pw.print("context: "); mContentCaptureContext.dump(pw); pw.println(); 234 pw.print(prefix); pw.print("activity token: "); pw.println(mActivityToken); 235 pw.print(prefix); pw.print("app component: "); pw.println(appComponentName); 236 pw.print(prefix); pw.print("has autofill callback: "); 237 } 238 toShortString()239 String toShortString() { 240 return mId + ":" + mActivityToken; 241 } 242 243 @Override toString()244 public String toString() { 245 return "ContentCaptureSession[id=" + mId + ", act=" + mActivityToken + "]"; 246 } 247 } 248