OSDN Git Service

Optimized Content Capture workflow by caching some state at the application level.
authorFelipe Leme <felipeal@google.com>
Tue, 19 Feb 2019 17:42:24 +0000 (09:42 -0800)
committerFelipe Leme <felipeal@google.com>
Thu, 21 Feb 2019 02:44:05 +0000 (18:44 -0800)
Content Capture for an activity and/or package is only available when the Content Capture service
explicitly whitelists it. As the whitelist is kept at system-server level, it's better to fetch that
info when the application is started and cache it locally, so we can optimize the
ContentCaptureManager APIs to return quickly when it's disabled.

This CL also caches other values such as the buffer parameters.

Test: atest CtsContentCaptureServiceTestCases

Bug: 120494182
Bug: 121202151

Change-Id: I9d5211bca496ffa85ba9efc2a7bb32411834b787

25 files changed:
api/test-current.txt
core/java/android/app/Activity.java
core/java/android/app/ActivityThread.java
core/java/android/app/ContextImpl.java
core/java/android/app/IApplicationThread.aidl
core/java/android/app/SystemServiceRegistry.java
core/java/android/content/ContentCaptureOptions.aidl [new file with mode: 0644]
core/java/android/content/ContentCaptureOptions.java [new file with mode: 0644]
core/java/android/content/Context.java
core/java/android/content/ContextWrapper.java
core/java/android/service/contentcapture/ContentCaptureService.java
core/java/android/service/contentcapture/IContentCaptureService.aidl
core/java/android/view/View.java
core/java/android/view/ViewRootImpl.java
core/java/android/view/contentcapture/ContentCaptureHelper.java
core/java/android/view/contentcapture/ContentCaptureManager.java
core/java/android/view/contentcapture/MainContentCaptureSession.java
core/java/com/android/internal/policy/DecorContext.java
core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
services/core/java/com/android/server/am/ActivityManagerService.java
services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java

index ab03aa4..01f4a2f 100644 (file)
@@ -483,6 +483,20 @@ package android.bluetooth {
 
 package android.content {
 
+  public final class ContentCaptureOptions implements android.os.Parcelable {
+    ctor public ContentCaptureOptions(int, int, int, int, int, @Nullable android.util.ArraySet<android.content.ComponentName>);
+    method public int describeContents();
+    method public static android.content.ContentCaptureOptions forWhitelistingItself();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.content.ContentCaptureOptions> CREATOR;
+    field public final int idleFlushingFrequencyMs;
+    field public final int logHistorySize;
+    field public final int loggingLevel;
+    field public final int maxBufferSize;
+    field public final int textChangeFlushingFrequencyMs;
+    field @Nullable public final android.util.ArraySet<android.content.ComponentName> whitelistedComponents;
+  }
+
   public class ContentProviderClient implements java.lang.AutoCloseable {
     method @RequiresPermission(android.Manifest.permission.REMOVE_TASKS) public void setDetectNotResponding(long);
   }
@@ -496,6 +510,7 @@ package android.content {
     method public android.os.UserHandle getUser();
     method public int getUserId();
     method public void setAutofillCompatibilityEnabled(boolean);
+    method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions);
   }
 
 }
index d29fedd..89e848b 100644 (file)
@@ -1110,7 +1110,7 @@ public class Activity extends ContextThemeWrapper
         super.attachBaseContext(newBase);
         if (newBase != null) {
             newBase.setAutofillClient(this);
-            newBase.setContentCaptureSupported(true);
+            newBase.setContentCaptureOptions(getContentCaptureOptions());
         }
     }
 
@@ -1120,12 +1120,6 @@ public class Activity extends ContextThemeWrapper
         return this;
     }
 
-    /** @hide */
-    @Override
-    public boolean isContentCaptureSupported() {
-        return true;
-    }
-
     /**
      * Register an {@link Application.ActivityLifecycleCallbacks} instance that receives
      * lifecycle callbacks for only this Activity.
@@ -7615,6 +7609,7 @@ public class Activity extends ContextThemeWrapper
         mWindow.setColorMode(info.colorMode);
 
         setAutofillCompatibilityEnabled(application.isAutofillCompatibilityEnabled());
+        setContentCaptureOptions(application.getContentCaptureOptions());
     }
 
     private void enableAutofillCompatibilityIfNeeded() {
index 7908637..001cd69 100644 (file)
@@ -46,6 +46,7 @@ import android.app.servertransaction.TransactionExecutorHelper;
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -746,6 +747,14 @@ public final class ActivityThread extends ClientTransactionHandler {
 
         boolean autofillCompatibilityEnabled;
 
+        /**
+         * Content capture options for the application - when null, it means ContentCapture is not
+         * enabled for the package.
+         */
+        @Nullable
+        ContentCaptureOptions contentCaptureOptions;
+
+        @Override
         public String toString() {
             return "AppBindData{appInfo=" + appInfo + "}";
         }
