| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.contentcapture; |
| |
| import static android.Manifest.permission.MANAGE_CONTENT_CAPTURE; |
| import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.UserIdInt; |
| import android.app.ActivityManagerInternal; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.RemoteException; |
| import android.os.ResultReceiver; |
| import android.os.ShellCallback; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.util.Slog; |
| import android.view.contentcapture.ContentCaptureEvent; |
| import android.view.contentcapture.IContentCaptureManager; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.os.IResultReceiver; |
| import com.android.internal.util.DumpUtils; |
| import com.android.internal.util.Preconditions; |
| import com.android.server.LocalServices; |
| import com.android.server.infra.AbstractMasterSystemService; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * A service used to observe the contents of the screen. |
| * |
| * <p>The data collected by this service can be analyzed and combined with other sources to provide |
| * contextual data in other areas of the system such as Autofill. |
| */ |
| public final class ContentCaptureManagerService extends |
| AbstractMasterSystemService<ContentCaptureManagerService, ContentCapturePerUserService> { |
| |
| private static final String TAG = ContentCaptureManagerService.class.getSimpleName(); |
| |
| static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions"; |
| |
| private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes |
| |
| @GuardedBy("mLock") |
| private ActivityManagerInternal mAm; |
| |
| private final LocalService mLocalService = new LocalService(); |
| |
| public ContentCaptureManagerService(Context context) { |
| super(context, UserManager.DISALLOW_CONTENT_CAPTURE); |
| } |
| |
| @Override // from AbstractMasterSystemService |
| protected ContentCapturePerUserService newServiceLocked(@UserIdInt int resolvedUserId, |
| boolean disabled) { |
| return new ContentCapturePerUserService(this, mLock, resolvedUserId); |
| } |
| |
| @Override // from SystemService |
| public void onStart() { |
| publishBinderService(CONTENT_CAPTURE_MANAGER_SERVICE, |
| new ContentCaptureManagerServiceStub()); |
| publishLocalService(ContentCaptureManagerInternal.class, mLocalService); |
| } |
| |
| @Override // from AbstractMasterSystemService |
| protected void onServiceRemoved(@NonNull ContentCapturePerUserService service, |
| @UserIdInt int userId) { |
| service.destroyLocked(); |
| } |
| |
| @Override // from AbstractMasterSystemService |
| protected void enforceCallingPermissionForManagement() { |
| getContext().enforceCallingPermission(MANAGE_CONTENT_CAPTURE, TAG); |
| } |
| |
| @Override // from AbstractMasterSystemService |
| protected int getMaximumTemporaryServiceDurationMs() { |
| return MAX_TEMP_SERVICE_DURATION_MS; |
| } |
| |
| // Called by Shell command. |
| void destroySessions(@UserIdInt int userId, @NonNull IResultReceiver receiver) { |
| Slog.i(TAG, "destroySessions() for userId " + userId); |
| enforceCallingPermissionForManagement(); |
| |
| synchronized (mLock) { |
| if (userId != UserHandle.USER_ALL) { |
| final ContentCapturePerUserService service = peekServiceForUserLocked(userId); |
| if (service != null) { |
| service.destroySessionsLocked(); |
| } |
| } else { |
| visitServicesLocked((s) -> s.destroySessionsLocked()); |
| } |
| } |
| |
| try { |
| receiver.send(0, new Bundle()); |
| } catch (RemoteException e) { |
| // Just ignore it... |
| } |
| } |
| |
| // Called by Shell command. |
| void listSessions(int userId, IResultReceiver receiver) { |
| Slog.i(TAG, "listSessions() for userId " + userId); |
| enforceCallingPermissionForManagement(); |
| |
| final Bundle resultData = new Bundle(); |
| final ArrayList<String> sessions = new ArrayList<>(); |
| |
| synchronized (mLock) { |
| if (userId != UserHandle.USER_ALL) { |
| final ContentCapturePerUserService service = peekServiceForUserLocked(userId); |
| if (service != null) { |
| service.listSessionsLocked(sessions); |
| } |
| } else { |
| visitServicesLocked((s) -> s.listSessionsLocked(sessions)); |
| } |
| } |
| |
| resultData.putStringArrayList(RECEIVER_BUNDLE_EXTRA_SESSIONS, sessions); |
| try { |
| receiver.send(0, resultData); |
| } catch (RemoteException e) { |
| // Just ignore it... |
| } |
| } |
| |
| private ActivityManagerInternal getAmInternal() { |
| synchronized (mLock) { |
| if (mAm == null) { |
| mAm = LocalServices.getService(ActivityManagerInternal.class); |
| } |
| } |
| return mAm; |
| } |
| |
| final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub { |
| |
| @Override |
| public void startSession(@UserIdInt int userId, @NonNull IBinder activityToken, |
| @NonNull ComponentName componentName, @NonNull String sessionId, |
| int flags, @NonNull IResultReceiver result) { |
| Preconditions.checkNotNull(activityToken); |
| Preconditions.checkNotNull(componentName); |
| Preconditions.checkNotNull(sessionId); |
| |
| // TODO(b/111276913): refactor getTaskIdForActivity() to also return ComponentName, |
| // so we don't pass it on startSession (same for Autofill) |
| final int taskId = getAmInternal().getTaskIdForActivity(activityToken, false); |
| |
| // TODO(b/111276913): get from AM as well |
| final int displayId = 0; |
| |
| synchronized (mLock) { |
| final ContentCapturePerUserService service = getServiceForUserLocked(userId); |
| service.startSessionLocked(activityToken, componentName, taskId, displayId, |
| sessionId, flags, mAllowInstantService, result); |
| } |
| } |
| |
| @Override |
| public void sendEvents(@UserIdInt int userId, @NonNull String sessionId, |
| @NonNull List<ContentCaptureEvent> events) { |
| Preconditions.checkNotNull(sessionId); |
| Preconditions.checkNotNull(events); |
| |
| synchronized (mLock) { |
| final ContentCapturePerUserService service = getServiceForUserLocked(userId); |
| service.sendEventsLocked(sessionId, events); |
| } |
| } |
| |
| @Override |
| public void finishSession(@UserIdInt int userId, @NonNull String sessionId, |
| @Nullable List<ContentCaptureEvent> events) { |
| Preconditions.checkNotNull(sessionId); |
| |
| synchronized (mLock) { |
| final ContentCapturePerUserService service = getServiceForUserLocked(userId); |
| service.finishSessionLocked(sessionId, events); |
| } |
| } |
| |
| @Override |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; |
| |
| synchronized (mLock) { |
| dumpLocked("", pw); |
| } |
| } |
| |
| @Override |
| public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, |
| String[] args, ShellCallback callback, ResultReceiver resultReceiver) |
| throws RemoteException { |
| new ContentCaptureManagerServiceShellCommand(ContentCaptureManagerService.this).exec( |
| this, in, out, err, args, callback, resultReceiver); |
| } |
| } |
| |
| private final class LocalService extends ContentCaptureManagerInternal { |
| |
| @Override |
| public boolean isContentCaptureServiceForUser(int uid, @UserIdInt int userId) { |
| synchronized (mLock) { |
| final ContentCapturePerUserService service = peekServiceForUserLocked(userId); |
| if (service != null) { |
| return service.isContentCaptureServiceForUserLocked(uid); |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean sendActivityAssistData(@UserIdInt int userId, @NonNull IBinder activityToken, |
| @NonNull Bundle data) { |
| synchronized (mLock) { |
| final ContentCapturePerUserService service = peekServiceForUserLocked(userId); |
| if (service != null) { |
| return service.sendActivityAssistDataLocked(activityToken, data); |
| } |
| } |
| return false; |
| } |
| } |
| } |