diff options
11 files changed, 236 insertions, 13 deletions
diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java index 1727d341bd82..76c4fb8caa0b 100644 --- a/core/java/android/content/ContentCaptureOptions.java +++ b/core/java/android/content/ContentCaptureOptions.java @@ -136,13 +136,18 @@ public final class ContentCaptureOptions implements Parcelable { @Override public String toString() { if (lite) { - return "ContentCaptureOptions [(lite) loggingLevel=" + loggingLevel + "]"; + return "ContentCaptureOptions [loggingLevel=" + loggingLevel + " (lite)]"; } - return "ContentCaptureOptions [loggingLevel=" + loggingLevel + ", maxBufferSize=" - + maxBufferSize + ", idleFlushingFrequencyMs=" + idleFlushingFrequencyMs - + ", textChangeFlushingFrequencyMs=" + textChangeFlushingFrequencyMs - + ", logHistorySize=" + logHistorySize + ", whitelistedComponents=" - + whitelistedComponents + "]"; + final StringBuilder string = new StringBuilder("ContentCaptureOptions ["); + string.append("loggingLevel=").append(loggingLevel) + .append(", maxBufferSize=").append(maxBufferSize) + .append(", idleFlushingFrequencyMs=").append(idleFlushingFrequencyMs) + .append(", textChangeFlushingFrequencyMs=").append(textChangeFlushingFrequencyMs) + .append(", logHistorySize=").append(logHistorySize); + if (whitelistedComponents != null) { + string.append(", whitelisted=").append(whitelistedComponents); + } + return string.append(']').toString(); } /** @hide */ diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java index f83090c6242d..dc57a1591913 100644 --- a/core/java/android/service/contentcapture/ContentCaptureService.java +++ b/core/java/android/service/contentcapture/ContentCaptureService.java @@ -17,6 +17,7 @@ package android.service.contentcapture; import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; +import static android.view.contentcapture.ContentCaptureHelper.toList; import static android.view.contentcapture.ContentCaptureSession.NO_SESSION_ID; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; @@ -54,7 +55,6 @@ import com.android.internal.os.IResultReceiver; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -241,11 +241,17 @@ public abstract class ContentCaptureService extends Service { */ public final void setContentCaptureConditions(@NonNull String packageName, @Nullable Set<ContentCaptureCondition> conditions) { - // TODO(b/129267994): implement - } + final IContentCaptureServiceCallback callback = mCallback; + if (callback == null) { + Log.w(TAG, "setContentCaptureConditions(): no server callback"); + return; + } - private <T> ArrayList<T> toList(@Nullable Set<T> set) { - return set == null ? null : new ArrayList<T>(set); + try { + callback.setContentCaptureConditions(packageName, toList(conditions)); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } /** diff --git a/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl b/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl index 8bc8defede80..0550ad3ea20c 100644 --- a/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl +++ b/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl @@ -17,6 +17,7 @@ package android.service.contentcapture; import android.content.ComponentName; +import android.view.contentcapture.ContentCaptureCondition; import java.util.List; @@ -27,5 +28,6 @@ import java.util.List; */ oneway interface IContentCaptureServiceCallback { void setContentCaptureWhitelist(in List<String> packages, in List<ComponentName> activities); + void setContentCaptureConditions(String packageName, in List<ContentCaptureCondition> conditions); void disableSelf(); } diff --git a/core/java/android/view/contentcapture/ContentCaptureCondition.aidl b/core/java/android/view/contentcapture/ContentCaptureCondition.aidl new file mode 100644 index 000000000000..99f8894408b3 --- /dev/null +++ b/core/java/android/view/contentcapture/ContentCaptureCondition.aidl @@ -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 ContentCaptureCondition; diff --git a/core/java/android/view/contentcapture/ContentCaptureCondition.java b/core/java/android/view/contentcapture/ContentCaptureCondition.java index ed872578d069..cf171d738524 100644 --- a/core/java/android/view/contentcapture/ContentCaptureCondition.java +++ b/core/java/android/view/contentcapture/ContentCaptureCondition.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.content.LocusId; import android.os.Parcel; import android.os.Parcelable; +import android.util.DebugUtils; import com.android.internal.util.Preconditions; @@ -58,7 +59,6 @@ public final class ContentCaptureCondition implements Parcelable { public ContentCaptureCondition(@NonNull LocusId locusId, @Flags int flags) { this.mLocusId = Preconditions.checkNotNull(locusId); this.mFlags = flags; - // TODO(b/129267994): check flags, add test case for null and invalid flags } /** @@ -79,6 +79,42 @@ public final class ContentCaptureCondition implements Parcelable { } @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mFlags; + result = prime * result + ((mLocusId == null) ? 0 : mLocusId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + final ContentCaptureCondition other = (ContentCaptureCondition) obj; + if (mFlags != other.mFlags) return false; + if (mLocusId == null) { + if (other.mLocusId != null) return false; + } else { + if (!mLocusId.equals(other.mLocusId)) return false; + } + return true; + } + + @Override + public String toString() { + final StringBuilder string = new StringBuilder(mLocusId.toString()); // LocusID is PII safe + if (mFlags != 0) { + string + .append(" (") + .append(DebugUtils.flagsToString(ContentCaptureCondition.class, "FLAG_", mFlags)) + .append(')'); + } + return string.toString(); + } + + @Override public int describeContents() { return 0; } diff --git a/core/java/android/view/contentcapture/ContentCaptureHelper.java b/core/java/android/view/contentcapture/ContentCaptureHelper.java index 6bc382907457..c7ca2209d387 100644 --- a/core/java/android/view/contentcapture/ContentCaptureHelper.java +++ b/core/java/android/view/contentcapture/ContentCaptureHelper.java @@ -23,9 +23,14 @@ import static android.view.contentcapture.ContentCaptureManager.LOGGING_LEVEL_VE import android.annotation.Nullable; import android.os.Build; import android.provider.DeviceConfig; +import android.util.ArraySet; import android.util.Log; import android.view.contentcapture.ContentCaptureManager.LoggingLevel; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + /** * Helper class for this package and server's. * @@ -101,6 +106,22 @@ public final class ContentCaptureHelper { } } + /** + * Converts a set to a list. + */ + @Nullable + public static <T> ArrayList<T> toList(@Nullable Set<T> set) { + return set == null ? null : new ArrayList<T>(set); + } + + /** + * Converts a list to a set. + */ + @Nullable + public static <T> ArraySet<T> toSet(@Nullable List<T> list) { + return list == null ? null : new ArraySet<T>(list); + } + private ContentCaptureHelper() { throw new UnsupportedOperationException("contains only static methods"); } diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 4f3325ccf64f..35f802303fa1 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -17,6 +17,7 @@ package android.view.contentcapture; import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; +import static android.view.contentcapture.ContentCaptureHelper.toSet; import android.annotation.IntDef; import android.annotation.NonNull; @@ -46,6 +47,7 @@ import com.android.internal.util.SyncResultReceiver; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Set; /** @@ -518,7 +520,20 @@ public final class ContentCaptureManager { */ @Nullable public Set<ContentCaptureCondition> getContentCaptureConditions() { - return null; // TODO(b/129267994): implement + // NOTE: we could cache the conditions on ContentCaptureOptions, but then it would be stick + // to the lifetime of the app. OTOH, by dynamically calling the server every time, we allow + // the service to fine tune how long-lived apps (like browsers) are whitelisted. + if (!isContentCaptureEnabled() && !mOptions.lite) return null; + + final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); + try { + mService.getContentCaptureConditions(mContext.getPackageName(), resultReceiver); + final ArrayList<ContentCaptureCondition> result = resultReceiver + .getParcelableListResult(); + return toSet(result); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl index 2775029e849a..7335073c59e0 100644 --- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl +++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl @@ -72,4 +72,9 @@ oneway interface IContentCaptureManager { * Returns a ComponentName with the name of custom service activity, if defined. */ void getServiceSettingsActivity(in IResultReceiver result); + + /** + * Returns a list with the ContentCaptureConditions for the package (or null if not defined). + */ + void getContentCaptureConditions(String packageName, in IResultReceiver result); } diff --git a/core/java/com/android/internal/util/SyncResultReceiver.java b/core/java/com/android/internal/util/SyncResultReceiver.java index 60af5117489b..6fad84bb6c43 100644 --- a/core/java/com/android/internal/util/SyncResultReceiver.java +++ b/core/java/com/android/internal/util/SyncResultReceiver.java @@ -23,6 +23,7 @@ import android.os.RemoteException; import com.android.internal.os.IResultReceiver; +import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -97,6 +98,15 @@ public final class SyncResultReceiver extends IResultReceiver.Stub { } /** + * Gets the result from an operation that returns a {@code Parcelable} list. + */ + @Nullable + public <P extends Parcelable> ArrayList<P> getParcelableListResult() throws TimeoutException { + waitResult(); + return mBundle == null ? null : mBundle.getParcelableArrayList(EXTRA); + } + + /** * Gets the optional result from an operation that returns an extra {@code int} (besides the * result code). * @@ -150,6 +160,17 @@ public final class SyncResultReceiver extends IResultReceiver.Stub { } /** + * Creates a bundle for a {@code Parcelable} list so it can be retrieved by + * {@link #getParcelableResult()}. + */ + @NonNull + public static Bundle bundleFor(@Nullable ArrayList<? extends Parcelable> value) { + final Bundle bundle = new Bundle(); + bundle.putParcelableArrayList(EXTRA, value); + return bundle; + } + + /** * Creates a bundle for an {@code int} value so it can be retrieved by * {@link #getParcelableResult()} - typically used to return an extra {@code int} (as the 1st * is returned as the result code). diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index c88d3ae4ea24..2894662becac 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -18,6 +18,7 @@ package com.android.server.contentcapture; import static android.Manifest.permission.MANAGE_CONTENT_CAPTURE; import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE; +import static android.view.contentcapture.ContentCaptureHelper.toList; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_OK; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_SECURITY_EXCEPTION; @@ -56,6 +57,7 @@ import android.util.LocalLog; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.view.contentcapture.ContentCaptureCondition; import android.view.contentcapture.ContentCaptureHelper; import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.IContentCaptureManager; @@ -568,6 +570,8 @@ public final class ContentCaptureManagerService extends @Override public void removeUserData(@NonNull UserDataRemovalRequest request) { Preconditions.checkNotNull(request); + // TODO(b/122959591): check caller uid owns the package name + final int userId = UserHandle.getCallingUserId(); synchronized (mLock) { final ContentCapturePerUserService service = getServiceForUserLocked(userId); @@ -621,6 +625,25 @@ public final class ContentCaptureManagerService extends } @Override + public void getContentCaptureConditions(@NonNull String packageName, + @NonNull IResultReceiver result) { + // TODO(b/122959591): check caller uid owns the package name + + final int userId = UserHandle.getCallingUserId(); + final ArrayList<ContentCaptureCondition> conditions; + synchronized (mLock) { + final ContentCapturePerUserService service = getServiceForUserLocked(userId); + conditions = service == null ? null + : toList(service.getContentCaptureConditionsLocked(packageName)); + } + try { + result.send(RESULT_CODE_OK, bundleFor(conditions)); + } catch (RemoteException e) { + Slog.w(mTag, "Unable to send getServiceComponentName(): " + e); + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), mTag, pw)) return; diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index 665d3dfc7bb7..564952697250 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -35,11 +35,13 @@ import android.app.ActivityManagerInternal; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; import android.content.ComponentName; +import android.content.ContentCaptureOptions; import android.content.pm.ActivityPresentationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ServiceInfo; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.UserHandle; @@ -50,8 +52,11 @@ import android.service.contentcapture.ContentCaptureService; import android.service.contentcapture.ContentCaptureServiceInfo; import android.service.contentcapture.IContentCaptureServiceCallback; import android.service.contentcapture.SnapshotData; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; +import android.view.contentcapture.ContentCaptureCondition; import android.view.contentcapture.UserDataRemovalRequest; import com.android.internal.annotations.GuardedBy; @@ -98,6 +103,13 @@ final class ContentCapturePerUserService private final WhitelistHelper mWhitelistHelper = new WhitelistHelper(); /** + * List of conditions keyed by package. + */ + @GuardedBy("mLock") + private final ArrayMap<String, ArraySet<ContentCaptureCondition>> mConditionsByPkg = + new ArrayMap<>(); + + /** * When {@code true}, remote service died but service state is kept so it's restored after * the system re-binds to it. */ @@ -449,6 +461,47 @@ final class ContentCapturePerUserService } @GuardedBy("mLock") + @Nullable + ContentCaptureOptions getOptionsForPackageLocked(@NonNull String packageName) { + if (!mWhitelistHelper.isWhitelisted(packageName)) { + if (packageName.equals(getServicePackageName())) { + if (mMaster.verbose) Slog.v(mTag, "getOptionsForPackage() lite for " + packageName); + return new ContentCaptureOptions(mMaster.mDevCfgLoggingLevel); + } + if (mMaster.verbose) { + Slog.v(mTag, "getOptionsForPackage(" + packageName + "): not whitelisted"); + } + return null; + } + + final ArraySet<ComponentName> whitelistedComponents = mWhitelistHelper + .getWhitelistedComponents(packageName); + if (Build.IS_USER && isTemporaryServiceSetLocked()) { + final String servicePackageName = getServicePackageName(); + if (!packageName.equals(servicePackageName)) { + Slog.w(mTag, "Ignoring package " + packageName + + " while using temporary service " + servicePackageName); + return null; + } + } + final ContentCaptureOptions options = new ContentCaptureOptions(mMaster.mDevCfgLoggingLevel, + mMaster.mDevCfgMaxBufferSize, mMaster.mDevCfgIdleFlushingFrequencyMs, + mMaster.mDevCfgTextChangeFlushingFrequencyMs, mMaster.mDevCfgLogHistorySize, + whitelistedComponents); + if (mMaster.verbose) { + Slog.v(mTag, "getOptionsForPackage(" + packageName + "): " + options); + } + return options; + } + + @GuardedBy("mLock") + @Nullable + ArraySet<ContentCaptureCondition> getContentCaptureConditionsLocked( + @NonNull String packageName) { + return mConditionsByPkg.get(packageName); + } + + @GuardedBy("mLock") void onActivityEventLocked(@NonNull ComponentName componentName, @ActivityEventType int type) { if (mRemoteService == null) { if (mMaster.debug) Slog.d(mTag, "onActivityEvent(): no remote service"); @@ -536,6 +589,23 @@ final class ContentCapturePerUserService } @Override + public void setContentCaptureConditions(String packageName, + List<ContentCaptureCondition> conditions) { + if (mMaster.verbose) { + Slog.v(TAG, "setContentCaptureConditions(" + packageName + "): " + + (conditions == null ? "null" : conditions.size() + " conditions")); + } + synchronized (mLock) { + if (conditions == null) { + mConditionsByPkg.remove(packageName); + } else { + mConditionsByPkg.put(packageName, new ArraySet<>(conditions)); + } + } + // TODO(b/119613670): log metrics + } + + @Override public void disableSelf() { if (mMaster.verbose) Slog.v(TAG, "disableSelf()"); |