blob: c467935f1996321fc05191246febb9674edb62ca [file] [log] [blame]
Felipe Lemee348dc32018-11-05 12:35:29 -08001/*
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
Felipe Leme749b8892018-12-03 16:30:30 -080017package com.android.server.contentcapture;
Felipe Lemee348dc32018-11-05 12:35:29 -080018
Felipe Lemeb96878492018-12-17 12:22:29 -080019import static android.service.contentcapture.ContentCaptureService.setClientState;
20
Winson Chungfbbb1582018-11-13 16:09:01 -080021import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT;
22import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA;
23import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
24
Felipe Lemee348dc32018-11-05 12:35:29 -080025import android.Manifest;
26import android.annotation.NonNull;
Felipe Leme284ad1c2018-11-15 18:16:12 -080027import android.annotation.UserIdInt;
Felipe Lemee348dc32018-11-05 12:35:29 -080028import android.app.AppGlobals;
Winson Chungfbbb1582018-11-13 16:09:01 -080029import android.app.assist.AssistContent;
30import android.app.assist.AssistStructure;
Felipe Lemee348dc32018-11-05 12:35:29 -080031import android.content.ComponentName;
32import android.content.pm.PackageManager;
33import android.content.pm.PackageManager.NameNotFoundException;
34import android.content.pm.ServiceInfo;
Winson Chungfbbb1582018-11-13 16:09:01 -080035import android.os.Bundle;
Felipe Lemee348dc32018-11-05 12:35:29 -080036import android.os.IBinder;
37import android.os.RemoteException;
Felipe Leme50b33dc2018-12-20 16:01:39 -080038import android.service.contentcapture.ContentCaptureService;
Felipe Leme749b8892018-12-03 16:30:30 -080039import android.service.contentcapture.SnapshotData;
Felipe Lemee348dc32018-11-05 12:35:29 -080040import android.util.ArrayMap;
41import android.util.Slog;
Felipe Lemeaa5088e2018-12-10 14:53:58 -080042import android.view.contentcapture.ContentCaptureSession;
Felipe Lemee348dc32018-11-05 12:35:29 -080043
44import com.android.internal.annotations.GuardedBy;
45import com.android.internal.os.IResultReceiver;
Felipe Leme50b33dc2018-12-20 16:01:39 -080046import com.android.server.contentcapture.RemoteContentCaptureService.ContentCaptureServiceCallbacks;
Felipe Leme1e3de572018-12-05 15:44:17 -080047import com.android.server.infra.AbstractPerUserSystemService;
Felipe Lemee348dc32018-11-05 12:35:29 -080048
49import java.io.PrintWriter;
Felipe Leme5933efb2018-11-29 16:08:49 -080050import java.util.ArrayList;
Felipe Lemee348dc32018-11-05 12:35:29 -080051
52/**
Felipe Leme749b8892018-12-03 16:30:30 -080053 * Per-user instance of {@link ContentCaptureManagerService}.
Felipe Lemee348dc32018-11-05 12:35:29 -080054 */
Felipe Leme749b8892018-12-03 16:30:30 -080055final class ContentCapturePerUserService
56 extends
Felipe Leme50b33dc2018-12-20 16:01:39 -080057 AbstractPerUserSystemService<ContentCapturePerUserService, ContentCaptureManagerService>
58 implements ContentCaptureServiceCallbacks {
Felipe Lemee348dc32018-11-05 12:35:29 -080059
Felipe Leme749b8892018-12-03 16:30:30 -080060 private static final String TAG = ContentCaptureManagerService.class.getSimpleName();
Felipe Lemee348dc32018-11-05 12:35:29 -080061
Felipe Lemee348dc32018-11-05 12:35:29 -080062 @GuardedBy("mLock")
Felipe Lemeaa5088e2018-12-10 14:53:58 -080063 private final ArrayMap<String, ContentCaptureServerSession> mSessions =
Felipe Lemea7bdb142018-11-05 16:29:29 -080064 new ArrayMap<>();
Felipe Lemee348dc32018-11-05 12:35:29 -080065
Felipe Leme50b33dc2018-12-20 16:01:39 -080066 /**
67 * Reference to the remote service.
68 *
69 * <p>It's set in the constructor, but it's also updated when the service's updated in the
70 * master's cache (for example, because a temporary service was set).
71 */
72 @GuardedBy("mLock")
73 private RemoteContentCaptureService mRemoteService;
74
Felipe Lemee348dc32018-11-05 12:35:29 -080075 // TODO(b/111276913): add mechanism to prune stale sessions, similar to Autofill's
76
Felipe Leme50b33dc2018-12-20 16:01:39 -080077 ContentCapturePerUserService(@NonNull ContentCaptureManagerService master,
78 @NonNull Object lock, boolean disabled, @UserIdInt int userId) {
Felipe Lemec28711c2018-12-20 11:17:02 -080079 super(master, lock, userId);
Felipe Leme50b33dc2018-12-20 16:01:39 -080080
81 updateRemoteServiceLocked(disabled);
82 }
83
84 /**
85 * Updates the reference to the remote service.
86 */
87 private void updateRemoteServiceLocked(boolean disabled) {
88 if (mRemoteService != null) {
89 if (mMaster.debug) Slog.d(TAG, "updateRemoteService(): destroying old remote service");
90 mRemoteService.destroy();
91 mRemoteService = null;
92 }
93
94 // Updates the component name
95 final ComponentName serviceComponentName = updateServiceInfoLocked();
96
97 if (serviceComponentName == null) {
98 Slog.w(TAG, "updateRemoteService(): no service componennt name");
99 return;
100 }
101
102 if (!disabled) {
103 mRemoteService = new RemoteContentCaptureService(
104 mMaster.getContext(),
105 ContentCaptureService.SERVICE_INTERFACE, serviceComponentName, mUserId, this,
106 mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
107 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800108 }
109
110 @Override // from PerUserSystemService
Felipe Leme50b33dc2018-12-20 16:01:39 -0800111 protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
Felipe Lemee348dc32018-11-05 12:35:29 -0800112 throws NameNotFoundException {
113
Felipe Leme658c8e42018-11-30 12:49:45 -0800114 int flags = PackageManager.GET_META_DATA;
115 final boolean isTemp = isTemporaryServiceSetLocked();
116 if (!isTemp) {
117 flags |= PackageManager.MATCH_SYSTEM_ONLY;
118 }
119
Felipe Lemee348dc32018-11-05 12:35:29 -0800120 ServiceInfo si;
121 try {
Felipe Leme658c8e42018-11-30 12:49:45 -0800122 si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, flags, mUserId);
Felipe Lemee348dc32018-11-05 12:35:29 -0800123 } catch (RemoteException e) {
124 Slog.w(TAG, "Could not get service for " + serviceComponent + ": " + e);
125 return null;
126 }
Felipe Leme658c8e42018-11-30 12:49:45 -0800127 if (si == null) {
128 Slog.w(TAG, "Could not get serviceInfo for " + (isTemp ? " (temp)" : "(default system)")
129 + " " + serviceComponent.flattenToShortString());
130 return null;
131 }
Felipe Leme749b8892018-12-03 16:30:30 -0800132 if (!Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE.equals(si.permission)) {
133 Slog.w(TAG, "ContentCaptureService from '" + si.packageName
Felipe Lemee348dc32018-11-05 12:35:29 -0800134 + "' does not require permission "
Felipe Leme749b8892018-12-03 16:30:30 -0800135 + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE);
Felipe Lemee348dc32018-11-05 12:35:29 -0800136 throw new SecurityException("Service does not require permission "
Felipe Leme749b8892018-12-03 16:30:30 -0800137 + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE);
Felipe Lemee348dc32018-11-05 12:35:29 -0800138 }
139 return si;
140 }
141
Felipe Lemeecb08be2018-11-27 15:48:47 -0800142 @Override // from PerUserSystemService
143 @GuardedBy("mLock")
144 protected boolean updateLocked(boolean disabled) {
145 destroyLocked();
Felipe Leme50b33dc2018-12-20 16:01:39 -0800146 final boolean disabledStateChanged = super.updateLocked(disabled);
147 updateRemoteServiceLocked(disabled);
148 return disabledStateChanged;
Felipe Lemeecb08be2018-11-27 15:48:47 -0800149 }
150
Felipe Leme50b33dc2018-12-20 16:01:39 -0800151 @Override // from ContentCaptureServiceCallbacks
152 public void onServiceDied(@NonNull RemoteContentCaptureService service) {
153 if (mMaster.debug) Slog.d(TAG, "remote service died: " + service);
154 synchronized (mLock) {
155 removeSelfFromCacheLocked();
156 }
157 }
158
159 // TODO(b/119613670): log metrics
Felipe Lemee348dc32018-11-05 12:35:29 -0800160 @GuardedBy("mLock")
161 public void startSessionLocked(@NonNull IBinder activityToken,
Felipe Lemea7bdb142018-11-05 16:29:29 -0800162 @NonNull ComponentName componentName, int taskId, int displayId,
Felipe Leme50b33dc2018-12-20 16:01:39 -0800163 @NonNull String sessionId, int uid, int flags,
Felipe Leme87a9dc92018-12-18 14:28:07 -0800164 @NonNull IResultReceiver clientReceiver) {
Felipe Leme8e2e3412018-11-14 10:39:29 -0800165 if (!isEnabledLocked()) {
Felipe Lemeb96878492018-12-17 12:22:29 -0800166 setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED, /* binder=*/ null);
Felipe Leme8e2e3412018-11-14 10:39:29 -0800167 return;
168 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800169 final ComponentName serviceComponentName = getServiceComponentName();
170 if (serviceComponentName == null) {
171 // TODO(b/111276913): this happens when the system service is starting, we should
172 // probably handle it in a more elegant way (like waiting for boot_complete or
173 // something like that
Felipe Leme8e2e3412018-11-14 10:39:29 -0800174 if (mMaster.debug) {
175 Slog.d(TAG, "startSession(" + activityToken + "): hold your horses");
176 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800177 return;
178 }
179
Felipe Lemeb96878492018-12-17 12:22:29 -0800180 final ContentCaptureServerSession existingSession = mSessions.get(sessionId);
181 if (existingSession != null) {
182 Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken
183 + ": ignoring because it already exists for " + existingSession.mActivityToken);
184 setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_DUPLICATED_ID,
185 /* binder=*/ null);
Felipe Lemee348dc32018-11-05 12:35:29 -0800186 return;
187 }
188
Felipe Leme50b33dc2018-12-20 16:01:39 -0800189 if (mRemoteService == null) {
190 updateRemoteServiceLocked(/* disabled= */ false); // already checked for isEnabled
191 }
192
193 if (mRemoteService == null) {
194 // TODO(b/119613670): log metrics
195 Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken
196 + ": ignoring because service is not set");
197 // TODO(b/111276913): use a new disabled state?
198 setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED,
199 /* binder=*/ null);
200 return;
201 }
202
203 final ContentCaptureServerSession newSession = new ContentCaptureServerSession(
204 activityToken, this, mRemoteService, componentName, taskId,
205 displayId, sessionId, uid, flags);
Felipe Lemee348dc32018-11-05 12:35:29 -0800206 if (mMaster.verbose) {
Felipe Lemeb96878492018-12-17 12:22:29 -0800207 Slog.v(TAG, "startSession(): new session for "
208 + ComponentName.flattenToShortString(componentName) + " and id " + sessionId);
Felipe Lemee348dc32018-11-05 12:35:29 -0800209 }
Felipe Lemeb96878492018-12-17 12:22:29 -0800210 mSessions.put(sessionId, newSession);
211 newSession.notifySessionStartedLocked(clientReceiver);
Felipe Lemee348dc32018-11-05 12:35:29 -0800212 }
213
Felipe Leme50b33dc2018-12-20 16:01:39 -0800214 // TODO(b/119613670): log metrics
Felipe Lemee348dc32018-11-05 12:35:29 -0800215 @GuardedBy("mLock")
Felipe Lemeb96878492018-12-17 12:22:29 -0800216 public void finishSessionLocked(@NonNull String sessionId) {
Felipe Leme8e2e3412018-11-14 10:39:29 -0800217 if (!isEnabledLocked()) {
218 return;
219 }
220
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800221 final ContentCaptureServerSession session = mSessions.get(sessionId);
Felipe Lemee348dc32018-11-05 12:35:29 -0800222 if (session == null) {
Felipe Leme8e2e3412018-11-14 10:39:29 -0800223 if (mMaster.debug) {
224 Slog.d(TAG, "finishSession(): no session with id" + sessionId);
225 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800226 return;
227 }
Felipe Lemeb96878492018-12-17 12:22:29 -0800228 if (mMaster.verbose) Slog.v(TAG, "finishSession(): id=" + sessionId);
229 session.removeSelfLocked(/* notifyRemoteService= */ true);
Felipe Leme7a534082018-11-05 15:03:04 -0800230 }
231
232 @GuardedBy("mLock")
Winson Chungfbbb1582018-11-13 16:09:01 -0800233 public boolean sendActivityAssistDataLocked(@NonNull IBinder activityToken,
234 @NonNull Bundle data) {
Felipe Leme749b8892018-12-03 16:30:30 -0800235 final String id = getSessionId(activityToken);
Winson Chungfbbb1582018-11-13 16:09:01 -0800236 if (id != null) {
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800237 final ContentCaptureServerSession session = mSessions.get(id);
Winson Chungfbbb1582018-11-13 16:09:01 -0800238 final Bundle assistData = data.getBundle(ASSIST_KEY_DATA);
239 final AssistStructure assistStructure = data.getParcelable(ASSIST_KEY_STRUCTURE);
240 final AssistContent assistContent = data.getParcelable(ASSIST_KEY_CONTENT);
241 final SnapshotData snapshotData = new SnapshotData(assistData,
242 assistStructure, assistContent);
243 session.sendActivitySnapshotLocked(snapshotData);
244 return true;
245 } else {
246 Slog.e(TAG, "Failed to notify activity assist data for activity: " + activityToken);
247 }
248 return false;
249 }
250
251 @GuardedBy("mLock")
Felipe Leme749b8892018-12-03 16:30:30 -0800252 public void removeSessionLocked(@NonNull String sessionId) {
Felipe Lemea7bdb142018-11-05 16:29:29 -0800253 mSessions.remove(sessionId);
Felipe Lemee348dc32018-11-05 12:35:29 -0800254 }
255
Felipe Leme927fbf02018-11-12 17:27:23 -0800256 @GuardedBy("mLock")
Felipe Leme749b8892018-12-03 16:30:30 -0800257 public boolean isContentCaptureServiceForUserLocked(int uid) {
Felipe Leme927fbf02018-11-12 17:27:23 -0800258 return uid == getServiceUidLocked();
259 }
260
Felipe Leme284ad1c2018-11-15 18:16:12 -0800261 @GuardedBy("mLock")
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800262 private ContentCaptureServerSession getSession(@NonNull IBinder activityToken) {
Felipe Leme284ad1c2018-11-15 18:16:12 -0800263 for (int i = 0; i < mSessions.size(); i++) {
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800264 final ContentCaptureServerSession session = mSessions.valueAt(i);
Felipe Leme284ad1c2018-11-15 18:16:12 -0800265 if (session.mActivityToken.equals(activityToken)) {
266 return session;
267 }
268 }
269 return null;
270 }
271
Felipe Leme8e2e3412018-11-14 10:39:29 -0800272 /**
273 * Destroys the service and all state associated with it.
274 *
275 * <p>Called when the service was disabled (for example, if the settings change).
276 */
277 @GuardedBy("mLock")
278 public void destroyLocked() {
279 if (mMaster.debug) Slog.d(TAG, "destroyLocked()");
Felipe Leme5933efb2018-11-29 16:08:49 -0800280 destroySessionsLocked();
281 }
282
283 @GuardedBy("mLock")
284 void destroySessionsLocked() {
Felipe Leme8e2e3412018-11-14 10:39:29 -0800285 final int numSessions = mSessions.size();
286 for (int i = 0; i < numSessions; i++) {
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800287 final ContentCaptureServerSession session = mSessions.valueAt(i);
Felipe Leme8e2e3412018-11-14 10:39:29 -0800288 session.destroyLocked(true);
289 }
290 mSessions.clear();
291 }
292
Felipe Leme5933efb2018-11-29 16:08:49 -0800293 @GuardedBy("mLock")
294 void listSessionsLocked(ArrayList<String> output) {
295 final int numSessions = mSessions.size();
296 for (int i = 0; i < numSessions; i++) {
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800297 final ContentCaptureServerSession session = mSessions.valueAt(i);
Felipe Leme5933efb2018-11-29 16:08:49 -0800298 output.add(session.toShortString());
299 }
300 }
301
Felipe Lemee348dc32018-11-05 12:35:29 -0800302 @Override
303 protected void dumpLocked(String prefix, PrintWriter pw) {
304 super.dumpLocked(prefix, pw);
Felipe Leme50b33dc2018-12-20 16:01:39 -0800305
306 final String prefix2 = prefix + " ";
307 if (mRemoteService != null) {
308 pw.print(prefix); pw.println("remote service:");
309 mRemoteService.dump(prefix2, pw);
310 }
311
Felipe Lemee348dc32018-11-05 12:35:29 -0800312 if (mSessions.isEmpty()) {
313 pw.print(prefix); pw.println("no sessions");
314 } else {
315 final int size = mSessions.size();
316 pw.print(prefix); pw.print("number sessions: "); pw.println(size);
Felipe Lemee348dc32018-11-05 12:35:29 -0800317 for (int i = 0; i < size; i++) {
318 pw.print(prefix); pw.print("session@"); pw.println(i);
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800319 final ContentCaptureServerSession session = mSessions.valueAt(i);
Felipe Lemee348dc32018-11-05 12:35:29 -0800320 session.dumpLocked(prefix2, pw);
321 }
322 }
323 }
324
Winson Chungfbbb1582018-11-13 16:09:01 -0800325 /**
Felipe Leme749b8892018-12-03 16:30:30 -0800326 * Returns the session id associated with the given activity.
Winson Chungfbbb1582018-11-13 16:09:01 -0800327 */
328 @GuardedBy("mLock")
Felipe Leme749b8892018-12-03 16:30:30 -0800329 private String getSessionId(@NonNull IBinder activityToken) {
Winson Chungfbbb1582018-11-13 16:09:01 -0800330 for (int i = 0; i < mSessions.size(); i++) {
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800331 ContentCaptureServerSession session = mSessions.valueAt(i);
Winson Chungfbbb1582018-11-13 16:09:01 -0800332 if (session.isActivitySession(activityToken)) {
333 return mSessions.keyAt(i);
334 }
335 }
336 return null;
337 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800338}