Implement onUserDataRemovalRequest
Change-Id: I7c818d42000b6155f9dcaad85b7f9f761741a484 Fixes: 121289354 Test: atest CtsContentCaptureServiceTestCases # still passing Test: manual verification
This commit is contained in:
@@ -116,6 +116,13 @@ public abstract class ContentCaptureService extends Service {
|
||||
mHandler.sendMessage(obtainMessage(ContentCaptureService::handleFinishSession,
|
||||
ContentCaptureService.this, sessionId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserDataRemovalRequest(UserDataRemovalRequest request) {
|
||||
mHandler.sendMessage(
|
||||
obtainMessage(ContentCaptureService::handleOnUserDataRemovalRequest,
|
||||
ContentCaptureService.this, request));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -431,6 +438,10 @@ public abstract class ContentCaptureService extends Service {
|
||||
onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId));
|
||||
}
|
||||
|
||||
private void handleOnUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) {
|
||||
onUserDataRemovalRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given {@code uid} owns the session associated with the event.
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,7 @@ package android.service.contentcapture;
|
||||
import android.os.IBinder;
|
||||
import android.service.contentcapture.SnapshotData;
|
||||
import android.view.contentcapture.ContentCaptureContext;
|
||||
import android.view.contentcapture.UserDataRemovalRequest;
|
||||
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
|
||||
@@ -36,4 +37,5 @@ oneway interface IContentCaptureService {
|
||||
in IResultReceiver clientReceiver);
|
||||
void onSessionFinished(String sessionId);
|
||||
void onActivitySnapshot(String sessionId, in SnapshotData snapshotData);
|
||||
void onUserDataRemovalRequest(in UserDataRemovalRequest request);
|
||||
}
|
||||
|
||||
@@ -191,13 +191,19 @@ public final class ContentCaptureManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the ap to request the Content Capture service to remove user-data associated with
|
||||
* Called by the app to request the Content Capture service to remove user-data associated with
|
||||
* some context.
|
||||
*
|
||||
* @param request object specifying what user data should be removed.
|
||||
*/
|
||||
public void removeUserData(@NonNull UserDataRemovalRequest request) {
|
||||
//TODO(b/111276913): implement
|
||||
Preconditions.checkNotNull(request);
|
||||
|
||||
try {
|
||||
mService.removeUserData(mContext.getUserId(), request);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
|
||||
@@ -19,6 +19,7 @@ package android.view.contentcapture;
|
||||
import android.content.ComponentName;
|
||||
import android.view.contentcapture.ContentCaptureContext;
|
||||
import android.view.contentcapture.ContentCaptureEvent;
|
||||
import android.view.contentcapture.UserDataRemovalRequest;
|
||||
import android.os.IBinder;
|
||||
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
@@ -56,4 +57,9 @@ oneway interface IContentCaptureManager {
|
||||
* provided {@code Bundle} with key "{@code EXTRA}".
|
||||
*/
|
||||
void getReceiverServiceComponentName(int userId, in IResultReceiver result);
|
||||
|
||||
/**
|
||||
* Requests the removal of user data for the provided {@code userId}.
|
||||
*/
|
||||
void removeUserData(int userId, in UserDataRemovalRequest request);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 UserDataRemovalRequest;
|
||||
@@ -17,10 +17,15 @@ package android.view.contentcapture;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.ActivityThread;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.IntArray;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -29,8 +34,35 @@ import java.util.List;
|
||||
*/
|
||||
public final class UserDataRemovalRequest implements Parcelable {
|
||||
|
||||
private UserDataRemovalRequest(Builder builder) {
|
||||
// TODO(b/111276913): implement
|
||||
private final String mPackageName;
|
||||
|
||||
private final boolean mForEverything;
|
||||
private ArrayList<UriRequest> mUriRequests;
|
||||
|
||||
private UserDataRemovalRequest(@NonNull Builder builder) {
|
||||
mPackageName = ActivityThread.currentActivityThread().getApplication().getPackageName();
|
||||
mForEverything = builder.mForEverything;
|
||||
if (builder.mUris != null) {
|
||||
final int size = builder.mUris.size();
|
||||
mUriRequests = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
mUriRequests.add(new UriRequest(builder.mUris.get(i),
|
||||
builder.mRecursive.get(i) == 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private UserDataRemovalRequest(@NonNull Parcel parcel) {
|
||||
mPackageName = parcel.readString();
|
||||
mForEverything = parcel.readBoolean();
|
||||
if (!mForEverything) {
|
||||
final int size = parcel.readInt();
|
||||
mUriRequests = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
mUriRequests.add(new UriRequest((Uri) parcel.readValue(null),
|
||||
parcel.readBoolean()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,9 +72,7 @@ public final class UserDataRemovalRequest implements Parcelable {
|
||||
@SystemApi
|
||||
@NonNull
|
||||
public String getPackageName() {
|
||||
// TODO(b/111276913): implement
|
||||
// TODO(b/111276913): make sure it's set on system_service so it cannot be faked by app
|
||||
return null;
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,8 +82,7 @@ public final class UserDataRemovalRequest implements Parcelable {
|
||||
*/
|
||||
@SystemApi
|
||||
public boolean isForEverything() {
|
||||
// TODO(b/111276913): implement
|
||||
return false;
|
||||
return mForEverything;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,8 +93,7 @@ public final class UserDataRemovalRequest implements Parcelable {
|
||||
@SystemApi
|
||||
@NonNull
|
||||
public List<UriRequest> getUriRequests() {
|
||||
// TODO(b/111276913): implement
|
||||
return null;
|
||||
return mUriRequests;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,6 +101,12 @@ public final class UserDataRemovalRequest implements Parcelable {
|
||||
*/
|
||||
public static final class Builder {
|
||||
|
||||
private boolean mForEverything;
|
||||
private ArrayList<Uri> mUris;
|
||||
private IntArray mRecursive;
|
||||
|
||||
private boolean mDestroyed;
|
||||
|
||||
/**
|
||||
* Requests servive to remove all user data associated with the app's package.
|
||||
*
|
||||
@@ -80,7 +114,12 @@ public final class UserDataRemovalRequest implements Parcelable {
|
||||
*/
|
||||
@NonNull
|
||||
public Builder forEverything() {
|
||||
// TODO(b/111276913): implement
|
||||
throwIfDestroyed();
|
||||
if (mUris != null) {
|
||||
throw new IllegalStateException("Already added Uris");
|
||||
}
|
||||
|
||||
mForEverything = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -94,7 +133,19 @@ public final class UserDataRemovalRequest implements Parcelable {
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder addUri(@NonNull Uri uri, boolean recursive) {
|
||||
// TODO(b/111276913): implement
|
||||
throwIfDestroyed();
|
||||
if (mForEverything) {
|
||||
throw new IllegalStateException("Already is for everything");
|
||||
}
|
||||
Preconditions.checkNotNull(uri);
|
||||
|
||||
if (mUris == null) {
|
||||
mUris = new ArrayList<>();
|
||||
mRecursive = new IntArray();
|
||||
}
|
||||
|
||||
mUris.add(uri);
|
||||
mRecursive.add(recursive ? 1 : 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -103,8 +154,16 @@ public final class UserDataRemovalRequest implements Parcelable {
|
||||
*/
|
||||
@NonNull
|
||||
public UserDataRemovalRequest build() {
|
||||
// TODO(b/111276913): implement / unit test / check built / document exceptions
|
||||
return null;
|
||||
throwIfDestroyed();
|
||||
|
||||
Preconditions.checkState(mForEverything || mUris != null);
|
||||
|
||||
mDestroyed = true;
|
||||
return new UserDataRemovalRequest(this);
|
||||
}
|
||||
|
||||
private void throwIfDestroyed() {
|
||||
Preconditions.checkState(!mDestroyed, "Already destroyed!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +174,17 @@ public final class UserDataRemovalRequest implements Parcelable {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
// TODO(b/111276913): implement
|
||||
parcel.writeString(mPackageName);
|
||||
parcel.writeBoolean(mForEverything);
|
||||
if (!mForEverything) {
|
||||
final int size = mUriRequests.size();
|
||||
parcel.writeInt(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
final UriRequest request = mUriRequests.get(i);
|
||||
parcel.writeValue(request.getUri());
|
||||
parcel.writeBoolean(request.isRecursive());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<UserDataRemovalRequest> CREATOR =
|
||||
@@ -123,8 +192,7 @@ public final class UserDataRemovalRequest implements Parcelable {
|
||||
|
||||
@Override
|
||||
public UserDataRemovalRequest createFromParcel(Parcel parcel) {
|
||||
// TODO(b/111276913): implement
|
||||
return null;
|
||||
return new UserDataRemovalRequest(parcel);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 static org.testng.Assert.assertThrows;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
/**
|
||||
* Unit test for {@link ContentCaptureManager}.
|
||||
*
|
||||
* <p>To run it:
|
||||
* {@code atest FrameworksCoreTests:android.view.contentcapture.ContentCaptureManagerTest}
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ContentCaptureManagerTest {
|
||||
|
||||
@Mock
|
||||
private Context mMockContext;
|
||||
|
||||
private ContentCaptureManager mManager;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
mManager = new ContentCaptureManager(mMockContext, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveUserData_invalid() {
|
||||
assertThrows(NullPointerException.class, () -> mManager.removeUserData(null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.testng.Assert.assertThrows;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
/**
|
||||
* Unit test for {@link UserDataRemovalRequest}.
|
||||
*
|
||||
* <p>To run it:
|
||||
* {@code atest FrameworksCoreTests:android.view.contentcapture.UserDataRemovalRequestTest}
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class UserDataRemovalRequestTest {
|
||||
|
||||
@Mock
|
||||
private final Uri mUri = Uri.parse("content://com.example/");
|
||||
|
||||
private UserDataRemovalRequest.Builder mBuilder = new UserDataRemovalRequest.Builder();
|
||||
|
||||
@Test
|
||||
public void testBuilder_addUri_invalid() {
|
||||
assertThrows(NullPointerException.class, () -> mBuilder.addUri(null, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilder_addUri_valid() {
|
||||
assertThat(mBuilder.addUri(mUri, false)).isNotNull();
|
||||
assertThat(mBuilder.addUri(Uri.parse("content://com.example2"), true)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilder_addUriAfterForEverything() {
|
||||
assertThat(mBuilder.forEverything()).isNotNull();
|
||||
assertThrows(IllegalStateException.class, () -> mBuilder.addUri(mUri, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilder_forEverythingAfterAddingUri() {
|
||||
assertThat(mBuilder.addUri(mUri, false)).isNotNull();
|
||||
assertThrows(IllegalStateException.class, () -> mBuilder.forEverything());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuild_invalid() {
|
||||
assertThrows(IllegalStateException.class, () -> mBuilder.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuild_valid() {
|
||||
assertThat(new UserDataRemovalRequest.Builder().forEverything().build())
|
||||
.isNotNull();
|
||||
assertThat(new UserDataRemovalRequest.Builder().addUri(mUri, false).build())
|
||||
.isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoMoreInteractionsAfterBuild() {
|
||||
assertThat(mBuilder.forEverything().build()).isNotNull();
|
||||
|
||||
assertThrows(IllegalStateException.class, () -> mBuilder.addUri(mUri, false));
|
||||
assertThrows(IllegalStateException.class, () -> mBuilder.forEverything());
|
||||
assertThrows(IllegalStateException.class, () -> mBuilder.build());
|
||||
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import android.os.UserManager;
|
||||
import android.util.LocalLog;
|
||||
import android.util.Slog;
|
||||
import android.view.contentcapture.IContentCaptureManager;
|
||||
import android.view.contentcapture.UserDataRemovalRequest;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
@@ -223,6 +224,15 @@ public final class ContentCaptureManagerService extends
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUserData(@UserIdInt int userId, @NonNull UserDataRemovalRequest request) {
|
||||
Preconditions.checkNotNull(request);
|
||||
synchronized (mLock) {
|
||||
final ContentCapturePerUserService service = getServiceForUserLocked(userId);
|
||||
service.removeUserDataLocked(request);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
|
||||
|
||||
@@ -28,6 +28,7 @@ import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUC
|
||||
import android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.app.assist.AssistStructure;
|
||||
@@ -35,18 +36,22 @@ import android.content.ComponentName;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.service.contentcapture.ContentCaptureService;
|
||||
import android.service.contentcapture.IContentCaptureServiceCallback;
|
||||
import android.service.contentcapture.SnapshotData;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.view.contentcapture.UserDataRemovalRequest;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.contentcapture.RemoteContentCaptureService.ContentCaptureServiceCallbacks;
|
||||
import com.android.server.infra.AbstractPerUserSystemService;
|
||||
|
||||
@@ -248,6 +253,39 @@ final class ContentCapturePerUserService
|
||||
session.removeSelfLocked(/* notifyRemoteService= */ true);
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
public void removeUserDataLocked(@NonNull UserDataRemovalRequest request) {
|
||||
if (!isEnabledLocked()) {
|
||||
return;
|
||||
}
|
||||
assertCallerLocked(request.getPackageName());
|
||||
mRemoteService.onUserDataRemovalRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the component is owned by the caller.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private void assertCallerLocked(@NonNull String packageName) {
|
||||
final PackageManager pm = getContext().getPackageManager();
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
final int packageUid;
|
||||
try {
|
||||
packageUid = pm.getPackageUidAsUser(packageName, UserHandle.getCallingUserId());
|
||||
} catch (NameNotFoundException e) {
|
||||
throw new SecurityException("Could not verify UID for " + packageName);
|
||||
}
|
||||
if (callingUid != packageUid && !LocalServices.getService(ActivityManagerInternal.class)
|
||||
.hasRunningActivity(callingUid, packageName)) {
|
||||
final String[] packages = pm.getPackagesForUid(callingUid);
|
||||
final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid;
|
||||
Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid
|
||||
+ ") passed package (" + packageName + ") owned by UID " + packageUid);
|
||||
|
||||
throw new SecurityException("Invalid package: " + packageName);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
public boolean sendActivityAssistDataLocked(@NonNull IBinder activityToken,
|
||||
@NonNull Bundle data) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.service.contentcapture.SnapshotData;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Slog;
|
||||
import android.view.contentcapture.ContentCaptureContext;
|
||||
import android.view.contentcapture.UserDataRemovalRequest;
|
||||
|
||||
import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
@@ -108,6 +109,13 @@ final class RemoteContentCaptureService
|
||||
scheduleAsyncRequest((s) -> s.onActivitySnapshot(sessionId, snapshotData));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link ContentCaptureServerSession} to request removal of user data.
|
||||
*/
|
||||
public void onUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) {
|
||||
scheduleAsyncRequest((s) -> s.onUserDataRemovalRequest(request));
|
||||
}
|
||||
|
||||
public interface ContentCaptureServiceCallbacks
|
||||
extends VultureCallback<RemoteContentCaptureService> {
|
||||
// NOTE: so far we don't need to notify the callback implementation
|
||||
|
||||
Reference in New Issue
Block a user