OSDN Git Service

Implemented ContentCaptureConditions APIs.
authorFelipe Leme <felipeal@google.com>
Mon, 25 Mar 2019 23:36:09 +0000 (16:36 -0700)
committerFelipe Leme <felipeal@google.com>
Fri, 29 Mar 2019 01:48:31 +0000 (18:48 -0700)
Test: atest ContentCaptureConditionTest \
       CustomViewActivityTest#testContentCaptureConditions \
       CustomViewActivityTest#testContentCaptureConditions_otherPackage
Test: atest CtsContentCaptureServiceTestCases # sanity check (minus usual flakiness)

Fixes: 129266058

Change-Id: I199c3ae99fa6b407da64562a71d8d7581ebf80e6

core/java/android/content/ContentCaptureOptions.java
core/java/android/service/contentcapture/ContentCaptureService.java
core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl
core/java/android/view/contentcapture/ContentCaptureCondition.aidl [new file with mode: 0644]
core/java/android/view/contentcapture/ContentCaptureCondition.java
core/java/android/view/contentcapture/ContentCaptureHelper.java
core/java/android/view/contentcapture/ContentCaptureManager.java
core/java/android/view/contentcapture/IContentCaptureManager.aidl
core/java/com/android/internal/util/SyncResultReceiver.java
services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java

index 1727d34..76c4fb8 100644 (file)
@@ -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 */
index f83090c..dc57a15 100644 (file)
@@ -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();
+        }
     }
 
     /**
index 8bc8def..0550ad3 100644 (file)
@@ -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 (file)
index 0000000..99f8894
--- /dev/null
@@ -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;
index ed87257..cf171d7 100644 (file)
@@ -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;
     }
index 6bc3829..c7ca220 100644 (file)
@@ -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");
     }
index 4f3325c..35f8023 100644 (file)
@@ -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();
+        }
     }
 
     /**
index 2775029..7335073 100644 (file)
@@ -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);
 }
index 60af511..6fad84b 100644 (file)
@@ -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).
index c88d3ae..2894662 100644 (file)
@@ -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;
 
index 665d3df..5649526 100644 (file)
@@ -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()");