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