@@ -966,7 +975,8 @@ public final class ActivityThread extends ClientTransactionHandler {
                 boolean enableBinderTracking, boolean trackAllocation,
                 boolean isRestrictedBackupMode, boolean persistent, Configuration config,
                 CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
-                String buildSerial, boolean autofillCompatibilityEnabled) {
+                String buildSerial, boolean autofillCompatibilityEnabled,
+                ContentCaptureOptions contentCaptureOptions) {
 
             if (services != null) {
                 if (false) {
@@ -1014,6 +1024,7 @@ public final class ActivityThread extends ClientTransactionHandler {
             data.initProfilerInfo = profilerInfo;
             data.buildSerial = buildSerial;
             data.autofillCompatibilityEnabled = autofillCompatibilityEnabled;
+            data.contentCaptureOptions = contentCaptureOptions;
             sendMessage(H.BIND_APPLICATION, data);
         }
 
@@ -6155,6 +6166,9 @@ public final class ActivityThread extends ClientTransactionHandler {
             // Propagate autofill compat state
             app.setAutofillCompatibilityEnabled(data.autofillCompatibilityEnabled);
 
+            // Propagate Content Capture options
+            app.setContentCaptureOptions(data.contentCaptureOptions);
+
             mInitialApplication = app;
 
             // don't bring up providers in restricted mode; they may depend on the
index 6908ca2..3a1e80d 100644 (file)
@@ -23,6 +23,7 @@ import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -217,7 +218,7 @@ class ContextImpl extends Context {
     private AutofillClient mAutofillClient = null;
     private boolean mIsAutofillCompatEnabled;
 
-    private boolean mIsContentCaptureSupported = false;
+    private ContentCaptureOptions mContentCaptureOptions = null;
 
     private final Object mSync = new Object();
 
@@ -2388,14 +2389,14 @@ class ContextImpl extends Context {
 
     /** @hide */
     @Override
-    public boolean isContentCaptureSupported() {
-        return mIsContentCaptureSupported;
+    public ContentCaptureOptions getContentCaptureOptions() {
+        return mContentCaptureOptions;
     }
 
     /** @hide */
     @Override
-    public void setContentCaptureSupported(boolean supported) {
-        mIsContentCaptureSupported = supported;
+    public void setContentCaptureOptions(ContentCaptureOptions options) {
+        mContentCaptureOptions = options;
     }
 
     @UnsupportedAppUsage
index e7a8c0e..b73092a 100644 (file)
@@ -22,6 +22,7 @@ import android.app.ProfilerInfo;
 import android.app.ResultInfo;
 import android.app.servertransaction.ClientTransaction;
 import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
 import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -68,7 +69,8 @@ oneway interface IApplicationThread {
             int debugMode, boolean enableBinderTracking, boolean trackAllocation,
             boolean restrictedBackupMode, boolean persistent, in Configuration config,
             in CompatibilityInfo compatInfo, in Map services,
-            in Bundle coreSettings, in String buildSerial, boolean isAutofillCompatEnabled);
+            in Bundle coreSettings, in String buildSerial, boolean isAutofillCompatEnabled,
+            in ContentCaptureOptions contentCaptureOptions);
     void runIsolatedEntryPoint(in String entryPoint, in String[] entryPointArgs);
     void scheduleExit();
     void scheduleServiceArgs(IBinder token, in ParceledListSlice args);
index c12a92f..1faa2ac 100644 (file)
@@ -41,6 +41,7 @@ import android.bluetooth.BluetoothManager;
 import android.companion.CompanionDeviceManager;
 import android.companion.ICompanionDeviceManager;
 import android.content.ClipboardManager;
+import android.content.ContentCaptureOptions;
 import android.content.Context;
 import android.content.IRestrictionsManager;
 import android.content.RestrictionsManager;
@@ -1125,16 +1126,19 @@ final class SystemServiceRegistry {
                     throws ServiceNotFoundException {
                 // Get the services without throwing as this is an optional feature
                 Context outerContext = ctx.getOuterContext();
-                if (outerContext.isContentCaptureSupported()) {
+                ContentCaptureOptions options = outerContext.getContentCaptureOptions();
+                // Options is null when the service didn't whitelist the activity or package
+                if (options != null) {
                     IBinder b = ServiceManager
                             .getService(Context.CONTENT_CAPTURE_MANAGER_SERVICE);
                     IContentCaptureManager service = IContentCaptureManager.Stub.asInterface(b);
+                    // Service is null when not provided by OEM or disabled by kill-switch.
                     if (service != null) {
-                        // When feature is disabled, we return a null manager to apps so the
-                        // performance impact is practically zero
-                        return new ContentCaptureManager(outerContext, service);
+                        return new ContentCaptureManager(outerContext, service, options);
                     }
                 }
+                // When feature is disabled or app / package not whitelisted, we return a null
+                // manager to apps so the performance impact is practically zero
                 return null;
             }});
 
diff --git a/core/java/android/content/ContentCaptureOptions.aidl b/core/java/android/content/ContentCaptureOptions.aidl
new file mode 100644 (file)
index 0000000..82ffac4
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+** Copyright 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.content;
+
+parcelable ContentCaptureOptions;
diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java
new file mode 100644 (file)
index 0000000..2fe9f14
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * 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.content;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.app.ActivityThread;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.contentcapture.ContentCaptureManager;
+
+import java.io.PrintWriter;
+
+/**
+ * Content capture options for a given package.
+ *
+ * <p>This object is created by the Content Capture System Service and passed back to the app when
+ * the application is created.
+ *
+ * @hide
+ */
+@TestApi
+public final class ContentCaptureOptions implements Parcelable {
+
+    private static final String TAG = ContentCaptureOptions.class.getSimpleName();
+
+    /**
+     * Logging level for {@code logcat} statements.
+     */
+    public final int loggingLevel;
+
+    /**
+     * Maximum number of events that are buffered before sent to the app.
+     */
+    public final int maxBufferSize;
+
+    /**
+     * Frequency the buffer is flushed if idle.
+     */
+    public final int idleFlushingFrequencyMs;
+
+    /**
+     * Frequency the buffer is flushed if last event is a text change.
+     */
+    public final int textChangeFlushingFrequencyMs;
+
+    /**
+     * Size of events that are logging on {@code dump}.
+     */
+    public final int logHistorySize;
+
+    /**
+     * List of activities explicitly whitelisted for content capture (or {@code null} if whitelisted
+     * for all acitivites in the package).
+     */
+    @Nullable
+    public final ArraySet<ComponentName> whitelistedComponents;
+
+    public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs,
+            int textChangeFlushingFrequencyMs, int logHistorySize,
+            @Nullable ArraySet<ComponentName> whitelistedComponents) {
+        this.loggingLevel = loggingLevel;
+        this.maxBufferSize = maxBufferSize;
+        this.idleFlushingFrequencyMs = idleFlushingFrequencyMs;
+        this.textChangeFlushingFrequencyMs = textChangeFlushingFrequencyMs;
+        this.logHistorySize = logHistorySize;
+        this.whitelistedComponents = whitelistedComponents;
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    public static ContentCaptureOptions forWhitelistingItself() {
+        final ActivityThread at = ActivityThread.currentActivityThread();
+        if (at == null) {
+            throw new IllegalStateException("No ActivityThread");
+        }
+
+        final String packageName = at.getApplication().getPackageName();
+
+        if (!"android.contentcaptureservice.cts".equals(packageName)) {
+            Log.e(TAG, "forWhitelistingItself(): called by " + packageName);
+            throw new SecurityException("Thou shall not pass!");
+        }
+
+        final ContentCaptureOptions options = new ContentCaptureOptions(
+                ContentCaptureManager.LOGGING_LEVEL_VERBOSE,
+                ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE,
+                ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS,
+                ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS,
+                ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE,
+                /* whitelistedComponents= */ null);
+        // Always log, as it's used by test only
+        Log.i(TAG, "forWhitelistingItself(" + packageName + "): " + options);
+
+        return options;
+    }
+
+    @Override
+    public String toString() {
+        return "ContentCaptureOptions [loggingLevel=" + loggingLevel + ", maxBufferSize="
+                + maxBufferSize + ", idleFlushingFrequencyMs=" + idleFlushingFrequencyMs
+                + ", textChangeFlushingFrequencyMs=" + textChangeFlushingFrequencyMs
+                + ", logHistorySize=" + logHistorySize + ", whitelistedComponents="
+                + whitelistedComponents + "]";
+    }
+
+    /** @hide */
+    public void dumpShort(@NonNull PrintWriter pw) {
+        pw.print("logLvl="); pw.print(loggingLevel);
+        pw.print(", bufferSize="); pw.print(maxBufferSize);
+        pw.print(", idle="); pw.print(idleFlushingFrequencyMs);
+        pw.print(", textIdle="); pw.print(textChangeFlushingFrequencyMs);
+        pw.print(", logSize="); pw.print(logHistorySize);
+        if (whitelistedComponents != null) {
+            pw.print(", whitelisted="); pw.print(whitelistedComponents);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(loggingLevel);
+        parcel.writeInt(maxBufferSize);
+        parcel.writeInt(idleFlushingFrequencyMs);
+        parcel.writeInt(textChangeFlushingFrequencyMs);
+        parcel.writeInt(logHistorySize);
+        parcel.writeArraySet(whitelistedComponents);
+    }
+
+    public static final Parcelable.Creator<ContentCaptureOptions> CREATOR =
+            new Parcelable.Creator<ContentCaptureOptions>() {
+
+                @Override
+                public ContentCaptureOptions createFromParcel(Parcel parcel) {
+                    final int loggingLevel = parcel.readInt();
+                    final int maxBufferSize = parcel.readInt();
+                    final int idleFlushingFrequencyMs = parcel.readInt();
+                    final int textChangeFlushingFrequencyMs = parcel.readInt();
+                    final int logHistorySize = parcel.readInt();
+                    @SuppressWarnings("unchecked")
+                    final ArraySet<ComponentName> whitelistedComponents =
+                            (ArraySet<ComponentName>) parcel.readArraySet(null);
+                    return new ContentCaptureOptions(loggingLevel, maxBufferSize,
+                            idleFlushingFrequencyMs, textChangeFlushingFrequencyMs, logHistorySize,
+                            whitelistedComponents);
+                }
+
+                @Override
+                public ContentCaptureOptions[] newArray(int size) {
+                    return new ContentCaptureOptions[size];
+                }
+
+    };
+}
index 25bfba2..fdb0041 100644 (file)
@@ -5353,22 +5353,21 @@ public abstract class Context {
     }
 
     /**
-     * Checks whether this context supports content capture.
+     * Gets the Content Capture options for this context, or {@code null} if it's not whitelisted.
      *
      * @hide
      */
-    // NOTE: for now we just need to check if it's supported so we can optimize calls that can be
-    // skipped when it isn't. Eventually, we might need a full
-    // ContentCaptureManager.ContentCaptureClient interface (as it's done with AutofillClient).
-    //
-    public boolean isContentCaptureSupported() {
-        return false;
+    @Nullable
+    public ContentCaptureOptions getContentCaptureOptions() {
+        return null;
     }
 
     /**
      * @hide
      */
-    public void setContentCaptureSupported(@SuppressWarnings("unused") boolean supported) {
+    @TestApi
+    public void setContentCaptureOptions(
+            @SuppressWarnings("unused") @Nullable ContentCaptureOptions options) {
     }
 
     /**
index 26ed3b7..68b4320 100644 (file)
@@ -1044,7 +1044,7 @@ public class ContextWrapper extends Context {
      */
     @TestApi
     @Override
-    public void setAutofillCompatibilityEnabled(boolean  autofillCompatEnabled) {
+    public void setAutofillCompatibilityEnabled(boolean autofillCompatEnabled) {
         if (mBase != null) {
             mBase.setAutofillCompatibilityEnabled(autofillCompatEnabled);
         }
@@ -1054,15 +1054,18 @@ public class ContextWrapper extends Context {
      * @hide
      */
     @Override
-    public boolean isContentCaptureSupported() {
-        return mBase.isContentCaptureSupported();
+    public ContentCaptureOptions getContentCaptureOptions() {
+        return mBase == null ? null : mBase.getContentCaptureOptions();
     }
 
     /**
      * @hide
      */
+    @TestApi
     @Override
-    public void setContentCaptureSupported(boolean supported) {
-        mBase.setContentCaptureSupported(supported);
+    public void setContentCaptureOptions(ContentCaptureOptions options) {
+        if (mBase != null) {
+            mBase.setContentCaptureOptions(options);
+        }
     }
 }
index d361a2c..d032e56 100644 (file)
@@ -15,6 +15,9 @@
  */
 package android.service.contentcapture;
 
+import static android.view.contentcapture.ContentCaptureHelper.sDebug;
+import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
+
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.annotation.CallSuper;
@@ -66,10 +69,6 @@ public abstract class ContentCaptureService extends Service {
 
     private static final String TAG = ContentCaptureService.class.getSimpleName();
 
-    // TODO(b/121044306): STOPSHIP use dynamic value, or change to false
-    static final boolean DEBUG = true;
-    static final boolean VERBOSE = false;
-
     /**
      * The {@link Intent} that must be declared as handled by the service.
      *
@@ -89,7 +88,9 @@ public abstract class ContentCaptureService extends Service {
     private final IContentCaptureService mServerInterface = new IContentCaptureService.Stub() {
 
         @Override
-        public void onConnected(IBinder callback) {
+        public void onConnected(IBinder callback, boolean verbose, boolean debug) {
+            sVerbose = verbose;
+            sDebug = debug;
             mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnConnected,
                     ContentCaptureService.this, callback));
         }
@@ -227,7 +228,7 @@ public abstract class ContentCaptureService extends Service {
      */
     public void onCreateContentCaptureSession(@NonNull ContentCaptureContext context,
             @NonNull ContentCaptureSessionId sessionId) {
-        if (VERBOSE) {
+        if (sVerbose) {
             Log.v(TAG, "onCreateContentCaptureSession(id=" + sessionId + ", ctx=" + context + ")");
         }
     }
@@ -240,7 +241,7 @@ public abstract class ContentCaptureService extends Service {
     @Deprecated
     public void onContentCaptureEventsRequest(@NonNull ContentCaptureSessionId sessionId,
             @NonNull ContentCaptureEventsRequest request) {
-        if (VERBOSE) Log.v(TAG, "onContentCaptureEventsRequest(id=" + sessionId + ")");
+        if (sVerbose) Log.v(TAG, "onContentCaptureEventsRequest(id=" + sessionId + ")");
     }
 
     /**
@@ -252,7 +253,7 @@ public abstract class ContentCaptureService extends Service {
      */
     public void onContentCaptureEvent(@NonNull ContentCaptureSessionId sessionId,
             @NonNull ContentCaptureEvent event) {
-        if (VERBOSE) Log.v(TAG, "onContentCaptureEventsRequest(id=" + sessionId + ")");
+        if (sVerbose) Log.v(TAG, "onContentCaptureEventsRequest(id=" + sessionId + ")");
         onContentCaptureEventsRequest(sessionId, new ContentCaptureEventsRequest(event));
     }
 
@@ -262,7 +263,7 @@ public abstract class ContentCaptureService extends Service {
      * @param request the user data requested to be removed
      */
     public void onUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) {
-        if (VERBOSE) Log.v(TAG, "onUserDataRemovalRequest()");
+        if (sVerbose) Log.v(TAG, "onUserDataRemovalRequest()");
     }
 
     /**
@@ -280,14 +281,14 @@ public abstract class ContentCaptureService extends Service {
      * @param sessionId the id of the session to destroy
      * */
     public void onDestroyContentCaptureSession(@NonNull ContentCaptureSessionId sessionId) {
-        if (VERBOSE) Log.v(TAG, "onDestroyContentCaptureSession(id=" + sessionId + ")");
+        if (sVerbose) Log.v(TAG, "onDestroyContentCaptureSession(id=" + sessionId + ")");
     }
 
     /**
      * Disables the Content Capture service for the given user.
      */
     public final void disableContentCaptureServices() {
-        if (DEBUG) Log.d(TAG, "disableContentCaptureServices()");
+        if (sDebug) Log.d(TAG, "disableContentCaptureServices()");
 
         final IContentCaptureServiceCallback callback = mCallback;
         if (callback == null) {
@@ -313,6 +314,7 @@ public abstract class ContentCaptureService extends Service {
     @Override
     @CallSuper
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.print("Debug: "); pw.print(sDebug); pw.print(" Verbose: "); pw.println(sVerbose);
         final int size = mSessionUids.size();
         pw.print("Number sessions: "); pw.println(size);
         if (size > 0) {
@@ -422,7 +424,7 @@ public abstract class ContentCaptureService extends Service {
         }
         final Integer rightUid = mSessionUids.get(sessionId);
         if (rightUid == null) {
-            if (VERBOSE) {
+            if (sVerbose) {
                 Log.v(TAG, "handleIsRightCallerFor(" + event + "): no session for " + sessionId
                         + ": " + mSessionUids);
             }
index d92fb3b..eb65032 100644 (file)
@@ -31,7 +31,7 @@ import java.util.List;
  * @hide
  */
 oneway interface IContentCaptureService {
-    void onConnected(IBinder callback);
+    void onConnected(IBinder callback, boolean verbose, boolean debug);
     void onDisconnected();
     void onSessionStarted(in ContentCaptureContext context, String sessionId, int uid,
                           in IResultReceiver clientReceiver);
index a78f2b0..278b9ff 100644 (file)
@@ -9378,7 +9378,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
         AttachInfo ai = mAttachInfo;
 
         // First check if context has client, so it saves a service lookup when it doesn't
-        if (!mContext.isContentCaptureSupported()) return;
+        if (mContext.getContentCaptureOptions() == null) return;
 
         // Then check if it's enabled in the context...
         final ContentCaptureManager ccm = ai != null ? ai.getContentCaptureManager(mContext)
index b1fee2d..ab4847d 100644 (file)
@@ -3497,7 +3497,7 @@ public final class ViewRootImpl implements ViewParent,
         }
         try {
             // First check if context supports it, so it saves a service lookup when it doesn't
-            if (!mContext.isContentCaptureSupported()) return;
+            if (mContext.getContentCaptureOptions() == null) return;
 
             // Then check if it's enabled in the contex itself.
             final ContentCaptureManager ccm = mContext
index 1cf27fc..6e84ff0 100644 (file)
@@ -63,12 +63,27 @@ public final class ContentCaptureHelper {
     }
 
     /**
+     * Gets the default logging level for the device.
+     */
+    @LoggingLevel
+    public static int getDefaultLoggingLevel() {
+        return Build.IS_DEBUGGABLE ? LOGGING_LEVEL_DEBUG : LOGGING_LEVEL_OFF;
+    }
+
+    /**
      * Sets the value of the static logging level constants based on device config.
      */
     public static void setLoggingLevel() {
-        final int defaultLevel = Build.IS_DEBUGGABLE ? LOGGING_LEVEL_DEBUG : LOGGING_LEVEL_OFF;
+        final int defaultLevel = getDefaultLoggingLevel();
         final int level = getIntDeviceConfigProperty(DEVICE_CONFIG_PROPERTY_LOGGING_LEVEL,
                 defaultLevel);
+        setLoggingLevel(level);
+    }
+
+    /**
+     * Sets the value of the static logging level constants based the given level.
+     */
+    public static void setLoggingLevel(@LoggingLevel int level) {
         Log.i(TAG, "Setting logging level to " + getLoggingLevelAsString(level));
         sVerbose = sDebug = false;
         switch (level) {
index 87e358c..336d997 100644 (file)
@@ -26,6 +26,7 @@ import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.annotation.UiThread;
 import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
 import android.content.Context;
 import android.os.Handler;
 import android.os.IBinder;
@@ -150,6 +151,16 @@ public final class ContentCaptureManager {
     @Retention(RetentionPolicy.SOURCE)
     public @interface LoggingLevel {}
 
+
+    /** @hide */
+    public static final int DEFAULT_MAX_BUFFER_SIZE = 100;
+    /** @hide */
+    public static final int DEFAULT_IDLE_FLUSHING_FREQUENCY_MS = 5_000;
+    /** @hide */
+    public static final int DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS = 1_000;
+    /** @hide */
+    public static final int DEFAULT_LOG_HISTORY_SIZE = 10;
+
     private final Object mLock = new Object();
 
     @NonNull
@@ -158,6 +169,9 @@ public final class ContentCaptureManager {
     @NonNull
     private final IContentCaptureManager mService;
 
+    @NonNull
+    final ContentCaptureOptions mOptions;
+
     // Flags used for starting session.
     @GuardedBy("mLock")
     private int mFlags;
@@ -172,14 +186,12 @@ public final class ContentCaptureManager {
 
     /** @hide */
     public ContentCaptureManager(@NonNull Context context,
-            @NonNull IContentCaptureManager service) {
+            @NonNull IContentCaptureManager service, @NonNull ContentCaptureOptions options) {
         mContext = Preconditions.checkNotNull(context, "context cannot be null");
         mService = Preconditions.checkNotNull(service, "service cannot be null");
+        mOptions = Preconditions.checkNotNull(options, "options cannot be null");
 
-        // TODO(b/123096662): right now we're reading the device config values here, but ideally
-        // it should be read on ContentCaptureManagerService and passed back when the activity
-        // started.
-        ContentCaptureHelper.setLoggingLevel();
+        ContentCaptureHelper.setLoggingLevel(mOptions.loggingLevel);
 
         if (sVerbose) Log.v(TAG, "Constructor for " + context.getPackageName());
 
@@ -355,12 +367,13 @@ public final class ContentCaptureManager {
         synchronized (mLock) {
             pw.print(prefix2); pw.print("isContentCaptureEnabled(): ");
             pw.println(isContentCaptureEnabled());
-            pw.print(prefix); pw.print("Debug: "); pw.print(sDebug);
+            pw.print(prefix2); pw.print("Debug: "); pw.print(sDebug);
             pw.print(" Verbose: "); pw.println(sVerbose);
-            pw.print(prefix); pw.print("Context: "); pw.println(mContext);
-            pw.print(prefix); pw.print("User: "); pw.println(mContext.getUserId());
-            pw.print(prefix); pw.print("Service: "); pw.println(mService);
-            pw.print(prefix); pw.print("Flags: "); pw.println(mFlags);
+            pw.print(prefix2); pw.print("Context: "); pw.println(mContext);
+            pw.print(prefix2); pw.print("User: "); pw.println(mContext.getUserId());
+            pw.print(prefix2); pw.print("Service: "); pw.println(mService);
+            pw.print(prefix2); pw.print("Flags: "); pw.println(mFlags);
+            pw.print(prefix2); pw.print("Options: "); mOptions.dumpShort(pw); pw.println();
             if (mMainSession != null) {
                 final String prefix3 = prefix2 + "  ";
                 pw.print(prefix2); pw.println("Main session:");
index f4021b1..0abf689 100644 (file)
@@ -23,13 +23,9 @@ import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_START
 import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
 import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
 import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
-import static android.view.contentcapture.ContentCaptureHelper.getIntDeviceConfigProperty;
 import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
 import static android.view.contentcapture.ContentCaptureHelper.sDebug;
 import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
-import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY;
-import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE;
-import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -76,10 +72,6 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
      */
     private static final int MSG_FLUSH = 1;
 
-    private static final int DEFAULT_MAX_BUFFER_SIZE = 100;
-    private static final int DEFAULT_FLUSHING_FREQUENCY_MS = 5_000;
-    private static final int DEFAULT_LOG_HISTORY_SIZE = 10;
-
     /**
      * Name of the {@link IResultReceiver} extra used to pass the binder interface to the service.
      * @hide
@@ -128,16 +120,6 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
     @Nullable
     private ArrayList<ContentCaptureEvent> mEvents;
 
-    /**
-     * Maximum number of events that are buffered before sent to the app.
-     */
-    private final int mMaxBufferSize;
-
-    /**
-     * Frequency the buffer is flushed if idle.
-     */
-    private final int mIdleFlushingFrequencyMs;
-
     // Used just for debugging purposes (on dump)
     private long mNextFlush;
 
@@ -153,16 +135,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
         mHandler = handler;
         mSystemServerInterface = systemServerInterface;
 
-        // TODO(b/123096662): right now we're reading the device config values here, but ideally
-        // it should be read on ContentCaptureManagerService and passed back when the activity
-        // started.
-        mMaxBufferSize = getIntDeviceConfigProperty(DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE,
-                DEFAULT_MAX_BUFFER_SIZE);
-        mIdleFlushingFrequencyMs = getIntDeviceConfigProperty(
-                DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY, DEFAULT_FLUSHING_FREQUENCY_MS);
-        final int logHistorySize = getIntDeviceConfigProperty(
-                DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE, DEFAULT_LOG_HISTORY_SIZE);
-
+        final int logHistorySize = mManager.mOptions.logHistorySize;
         mFlushHistory = logHistorySize > 0 ? new LocalLog(logHistorySize) : null;
     }
 
@@ -302,11 +275,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
             if (sVerbose) Log.v(TAG, "handleSendEvent(): ignoring when disabled");
             return;
         }
+        final int maxBufferSize = mManager.mOptions.maxBufferSize;
         if (mEvents == null) {
             if (sVerbose) {
-                Log.v(TAG, "handleSendEvent(): creating buffer for " + mMaxBufferSize + " events");
+                Log.v(TAG, "handleSendEvent(): creating buffer for " + maxBufferSize + " events");
             }
-            mEvents = new ArrayList<>(mMaxBufferSize);
+            mEvents = new ArrayList<>(maxBufferSize);
         }
 
         // Some type of events can be merged together
@@ -347,14 +321,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
 
         final int numberEvents = mEvents.size();
 
-        final boolean bufferEvent = numberEvents < mMaxBufferSize;
+        final boolean bufferEvent = numberEvents < maxBufferSize;
 
         if (bufferEvent && !forceFlush) {
             scheduleFlush(FLUSH_REASON_IDLE_TIMEOUT, /* checkExisting= */ true);
             return;
         }
 
-        if (mState != STATE_ACTIVE && numberEvents >= mMaxBufferSize) {
+        if (mState != STATE_ACTIVE && numberEvents >= maxBufferSize) {
             // Callback from startSession hasn't been called yet - typically happens on system
             // apps that are started before the system service
             // TODO(b/122959591): try to ignore session while system is not ready / boot
@@ -435,13 +409,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
             // "Renew" the flush message by removing the previous one
             mHandler.removeMessages(MSG_FLUSH);
         }
-        mNextFlush = System.currentTimeMillis() + mIdleFlushingFrequencyMs;
+        final int idleFlushingFrequencyMs = mManager.mOptions.idleFlushingFrequencyMs;
+        mNextFlush = System.currentTimeMillis() + idleFlushingFrequencyMs;
         if (sVerbose) {
             Log.v(TAG, "handleScheduleFlush(): scheduled to flush in "
-                    + mIdleFlushingFrequencyMs + "ms: " + TimeUtils.logTimeOfDay(mNextFlush));
+                    + idleFlushingFrequencyMs + "ms: " + TimeUtils.logTimeOfDay(mNextFlush));
         }
         // Post using a Runnable directly to trim a few Î¼s from PooledLambda.obtainMessage()
-        mHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, mIdleFlushingFrequencyMs);
+        mHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, idleFlushingFrequencyMs);
     }
 
     @UiThread
@@ -483,7 +458,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
         if (mFlushHistory != null) {
             // Logs reason, size, max size, idle timeout
             final String logRecord = "r=" + reasonString + " s=" + numberEvents
-                    + " m=" + mMaxBufferSize + " i=" + mIdleFlushingFrequencyMs;
+                    + " m=" + mManager.mOptions.maxBufferSize
+                    + " i=" + mManager.mOptions.idleFlushingFrequencyMs;
             mFlushHistory.log(logRecord);
         }
         try {
@@ -649,7 +625,6 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
 
         pw.print(prefix); pw.print("mContext: "); pw.println(mContext);
         pw.print(prefix); pw.print("user: "); pw.println(mContext.getUserId());
-        pw.print(prefix); pw.print("mSystemServerInterface: ");
         if (mDirectServiceInterface != null) {
             pw.print(prefix); pw.print("mDirectServiceInterface: ");
             pw.println(mDirectServiceInterface);
@@ -667,7 +642,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
         if (mEvents != null && !mEvents.isEmpty()) {
             final int numberEvents = mEvents.size();
             pw.print(prefix); pw.print("buffered events: "); pw.print(numberEvents);
-            pw.print('/'); pw.println(mMaxBufferSize);
+            pw.print('/'); pw.println(mManager.mOptions.maxBufferSize);
             if (sVerbose && numberEvents > 0) {
                 final String prefix3 = prefix + "  ";
                 for (int i = 0; i < numberEvents; i++) {
@@ -676,7 +651,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
                     pw.println();
                 }
             }
-            pw.print(prefix); pw.print("flush frequency: "); pw.println(mIdleFlushingFrequencyMs);
+            pw.print(prefix); pw.print("flush frequency: ");
+            pw.println(mManager.mOptions.idleFlushingFrequencyMs);
             pw.print(prefix); pw.print("next flush: ");
             TimeUtils.formatDuration(mNextFlush - System.currentTimeMillis(), pw);
             pw.print(" ("); pw.print(TimeUtils.logTimeOfDay(mNextFlush)); pw.println(")");
index 429c618..67cdd5d 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.android.internal.policy;
 
+import android.content.ContentCaptureOptions;
 import android.content.Context;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
@@ -93,7 +94,11 @@ class DecorContext extends ContextThemeWrapper {
     }
 
     @Override
-    public boolean isContentCaptureSupported() {
-        return true;
+    public ContentCaptureOptions getContentCaptureOptions() {
+        Context activityContext = mActivityContext.get();
+        if (activityContext != null) {
+            return activityContext.getContentCaptureOptions();
+        }
+        return null;
     }
 }
index a5ea441..9fabe44 100644 (file)
@@ -29,6 +29,7 @@ import android.app.IInstrumentationWatcher;
 import android.app.IUiAutomationConnection;
 import android.app.ProfilerInfo;
 import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
 import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -406,7 +407,7 @@ public class TransactionParcelTests {
                 IUiAutomationConnection iUiAutomationConnection, int i, boolean b, boolean b1,
                 boolean b2, boolean b3, Configuration configuration,
                 CompatibilityInfo compatibilityInfo, Map map, Bundle bundle1, String s1,
-                boolean autofillCompatEnabled) throws RemoteException {
+                boolean autofillCompatEnabled, ContentCaptureOptions o) throws RemoteException {
         }
 
         @Override
index 312e0e0..cd885e0 100644 (file)
@@ -41,7 +41,8 @@ public class ContentCaptureManagerTest {
 
     @Before
     public void before() {
-        mManager = new ContentCaptureManager(mMockContext, null);
+        mManager = new ContentCaptureManager(mMockContext, /* service= */ null,
+                /* options= */ null);
     }
 
     @Test
index 45ceeda..d5713a1 100644 (file)
@@ -25,6 +25,7 @@ import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityThread;
 import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ActivityPresentationInfo;
@@ -101,6 +102,13 @@ public final class ContentCaptureManagerService extends
     @Nullable
     private boolean mDisabledByDeviceConfig;
 
+    // Device-config settings that are cached and passed back to apps
+    public int mDevCfgLoggingLevel;
+    public int mDevCfgMaxBufferSize;
+    public int mDevCfgIdleFlushingFrequencyMs;
+    public int mDevCfgTextChangeFlushingFrequencyMs;
+    public int mDevCfgLogHistorySize;
+
     public ContentCaptureManagerService(@NonNull Context context) {
         super(context, new FrameworkResourcesServiceNameResolver(context,
                 com.android.internal.R.string.config_defaultContentCaptureService),
@@ -108,16 +116,15 @@ public final class ContentCaptureManagerService extends
         DeviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
                 ActivityThread.currentApplication().getMainExecutor(),
                 (namespace, key, value) -> onDeviceConfigChange(key, value));
-        setLoggingLevelFromDeviceConfig();
-        setDisabledFromDeviceConfig();
+        setDeviceConfigProperties();
 
-        final int loggingSize = ContentCaptureHelper.getIntDeviceConfigProperty(
-                ContentCaptureManager.DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE, 20);
-        if (loggingSize > 0) {
-            if (debug) Slog.d(mTag, "log history size: " + loggingSize);
-            mRequestsHistory = new LocalLog(loggingSize);
+        if (mDevCfgLogHistorySize > 0) {
+            if (debug) Slog.d(mTag, "log history size: " + mDevCfgLogHistorySize);
+            mRequestsHistory = new LocalLog(mDevCfgLogHistorySize);
         } else {
-            if (debug) Slog.d(mTag, "disabled log history because size is " + loggingSize);
+            if (debug) {
+                Slog.d(mTag, "disabled log history because size is " + mDevCfgLogHistorySize);
+            }
             mRequestsHistory = null;
         }
 
@@ -231,34 +238,64 @@ public final class ContentCaptureManagerService extends
             case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY:
             case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE:
             case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY:
-                // TODO(b/123096662): implement it
-                Slog.d(mTag, "changes on " + key + " not supported yet");
+                setFineTuneParamsFromDeviceConfig();
                 return;
             default:
                 Slog.i(mTag, "Ignoring change on " + key);
         }
     }
 
+    private void setFineTuneParamsFromDeviceConfig() {
+        mDevCfgMaxBufferSize = ContentCaptureHelper.getIntDeviceConfigProperty(
+                ContentCaptureManager.DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE,
+                ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE);
+        mDevCfgIdleFlushingFrequencyMs = ContentCaptureHelper.getIntDeviceConfigProperty(
+                ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY,
+                ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS);
+        mDevCfgTextChangeFlushingFrequencyMs = ContentCaptureHelper.getIntDeviceConfigProperty(
+                ContentCaptureManager.DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY,
+                ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS);
+        mDevCfgLogHistorySize = ContentCaptureHelper.getIntDeviceConfigProperty(
+                ContentCaptureManager.DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE, 20);
+        if (verbose) {
+            Slog.v(mTag, "setFineTuneParamsFromDeviceConfig(): bufferSize=" + mDevCfgMaxBufferSize
+                    + ", idleFlush=" + mDevCfgIdleFlushingFrequencyMs
+                    + ", textFluxh=" + mDevCfgTextChangeFlushingFrequencyMs
+                    + ", logHistory=" + mDevCfgLogHistorySize);
+        }
+    }
+
     private void setLoggingLevelFromDeviceConfig() {
-        ContentCaptureHelper.setLoggingLevel();
+        mDevCfgLoggingLevel = ContentCaptureHelper.getIntDeviceConfigProperty(
+                ContentCaptureManager.DEVICE_CONFIG_PROPERTY_LOGGING_LEVEL,
+                ContentCaptureHelper.getDefaultLoggingLevel());
+        ContentCaptureHelper.setLoggingLevel(mDevCfgLoggingLevel);
         verbose = ContentCaptureHelper.sVerbose;
         debug = ContentCaptureHelper.sDebug;
+        if (verbose) {
+            Slog.v(mTag, "setLoggingLevelFromDeviceConfig(): level=" + mDevCfgLoggingLevel
+                    + ", debug=" + debug + ", verbose=" + verbose);
+        }
     }
 
-    private void setDisabledFromDeviceConfig() {
-        final String value = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+    private void setDeviceConfigProperties() {
+        setLoggingLevelFromDeviceConfig();
+        setFineTuneParamsFromDeviceConfig();
+        final String enabled = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
                 ContentCaptureManager.DEVICE_CONFIG_PROPERTY_SERVICE_EXPLICITLY_ENABLED);
-        setDisabledByDeviceConfig(value);
+        setDisabledByDeviceConfig(enabled);
     }
 
-    private void setDisabledByDeviceConfig(@Nullable String value) {
-        if (verbose) Slog.v(mTag, "setDisabledByDeviceConfig(): value=" + value);
+    private void setDisabledByDeviceConfig(@Nullable String explicitlyEnabled) {
+        if (verbose) {
+            Slog.v(mTag, "setDisabledByDeviceConfig(): explicitlyEnabled=" + explicitlyEnabled);
+        }
         final UserManager um = getContext().getSystemService(UserManager.class);
         final List<UserInfo> users = um.getUsers();
 
         final boolean newDisabledValue;
 
-        if (value != null && value.equalsIgnoreCase("false")) {
+        if (explicitlyEnabled != null && explicitlyEnabled.equalsIgnoreCase("false")) {
             newDisabledValue = true;
         } else {
             newDisabledValue = false;
@@ -423,9 +460,18 @@ public final class ContentCaptureManagerService extends
     protected void dumpLocked(String prefix, PrintWriter pw) {
         super.dumpLocked(prefix, pw);
 
+        final String prefix2 = prefix + "  ";
+
         pw.print(prefix); pw.print("Disabled users: "); pw.println(mDisabledUsers);
-        pw.print(prefix); pw.print("Disabled by DeviceConfig: ");
-        pw.println(mDisabledByDeviceConfig);
+        pw.print(prefix); pw.println("DeviceConfig Settings: ");
+        pw.print(prefix2); pw.print("disabled: "); pw.println(mDisabledByDeviceConfig);
+        pw.print(prefix2); pw.print("loggingLevel: "); pw.println(mDevCfgLoggingLevel);
+        pw.print(prefix2); pw.print("maxBufferSize: "); pw.println(mDevCfgMaxBufferSize);
+        pw.print(prefix2); pw.print("idleFlushingFrequencyMs: ");
+        pw.println(mDevCfgIdleFlushingFrequencyMs);
+        pw.print(prefix2); pw.print("textChangeFlushingFrequencyMs: ");
+        pw.println(mDevCfgTextChangeFlushingFrequencyMs);
+        pw.print(prefix2); pw.print("logHistorySize: "); pw.println(mDevCfgLogHistorySize);
     }
 
     final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {
@@ -534,6 +580,8 @@ public final class ContentCaptureManagerService extends
                 pw.println();
                 mRequestsHistory.reverseDump(fd, pw, args);
                 pw.println();
+            } else {
+                pw.println();
             }
         }
 
@@ -570,5 +618,16 @@ public final class ContentCaptureManagerService extends
             }
             return false;
         }
+
+        @Override
+        public ContentCaptureOptions getOptionsForPackage(int userId, String packageName) {
+            synchronized (mLock) {
+                final ContentCapturePerUserService service = peekServiceForUserLocked(userId);
+                if (service != null) {
+                    return service.getOptionsForPackageLocked(packageName);
+                }
+            }
+            return null;
+        }
     }
 }
index 9e15960..5b48046 100644 (file)
@@ -35,6 +35,7 @@ import android.app.AppGlobals;
 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;
@@ -408,6 +409,27 @@ final class ContentCapturePerUserService
         }
     }
 
+    @GuardedBy("mLock")
+    ContentCaptureOptions getOptionsForPackageLocked(@NonNull String packageName) {
+        if (!mWhitelistedPackages.contains(packageName)) {
+            if (mMaster.verbose) {
+                Slog.v(mTag, "getOptionsForPackage(" + packageName + "): not whitelisted");
+            }
+            return null;
+        }
+
+        // TODO(b/122595322): need to check whitelisted activities as well.
+        final ArraySet<ComponentName> whitelistedComponents = null;
+        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;
+    }
+
     @Override
     protected void dumpLocked(String prefix, PrintWriter pw) {
         super.dumpLocked(prefix, pw);
index 54eea5d..de9896e 100644 (file)
@@ -15,6 +15,9 @@
  */
 package com.android.server.contentcapture;
 
+import static android.view.contentcapture.ContentCaptureHelper.sDebug;
+import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
@@ -75,7 +78,7 @@ final class RemoteContentCaptureService
         }
         try {
             if (state) {
-                mService.onConnected(mServerCallback);
+                mService.onConnected(mServerCallback, sVerbose, sDebug);
             } else {
                 mService.onDisconnected();
             }
index 96cd79b..7c46f1d 100644 (file)
@@ -190,6 +190,7 @@ import android.appwidget.AppWidgetManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -341,6 +342,7 @@ import com.android.server.Watchdog;
 import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
 import com.android.server.am.MemoryStatUtil.MemoryStat;
 import com.android.server.appop.AppOpsService;
+import com.android.server.contentcapture.ContentCaptureManagerInternal;
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.pm.Installer;
@@ -4748,6 +4750,15 @@ public class ActivityManagerService extends IActivityManager.Stub
                             app.info.packageName, app.info.versionCode, app.userId);
                 }
             }
+            ContentCaptureOptions contentCaptureOptions = null;
+            if (UserHandle.getAppId(app.info.uid) >= Process.FIRST_APPLICATION_UID) {
+                final ContentCaptureManagerInternal ccm =
+                        LocalServices.getService(ContentCaptureManagerInternal.class);
+                if (ccm != null) {
+                    contentCaptureOptions = ccm.getOptionsForPackage(app.userId,
+                            app.info.packageName);
+                }
+            }
 
             checkTime(startTime, "attachApplicationLocked: immediately before bindApplication");
             bindApplicationTimeMillis = SystemClock.elapsedRealtime();
@@ -4768,7 +4779,7 @@ public class ActivityManagerService extends IActivityManager.Stub
                         new Configuration(app.getWindowProcessController().getConfiguration()),
                         app.compat, getCommonServicesLocked(app.isolated),
                         mCoreSettingsObserver.getCoreSettingsLocked(),
-                        buildSerial, isAutofillCompatEnabled);
+                        buildSerial, isAutofillCompatEnabled, contentCaptureOptions);
             } else {
                 thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
                         null, null, null, testMode,
@@ -4777,7 +4788,7 @@ public class ActivityManagerService extends IActivityManager.Stub
                         new Configuration(app.getWindowProcessController().getConfiguration()),
                         app.compat, getCommonServicesLocked(app.isolated),
                         mCoreSettingsObserver.getCoreSettingsLocked(),
-                        buildSerial, isAutofillCompatEnabled);
+                        buildSerial, isAutofillCompatEnabled, contentCaptureOptions);
             }
             if (profilerInfo != null) {
                 profilerInfo.closeFd();
index 726362a..d04f920 100644 (file)
@@ -16,7 +16,9 @@
 package com.android.server.contentcapture;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.content.ContentCaptureOptions;
 import android.os.Bundle;
 import android.os.IBinder;
 
@@ -41,4 +43,12 @@ public abstract class ContentCaptureManagerInternal {
      */
     public abstract boolean sendActivityAssistData(@UserIdInt int userId,
             @NonNull IBinder activityToken, @NonNull Bundle data);
+
+    /**
+     * Gets the content capture options for the given user and package, or {@code null} if the
+     * package is not whitelisted by the service.
+     */
+    @Nullable
+    public abstract ContentCaptureOptions getOptionsForPackage(@UserIdInt int userId,
+            @NonNull String packageName);
 }