diff options
| author | Felipe Leme <felipeal@google.com> | 2019-02-19 09:42:24 -0800 |
|---|---|---|
| committer | Felipe Leme <felipeal@google.com> | 2019-02-20 18:44:05 -0800 |
| commit | 326f15a392e167b67f9dfaf130ee249b2ff733f8 (patch) | |
| tree | 4493229903accf06354c2562c29d399415cc6ef8 /core/java | |
| parent | 98ab00f921250aa18521076d93b41b1136b97bf5 (diff) | |
Optimized Content Capture workflow by caching some state at the application level.
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
Diffstat (limited to 'core/java')
17 files changed, 322 insertions, 98 deletions
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; } } |
