diff options
25 files changed, 468 insertions, 122 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index ab03aa490bbc..01f4a2fa09f0 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -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); } } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index d29feddc0963..89e848b2820e 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -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() { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 790863713b19..001cd69067a1 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -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 diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 6908ca27480c..3a1e80dc1c3f 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -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 diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index e7a8c0e28bc6..b73092a1276f 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -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); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index c12a92f44fa2..1faa2ac76d71 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -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 index 000000000000..82ffac4205d7 --- /dev/null +++ b/core/java/android/content/ContentCaptureOptions.aidl @@ -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 index 000000000000..2fe9f14c9dc9 --- /dev/null +++ b/core/java/android/content/ContentCaptureOptions.java @@ -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]; + } + + }; +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 25bfba26256f..fdb0041d49ed 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -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) { } /** diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 26ed3b736f80..68b4320568c2 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -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); + } } } diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java index d361a2c50935..d032e564111d 100644 --- a/core/java/android/service/contentcapture/ContentCaptureService.java +++ b/core/java/android/service/contentcapture/ContentCaptureService.java @@ -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); } diff --git a/core/java/android/service/contentcapture/IContentCaptureService.aidl b/core/java/android/service/contentcapture/IContentCaptureService.aidl index d92fb3bed679..eb650324a942 100644 --- a/core/java/android/service/contentcapture/IContentCaptureService.aidl +++ b/core/java/android/service/contentcapture/IContentCaptureService.aidl @@ -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); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a78f2b073a3a..278b9ff01c58 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -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) diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index b1fee2d17079..ab4847ded0e6 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -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 diff --git a/core/java/android/view/contentcapture/ContentCaptureHelper.java b/core/java/android/view/contentcapture/ContentCaptureHelper.java index 1cf27fc56a8c..6e84ff03d0f9 100644 --- a/core/java/android/view/contentcapture/ContentCaptureHelper.java +++ b/core/java/android/view/contentcapture/ContentCaptureHelper.java @@ -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) { diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 87e358c1165f..336d9979a781 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -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:"); diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index f4021b11f317..0abf68992938 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -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(")"); diff --git a/core/java/com/android/internal/policy/DecorContext.java b/core/java/com/android/internal/policy/DecorContext.java index 429c6187dfe0..67cdd5d8a479 100644 --- a/core/java/com/android/internal/policy/DecorContext.java +++ b/core/java/com/android/internal/policy/DecorContext.java @@ -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; } } diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index a5ea4411f97b..9fabe44f2a8a 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -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 diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java index 312e0e0d8268..cd885e0b9bcf 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java @@ -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 diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 45ceeda689f7..d5713a1febfe 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -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; + } } } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index 9e159600e42c..5b48046e3a4b 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -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); diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java index 54eea5d8591c..de9896ecffa6 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java @@ -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(); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 96cd79bfad13..7c46f1d70cb6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -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(); diff --git a/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java b/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java index 726362a7df36..d04f92039f4c 100644 --- a/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java +++ b/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java @@ -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); } |