• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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