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