Merge "Implement onUserDataRemovalRequest"

This commit is contained in:
TreeHugger Robot
2019-01-24 04:07:02 +00:00
committed by Android (Google) Code Review
11 changed files with 325 additions and 18 deletions

View File

@@ -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.
*/

View File

@@ -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);
}

View File

@@ -189,13 +189,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 */

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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));
}
}

View File

@@ -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());
}
}

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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