blob: 872fe4229479656af14faa0c81a456394df4ad3f [file] [log] [blame]
/*
* 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;
}
}
}