summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Volnov <volnov@google.com>2020-01-24 22:05:27 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2020-01-24 22:05:27 +0000
commitac90cd8bf9f48c81046d15a28b636b844c078913 (patch)
tree7ed1ea655842de796a0725e67a0b9a5eec0f0823
parent78acb10560bc6d92e096f09384d96d3adf0b9fb8 (diff)
parent6e049014a75aef84c6c76bfa8fcaf761f95a5d2d (diff)
Merge "Implement data sharing API for Content Capture."
-rw-r--r--api/current.txt19
-rwxr-xr-xapi/system-current.txt11
-rw-r--r--core/java/android/service/contentcapture/ContentCaptureService.java113
-rw-r--r--core/java/android/service/contentcapture/DataShareCallback.java47
-rw-r--r--core/java/android/service/contentcapture/DataShareReadAdapter.java47
-rw-r--r--core/java/android/service/contentcapture/IContentCaptureService.aidl3
-rw-r--r--core/java/android/service/contentcapture/IDataShareCallback.aidl25
-rw-r--r--core/java/android/service/contentcapture/IDataShareReadAdapter.aidl25
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureManager.java86
-rw-r--r--core/java/android/view/contentcapture/DataShareRequest.aidl18
-rw-r--r--core/java/android/view/contentcapture/DataShareRequest.java207
-rw-r--r--core/java/android/view/contentcapture/DataShareWriteAdapter.java58
-rw-r--r--core/java/android/view/contentcapture/IContentCaptureManager.aidl7
-rw-r--r--core/java/android/view/contentcapture/IDataShareWriteAdapter.aidl28
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java229
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java12
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java7
17 files changed, 942 insertions, 0 deletions
diff --git a/api/current.txt b/api/current.txt
index 4ab54b3654b8..26c4493d2eac 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -56148,6 +56148,7 @@ package android.view.contentcapture {
method public boolean isContentCaptureEnabled();
method public void removeData(@NonNull android.view.contentcapture.DataRemovalRequest);
method public void setContentCaptureEnabled(boolean);
+ method public void shareData(@NonNull android.view.contentcapture.DataShareRequest, @NonNull java.util.concurrent.Executor, @NonNull android.view.contentcapture.DataShareWriteAdapter);
}
public abstract class ContentCaptureSession implements java.lang.AutoCloseable {
@@ -56196,6 +56197,24 @@ package android.view.contentcapture {
method @NonNull public android.content.LocusId getLocusId();
}
+ public final class DataShareRequest implements android.os.Parcelable {
+ ctor public DataShareRequest(@Nullable android.content.LocusId, @NonNull String);
+ method public int describeContents();
+ method @Nullable public android.content.LocusId getLocusId();
+ method @NonNull public String getMimeType();
+ method @NonNull public String getPackageName();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.contentcapture.DataShareRequest> CREATOR;
+ }
+
+ public interface DataShareWriteAdapter {
+ method public default void onError(int);
+ method public void onRejected();
+ method public void onWrite(@NonNull android.os.ParcelFileDescriptor, @NonNull android.os.CancellationSignal);
+ field public static final int ERROR_CONCURRENT_REQUEST = 1; // 0x1
+ field public static final int ERROR_UNKNOWN = 2; // 0x2
+ }
+
}
package android.view.inline {
diff --git a/api/system-current.txt b/api/system-current.txt
index 5691bf3fddbc..168bccb369d6 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -10140,6 +10140,7 @@ package android.service.contentcapture {
method public void onContentCaptureEvent(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.view.contentcapture.ContentCaptureEvent);
method public void onCreateContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext, @NonNull android.view.contentcapture.ContentCaptureSessionId);
method public void onDataRemovalRequest(@NonNull android.view.contentcapture.DataRemovalRequest);
+ method public void onDataShareRequest(@NonNull android.view.contentcapture.DataShareRequest, @NonNull android.service.contentcapture.DataShareCallback);
method public void onDestroyContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureSessionId);
method public void onDisconnected();
method public final void setContentCaptureConditions(@NonNull String, @Nullable java.util.Set<android.view.contentcapture.ContentCaptureCondition>);
@@ -10148,6 +10149,16 @@ package android.service.contentcapture {
field public static final String SERVICE_META_DATA = "android.content_capture";
}
+ public interface DataShareCallback {
+ method public void onAccept(@NonNull java.util.concurrent.Executor, @NonNull android.service.contentcapture.DataShareReadAdapter);
+ method public void onReject();
+ }
+
+ public interface DataShareReadAdapter {
+ method public void onError(int);
+ method public void onStart(@NonNull android.os.ParcelFileDescriptor, @NonNull android.os.CancellationSignal);
+ }
+
public final class SnapshotData implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.app.assist.AssistContent getAssistContent();
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 3d82946876d1..36e2d1f6b251 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -34,9 +34,12 @@ import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
+import android.os.ICancellationSignal;
import android.os.Looper;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
import android.util.Slog;
@@ -49,15 +52,20 @@ import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.ContentCaptureSession;
import android.view.contentcapture.ContentCaptureSessionId;
import android.view.contentcapture.DataRemovalRequest;
+import android.view.contentcapture.DataShareRequest;
import android.view.contentcapture.IContentCaptureDirectManager;
import android.view.contentcapture.MainContentCaptureSession;
import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.Preconditions;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* A service used to capture the content of the screen to provide contextual data in other areas of
@@ -166,6 +174,12 @@ public abstract class ContentCaptureService extends Service {
}
@Override
+ public void onDataShared(DataShareRequest request, IDataShareCallback callback) {
+ mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnDataShared,
+ ContentCaptureService.this, request, callback));
+ }
+
+ @Override
public void onActivityEvent(ActivityEvent event) {
mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnActivityEvent,
ContentCaptureService.this, event));
@@ -318,6 +332,21 @@ public abstract class ContentCaptureService extends Service {
}
/**
+ * Notifies the service that data has been shared via a readable file.
+ *
+ * @param request request object containing information about data being shared
+ * @param callback callback to be fired with response on whether the request is "needed" and can
+ * be handled by the Content Capture service.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void onDataShareRequest(@NonNull DataShareRequest request,
+ @NonNull DataShareCallback callback) {
+ if (sVerbose) Log.v(TAG, "onDataShareRequest()");
+ }
+
+ /**
* Notifies the service of {@link SnapshotData snapshot data} associated with a session.
*
* @param sessionId the session's Id
@@ -505,6 +534,37 @@ public abstract class ContentCaptureService extends Service {
onDataRemovalRequest(request);
}
+ private void handleOnDataShared(@NonNull DataShareRequest request,
+ IDataShareCallback callback) {
+ onDataShareRequest(request, new DataShareCallback() {
+
+ @Override
+ public void onAccept(@NonNull Executor executor,
+ @NonNull DataShareReadAdapter adapter) {
+ Preconditions.checkNotNull(adapter);
+ Preconditions.checkNotNull(executor);
+
+ DataShareReadAdapterDelegate delegate =
+ new DataShareReadAdapterDelegate(executor, adapter);
+
+ try {
+ callback.accept(delegate);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to accept data sharing", e);
+ }
+ }
+
+ @Override
+ public void onReject() {
+ try {
+ callback.reject();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to reject data sharing", e);
+ }
+ }
+ });
+ }
+
private void handleOnActivityEvent(@NonNull ActivityEvent event) {
onActivityEvent(event);
}
@@ -589,4 +649,57 @@ public abstract class ContentCaptureService extends Service {
Log.e(TAG, "failed to write flush metrics: " + e);
}
}
+
+ private static class DataShareReadAdapterDelegate extends IDataShareReadAdapter.Stub {
+
+ private final Object mLock = new Object();
+ private final WeakReference<DataShareReadAdapter> mAdapterReference;
+ private final WeakReference<Executor> mExecutorReference;
+
+ DataShareReadAdapterDelegate(Executor executor, DataShareReadAdapter adapter) {
+ Preconditions.checkNotNull(executor);
+ Preconditions.checkNotNull(adapter);
+
+ mExecutorReference = new WeakReference<>(executor);
+ mAdapterReference = new WeakReference<>(adapter);
+ }
+
+ @Override
+ public void start(ParcelFileDescriptor fd, ICancellationSignal remoteCancellationSignal)
+ throws RemoteException {
+ synchronized (mLock) {
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ cancellationSignal.setRemote(remoteCancellationSignal);
+
+ executeAdapterMethodLocked(
+ adapter -> adapter.onStart(fd, cancellationSignal), "onStart");
+ }
+ }
+
+ @Override
+ public void error(int errorCode) throws RemoteException {
+ synchronized (mLock) {
+ executeAdapterMethodLocked(
+ adapter -> adapter.onError(errorCode), "onError");
+ }
+ }
+
+ private void executeAdapterMethodLocked(Consumer<DataShareReadAdapter> adapterFn,
+ String methodName) {
+ DataShareReadAdapter adapter = mAdapterReference.get();
+ Executor executor = mExecutorReference.get();
+
+ if (adapter == null || executor == null) {
+ Slog.w(TAG, "Can't execute " + methodName + "(), references have been GC'ed");
+ return;
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> adapterFn.accept(adapter));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
}
diff --git a/core/java/android/service/contentcapture/DataShareCallback.java b/core/java/android/service/contentcapture/DataShareCallback.java
new file mode 100644
index 000000000000..e3c7bb3cd24f
--- /dev/null
+++ b/core/java/android/service/contentcapture/DataShareCallback.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 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 android.service.contentcapture;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Callback for the Content Capture Service to accept or reject the data share request from a client
+ * app.
+ *
+ * If the request is rejected, client app would receive a signal and the data share session wouldn't
+ * be started.
+ *
+ * @hide
+ **/
+@SystemApi
+public interface DataShareCallback {
+
+ /** Accept the data share.
+ *
+ * @param executor executor to be used for running the adapter in.
+ * @param adapter adapter to be used for the share operation
+ */
+ void onAccept(@NonNull @CallbackExecutor Executor executor,
+ @NonNull DataShareReadAdapter adapter);
+
+ /** Reject the data share. */
+ void onReject();
+}
diff --git a/core/java/android/service/contentcapture/DataShareReadAdapter.java b/core/java/android/service/contentcapture/DataShareReadAdapter.java
new file mode 100644
index 000000000000..d9350ba5d774
--- /dev/null
+++ b/core/java/android/service/contentcapture/DataShareReadAdapter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 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 android.service.contentcapture;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Adapter class to be used for the Content Capture Service app to propagate the status of the
+ * session
+ *
+ * @hide
+ **/
+@SystemApi
+public interface DataShareReadAdapter {
+
+ /**
+ * Signals the start of the data sharing session.
+ *
+ * @param fd file descriptor to use for reading data, that's being shared
+ * @param cancellationSignal cancellation signal to use if data is no longer needed and the
+ * session needs to be terminated.
+ **/
+ void onStart(@NonNull ParcelFileDescriptor fd, @NonNull CancellationSignal cancellationSignal);
+
+ /**
+ * Signals that the session failed to start or terminated unsuccessfully (e.g. due to a
+ * timeout).
+ **/
+ void onError(int errorCode);
+}
diff --git a/core/java/android/service/contentcapture/IContentCaptureService.aidl b/core/java/android/service/contentcapture/IContentCaptureService.aidl
index a7578af94004..277d82b5d909 100644
--- a/core/java/android/service/contentcapture/IContentCaptureService.aidl
+++ b/core/java/android/service/contentcapture/IContentCaptureService.aidl
@@ -20,8 +20,10 @@ import android.content.ComponentName;
import android.os.IBinder;
import android.service.contentcapture.ActivityEvent;
import android.service.contentcapture.SnapshotData;
+import android.service.contentcapture.IDataShareCallback;
import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.DataRemovalRequest;
+import android.view.contentcapture.DataShareRequest;
import com.android.internal.os.IResultReceiver;
@@ -40,5 +42,6 @@ oneway interface IContentCaptureService {
void onSessionFinished(int sessionId);
void onActivitySnapshot(int sessionId, in SnapshotData snapshotData);
void onDataRemovalRequest(in DataRemovalRequest request);
+ void onDataShared(in DataShareRequest request, in IDataShareCallback callback);
void onActivityEvent(in ActivityEvent event);
}
diff --git a/core/java/android/service/contentcapture/IDataShareCallback.aidl b/core/java/android/service/contentcapture/IDataShareCallback.aidl
new file mode 100644
index 000000000000..c1aa1bb7dcb5
--- /dev/null
+++ b/core/java/android/service/contentcapture/IDataShareCallback.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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 android.service.contentcapture;
+
+import android.service.contentcapture.IDataShareReadAdapter;
+
+/** @hide */
+oneway interface IDataShareCallback {
+ void accept(in IDataShareReadAdapter adapter);
+ void reject();
+}
diff --git a/core/java/android/service/contentcapture/IDataShareReadAdapter.aidl b/core/java/android/service/contentcapture/IDataShareReadAdapter.aidl
new file mode 100644
index 000000000000..73da5d515edc
--- /dev/null
+++ b/core/java/android/service/contentcapture/IDataShareReadAdapter.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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 android.service.contentcapture;
+
+import android.os.ICancellationSignal;
+
+/** @hide */
+oneway interface IDataShareReadAdapter {
+ void start(in ParcelFileDescriptor fd, in ICancellationSignal cancellationSignal);
+ void error(int errorCode);
+}
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 6040abd4f5f6..81c83834098c 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -19,6 +19,7 @@ import static android.view.contentcapture.ContentCaptureHelper.sDebug;
import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
import static android.view.contentcapture.ContentCaptureHelper.toSet;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -30,12 +31,16 @@ import android.content.ComponentName;
import android.content.ContentCaptureOptions;
import android.content.Context;
import android.graphics.Canvas;
+import android.os.Binder;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
+import android.util.Slog;
import android.view.View;
import android.view.ViewStructure;
import android.view.WindowManager;
@@ -48,8 +53,11 @@ import com.android.internal.util.SyncResultReceiver;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* <p>The {@link ContentCaptureManager} provides additional ways for for apps to
@@ -629,6 +637,33 @@ public final class ContentCaptureManager {
}
/**
+ * Called by the app to request data sharing via writing to a file.
+ *
+ * <p>The ContentCaptureService app will receive a read-only file descriptor pointing to the
+ * same file and will be able to read data being shared from it.
+ *
+ * <p>Note: using this API doesn't guarantee the app staying alive and is "best-effort".
+ * Starting a foreground service would minimize the chances of the app getting killed during the
+ * file sharing session.
+ *
+ * @param request object specifying details of the data being shared.
+ */
+ public void shareData(@NonNull DataShareRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull DataShareWriteAdapter dataShareWriteAdapter) {
+ Preconditions.checkNotNull(request);
+ Preconditions.checkNotNull(dataShareWriteAdapter);
+ Preconditions.checkNotNull(executor);
+
+ try {
+ mService.shareData(request,
+ new DataShareAdapterDelegate(executor, dataShareWriteAdapter));
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Runs a sync method in the service, properly handling exceptions.
*
* @throws SecurityException if caller is not allowed to execute the method.
@@ -675,4 +710,55 @@ public final class ContentCaptureManager {
private interface MyRunnable {
void run(@NonNull SyncResultReceiver receiver) throws RemoteException;
}
+
+ private static class DataShareAdapterDelegate extends IDataShareWriteAdapter.Stub {
+
+ private final WeakReference<DataShareWriteAdapter> mAdapterReference;
+ private final WeakReference<Executor> mExecutorReference;
+
+ private DataShareAdapterDelegate(Executor executor, DataShareWriteAdapter adapter) {
+ Preconditions.checkNotNull(executor);
+ Preconditions.checkNotNull(adapter);
+
+ mExecutorReference = new WeakReference<>(executor);
+ mAdapterReference = new WeakReference<>(adapter);
+ }
+
+ @Override
+ public void write(ParcelFileDescriptor destination)
+ throws RemoteException {
+ // TODO(b/148264965): implement this.
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ executeAdapterMethodLocked(adapter -> adapter.onWrite(destination, cancellationSignal),
+ "onWrite");
+ }
+
+ @Override
+ public void error(int errorCode) throws RemoteException {
+ executeAdapterMethodLocked(adapter -> adapter.onError(errorCode), "onError");
+ }
+
+ @Override
+ public void rejected() throws RemoteException {
+ executeAdapterMethodLocked(DataShareWriteAdapter::onRejected, "onRejected");
+ }
+
+ private void executeAdapterMethodLocked(Consumer<DataShareWriteAdapter> adapterFn,
+ String methodName) {
+ DataShareWriteAdapter adapter = mAdapterReference.get();
+ Executor executor = mExecutorReference.get();
+
+ if (adapter == null || executor == null) {
+ Slog.w(TAG, "Can't execute " + methodName + "(), references have been GC'ed");
+ return;
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> adapterFn.accept(adapter));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
}
diff --git a/core/java/android/view/contentcapture/DataShareRequest.aidl b/core/java/android/view/contentcapture/DataShareRequest.aidl
new file mode 100644
index 000000000000..75073e411ec8
--- /dev/null
+++ b/core/java/android/view/contentcapture/DataShareRequest.aidl
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2020 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 android.view.contentcapture;
+
+parcelable DataShareRequest;
diff --git a/core/java/android/view/contentcapture/DataShareRequest.java b/core/java/android/view/contentcapture/DataShareRequest.java
new file mode 100644
index 000000000000..78c0ef9568ba
--- /dev/null
+++ b/core/java/android/view/contentcapture/DataShareRequest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2020 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 android.view.contentcapture;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.content.LocusId;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Preconditions;
+
+/** Container class representing a request to share data with Content Capture service. */
+@DataClass(
+ genConstructor = false,
+ genEqualsHashCode = true,
+ genHiddenConstDefs = true,
+ genParcelable = true,
+ genToString = true
+)
+public final class DataShareRequest implements Parcelable {
+
+ /** Name of the package making the request. */
+ @NonNull private final String mPackageName;
+
+ /** Locus id helping to identify what data is being shared. */
+ @Nullable private final LocusId mLocusId;
+
+ /** MIME type of the data being shared. */
+ @NonNull private final String mMimeType;
+
+ /** Constructs a request to share data with the Content Capture Service. */
+ public DataShareRequest(@Nullable LocusId locusId, @NonNull String mimeType) {
+ Preconditions.checkNotNull(mimeType);
+
+ mPackageName = ActivityThread.currentActivityThread().getApplication().getPackageName();
+ mLocusId = locusId;
+ mMimeType = mimeType;
+ }
+
+
+
+ // Code below generated by codegen v1.0.14.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/contentcapture/DataShareRequest.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Name of the package making the request.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Locus id helping to identify what data is being shared.
+ */
+ @DataClass.Generated.Member
+ public @Nullable LocusId getLocusId() {
+ return mLocusId;
+ }
+
+ /**
+ * MIME type of the data being shared.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getMimeType() {
+ return mMimeType;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "DataShareRequest { " +
+ "packageName = " + mPackageName + ", " +
+ "locusId = " + mLocusId + ", " +
+ "mimeType = " + mMimeType +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(DataShareRequest other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DataShareRequest that = (DataShareRequest) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mPackageName, that.mPackageName)
+ && java.util.Objects.equals(mLocusId, that.mLocusId)
+ && java.util.Objects.equals(mMimeType, that.mMimeType);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mLocusId);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mMimeType);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mLocusId != null) flg |= 0x2;
+ dest.writeByte(flg);
+ dest.writeString(mPackageName);
+ if (mLocusId != null) dest.writeTypedObject(mLocusId, flags);
+ dest.writeString(mMimeType);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ DataShareRequest(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ String packageName = in.readString();
+ LocusId locusId = (flg & 0x2) == 0 ? null : (LocusId) in.readTypedObject(LocusId.CREATOR);
+ String mimeType = in.readString();
+
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mLocusId = locusId;
+ this.mMimeType = mimeType;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mMimeType);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<DataShareRequest> CREATOR
+ = new Parcelable.Creator<DataShareRequest>() {
+ @Override
+ public DataShareRequest[] newArray(int size) {
+ return new DataShareRequest[size];
+ }
+
+ @Override
+ public DataShareRequest createFromParcel(@NonNull Parcel in) {
+ return new DataShareRequest(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1579870254459L,
+ codegenVersion = "1.0.14",
+ sourceFile = "frameworks/base/core/java/android/view/contentcapture/DataShareRequest.java",
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable android.content.LocusId mLocusId\nprivate final @android.annotation.NonNull java.lang.String mMimeType\nclass DataShareRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/view/contentcapture/DataShareWriteAdapter.java b/core/java/android/view/contentcapture/DataShareWriteAdapter.java
new file mode 100644
index 000000000000..f791fea7ee8d
--- /dev/null
+++ b/core/java/android/view/contentcapture/DataShareWriteAdapter.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 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 android.view.contentcapture;
+
+import android.annotation.NonNull;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+
+/** Adapter class used by apps to share data with the Content Capture service. */
+public interface DataShareWriteAdapter {
+
+ /** Request has been rejected, because a concurrent data share sessions is in progress. */
+ int ERROR_CONCURRENT_REQUEST = 1;
+
+ /** Data share session timed out. */
+ int ERROR_UNKNOWN = 2;
+
+ /**
+ * Method invoked when the data share session has been started and the app needs to start
+ * writing into the file used for sharing.
+ *
+ * <p>App needs to handle explicitly cases when the file descriptor is closed and handle
+ * gracefully if IOExceptions happen.
+ *
+ * @param destination file descriptor used to write data into
+ * @param cancellationSignal cancellation signal that the app can use to subscribe to cancel
+ * operations.
+ */
+ void onWrite(@NonNull ParcelFileDescriptor destination,
+ @NonNull CancellationSignal cancellationSignal);
+
+ /** Data share sessions has been rejected by the Content Capture service. */
+ void onRejected();
+
+ /**
+ * Method invoked when an error occurred, for example sessions has not been started or
+ * terminated unsuccessfully.
+ *
+ * @param errorCode the error code corresponding to an ERROR_* value.
+ */
+ default void onError(int errorCode) {
+ /* do nothing - stub */
+ }
+}
diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
index 7850b67b8404..e8d85ac69907 100644
--- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl
+++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
@@ -20,6 +20,8 @@ import android.content.ComponentName;
import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureEvent;
import android.view.contentcapture.DataRemovalRequest;
+import android.view.contentcapture.DataShareRequest;
+import android.view.contentcapture.IDataShareWriteAdapter;
import android.os.IBinder;
import com.android.internal.os.IResultReceiver;
@@ -64,6 +66,11 @@ oneway interface IContentCaptureManager {
void removeData(in DataRemovalRequest request);
/**
+ * Requests sharing of a binary data with the content capture service.
+ */
+ void shareData(in DataShareRequest request, in IDataShareWriteAdapter adapter);
+
+ /**
* Returns whether the content capture feature is enabled for the calling user.
*/
void isContentCaptureFeatureEnabled(in IResultReceiver result);
diff --git a/core/java/android/view/contentcapture/IDataShareWriteAdapter.aidl b/core/java/android/view/contentcapture/IDataShareWriteAdapter.aidl
new file mode 100644
index 000000000000..80924ef78f85
--- /dev/null
+++ b/core/java/android/view/contentcapture/IDataShareWriteAdapter.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 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 android.view.contentcapture;
+
+import android.os.ICancellationSignal;
+
+/**
+ * @hide
+ */
+oneway interface IDataShareWriteAdapter {
+ void write(in ParcelFileDescriptor destination);
+ void error(int errorCode);
+ void rejected();
+}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 8eca62a4932b..9245a1da43b2 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -45,7 +45,12 @@ import android.database.ContentObserver;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -55,8 +60,11 @@ import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.provider.Settings;
import android.service.contentcapture.ActivityEvent.ActivityEventType;
+import android.service.contentcapture.IDataShareCallback;
+import android.service.contentcapture.IDataShareReadAdapter;
import android.util.ArraySet;
import android.util.LocalLog;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -64,7 +72,10 @@ import android.view.contentcapture.ContentCaptureCondition;
import android.view.contentcapture.ContentCaptureHelper;
import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.DataRemovalRequest;
+import android.view.contentcapture.DataShareRequest;
+import android.view.contentcapture.DataShareWriteAdapter;
import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.IDataShareWriteAdapter;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.infra.AbstractRemoteService;
@@ -77,9 +88,16 @@ import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.FrameworkResourcesServiceNameResolver;
import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
/**
* A service used to observe the contents of the screen.
@@ -94,6 +112,9 @@ public final class ContentCaptureManagerService extends
static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions";
private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+ private static final int MAX_DATA_SHARE_FILE_DESCRIPTORS_TTL_MS = 1_000 * 60 * 5; // 5 minutes
+ private static final int MAX_CONCURRENT_FILE_SHARING_REQUESTS = 10;
+ private static final int DATA_SHARE_BYTE_BUFFER_LENGTH = 1_024;
private final LocalService mLocalService = new LocalService();
@@ -126,6 +147,12 @@ public final class ContentCaptureManagerService extends
@GuardedBy("mLock") int mDevCfgLogHistorySize;
@GuardedBy("mLock") int mDevCfgIdleUnbindTimeoutMs;
+ private final Executor mDataShareExecutor = Executors.newCachedThreadPool();
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ @GuardedBy("mLock")
+ private final Set<String> mPackagesWithShareRequests = new HashSet<>();
+
final GlobalContentCaptureOptions mGlobalContentCaptureOptions =
new GlobalContentCaptureOptions();
@@ -618,6 +645,33 @@ public final class ContentCaptureManagerService extends
}
@Override
+ public void shareData(@NonNull DataShareRequest request,
+ @NonNull IDataShareWriteAdapter clientAdapter) {
+ Preconditions.checkNotNull(request);
+ Preconditions.checkNotNull(clientAdapter);
+
+ assertCalledByPackageOwner(request.getPackageName());
+
+ final int userId = UserHandle.getCallingUserId();
+ synchronized (mLock) {
+ final ContentCapturePerUserService service = getServiceForUserLocked(userId);
+
+ if (mPackagesWithShareRequests.size() >= MAX_CONCURRENT_FILE_SHARING_REQUESTS
+ || mPackagesWithShareRequests.contains(request.getPackageName())) {
+ try {
+ clientAdapter.error(DataShareWriteAdapter.ERROR_CONCURRENT_REQUEST);
+ } catch (RemoteException e) {
+ Slog.e(mTag, "Failed to send error message to client");
+ }
+ return;
+ }
+
+ service.onDataSharedLocked(request,
+ new DataShareCallbackDelegate(request, clientAdapter));
+ }
+ }
+
+ @Override
public void isContentCaptureFeatureEnabled(@NonNull IResultReceiver result) {
boolean enabled;
synchronized (mLock) {
@@ -860,4 +914,179 @@ public final class ContentCaptureManagerService extends
}
}
}
+
+ // TODO(b/148265162): DataShareCallbackDelegate should be a static class keeping week references
+ // to the needed info
+ private class DataShareCallbackDelegate extends IDataShareCallback.Stub {
+
+ @NonNull private final DataShareRequest mDataShareRequest;
+ @NonNull private final IDataShareWriteAdapter mClientAdapter;
+
+ DataShareCallbackDelegate(@NonNull DataShareRequest dataShareRequest,
+ @NonNull IDataShareWriteAdapter clientAdapter) {
+ mDataShareRequest = dataShareRequest;
+ mClientAdapter = clientAdapter;
+ }
+
+ @Override
+ public void accept(IDataShareReadAdapter serviceAdapter)
+ throws RemoteException {
+ Slog.i(mTag, "Data share request accepted by Content Capture service");
+
+ Pair<ParcelFileDescriptor, ParcelFileDescriptor> clientPipe = createPipe();
+ if (clientPipe == null) {
+ mClientAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN);
+ serviceAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN);
+ return;
+ }
+
+ ParcelFileDescriptor source_in = clientPipe.second;
+ ParcelFileDescriptor sink_in = clientPipe.first;
+
+ Pair<ParcelFileDescriptor, ParcelFileDescriptor> servicePipe = createPipe();
+ if (servicePipe == null) {
+ bestEffortCloseFileDescriptors(source_in, sink_in);
+
+ mClientAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN);
+ serviceAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN);
+ return;
+ }
+
+ ParcelFileDescriptor source_out = servicePipe.second;
+ ParcelFileDescriptor sink_out = servicePipe.first;
+
+ ICancellationSignal cancellationSignalTransport =
+ CancellationSignal.createTransport();
+ mPackagesWithShareRequests.add(mDataShareRequest.getPackageName());
+
+ mClientAdapter.write(source_in);
+ serviceAdapter.start(sink_out, cancellationSignalTransport);
+
+ // TODO(b/148264965): use cancellation signals for timeouts and cancelling
+ CancellationSignal cancellationSignal =
+ CancellationSignal.fromTransport(cancellationSignalTransport);
+
+ cancellationSignal.setOnCancelListener(() -> {
+ try {
+ // TODO(b/148264965): this should propagate with the cancellation signal to the
+ // client
+ mClientAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN);
+ } catch (RemoteException e) {
+ Slog.e(mTag, "Failed to propagate cancel operation to the caller", e);
+ }
+ });
+
+ // File descriptor received by the client app will be a copy of the current one. Close
+ // the one that belongs to the system server, so there's only 1 open left for the
+ // current pipe.
+ bestEffortCloseFileDescriptor(source_in);
+
+ mDataShareExecutor.execute(() -> {
+ try (InputStream fis =
+ new ParcelFileDescriptor.AutoCloseInputStream(sink_in);
+ OutputStream fos =
+ new ParcelFileDescriptor.AutoCloseOutputStream(source_out)) {
+
+ byte[] byteBuffer = new byte[DATA_SHARE_BYTE_BUFFER_LENGTH];
+ while (true) {
+ int readBytes = fis.read(byteBuffer);
+
+ if (readBytes == -1) {
+ break;
+ }
+
+ fos.write(byteBuffer, 0 /* offset */, readBytes);
+ }
+ } catch (IOException e) {
+ Slog.e(mTag, "Failed to pipe client and service streams", e);
+ }
+ });
+
+ mHandler.postDelayed(() -> {
+ synchronized (mLock) {
+ mPackagesWithShareRequests.remove(mDataShareRequest.getPackageName());
+
+ // Interaction finished successfully <=> all data has been written to Content
+ // Capture Service. If it hasn't been read successfully, service would be able
+ // to signal through the cancellation signal.
+ boolean finishedSuccessfully = !sink_in.getFileDescriptor().valid()
+ && !source_out.getFileDescriptor().valid();
+
+ if (finishedSuccessfully) {
+ Slog.i(mTag, "Content capture data sharing session terminated "
+ + "successfully for package '"
+ + mDataShareRequest.getPackageName()
+ + "'");
+ } else {
+ Slog.i(mTag, "Reached the timeout of Content Capture data sharing session "
+ + "for package '"
+ + mDataShareRequest.getPackageName()
+ + "', terminating the pipe.");
+ }
+
+ // Ensure all the descriptors are closed after the session.
+ bestEffortCloseFileDescriptors(source_in, sink_in, source_out, sink_out);
+
+ if (!finishedSuccessfully) {
+ try {
+ mClientAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN);
+ } catch (RemoteException e) {
+ Slog.e(mTag, "Failed to call error() to client", e);
+ }
+ try {
+ serviceAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN);
+ } catch (RemoteException e) {
+ Slog.e(mTag, "Failed to call error() to service", e);
+ }
+ }
+ }
+ }, MAX_DATA_SHARE_FILE_DESCRIPTORS_TTL_MS);
+ }
+
+ @Override
+ public void reject() throws RemoteException {
+ Slog.i(mTag, "Data share request rejected by Content Capture service");
+
+ mClientAdapter.rejected();
+ }
+
+ private Pair<ParcelFileDescriptor, ParcelFileDescriptor> createPipe() {
+ ParcelFileDescriptor[] fileDescriptors;
+ try {
+ fileDescriptors = ParcelFileDescriptor.createPipe();
+ } catch (IOException e) {
+ Slog.e(mTag, "Failed to create a content capture data-sharing pipe", e);
+ return null;
+ }
+
+ if (fileDescriptors.length != 2) {
+ Slog.e(mTag, "Failed to create a content capture data-sharing pipe, "
+ + "unexpected number of file descriptors");
+ return null;
+ }
+
+ if (!fileDescriptors[0].getFileDescriptor().valid()
+ || !fileDescriptors[1].getFileDescriptor().valid()) {
+ Slog.e(mTag, "Failed to create a content capture data-sharing pipe, didn't "
+ + "receive a pair of valid file descriptors.");
+ return null;
+ }
+
+ return Pair.create(fileDescriptors[0], fileDescriptors[1]);
+ }
+
+ private void bestEffortCloseFileDescriptor(ParcelFileDescriptor fd) {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ Slog.e(mTag, "Failed to close a file descriptor", e);
+ }
+ }
+
+ private void bestEffortCloseFileDescriptors(ParcelFileDescriptor... fds) {
+ for (ParcelFileDescriptor fd : fds) {
+ bestEffortCloseFileDescriptor(fd);
+ }
+ }
+ }
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index a186d4e7f467..0f1122e3886a 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -54,6 +54,7 @@ import android.service.contentcapture.ContentCaptureService;
import android.service.contentcapture.ContentCaptureServiceInfo;
import android.service.contentcapture.FlushMetrics;
import android.service.contentcapture.IContentCaptureServiceCallback;
+import android.service.contentcapture.IDataShareCallback;
import android.service.contentcapture.SnapshotData;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -63,6 +64,7 @@ import android.util.SparseBooleanArray;
import android.util.StatsLog;
import android.view.contentcapture.ContentCaptureCondition;
import android.view.contentcapture.DataRemovalRequest;
+import android.view.contentcapture.DataShareRequest;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
@@ -375,6 +377,16 @@ final class ContentCapturePerUserService
}
@GuardedBy("mLock")
+ public void onDataSharedLocked(@NonNull DataShareRequest request,
+ IDataShareCallback.Stub dataShareCallback) {
+ if (!isEnabledLocked()) {
+ return;
+ }
+ assertCallerLocked(request.getPackageName());
+ mRemoteService.onDataShareRequest(request, dataShareCallback);
+ }
+
+ @GuardedBy("mLock")
@Nullable
public ComponentName getServiceSettingsActivityLocked() {
if (mInfo == null) return null;
diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
index 01d33b0e5445..c16df0f19943 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
@@ -29,11 +29,13 @@ import android.os.IBinder;
import android.service.contentcapture.ActivityEvent;
import android.service.contentcapture.IContentCaptureService;
import android.service.contentcapture.IContentCaptureServiceCallback;
+import android.service.contentcapture.IDataShareCallback;
import android.service.contentcapture.SnapshotData;
import android.util.Slog;
import android.util.StatsLog;
import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.DataRemovalRequest;
+import android.view.contentcapture.DataShareRequest;
import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
import com.android.internal.os.IResultReceiver;
@@ -145,6 +147,11 @@ final class RemoteContentCaptureService
mComponentName);
}
+ public void onDataShareRequest(@NonNull DataShareRequest request,
+ @NonNull IDataShareCallback.Stub dataShareCallback) {
+ scheduleAsyncRequest((s) -> s.onDataShared(request, dataShareCallback));
+ }
+
/**
* Called by {@link ContentCaptureServerSession} to notify a high-level activity event.
*/