diff --git a/api/test-current.txt b/api/test-current.txt index d91ad44b27c52..16098c1a80fab 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -2748,7 +2748,15 @@ package android.view.contentcapture { public final class ContentCaptureManager { method public boolean isContentCaptureFeatureEnabled(); method public void setContentCaptureFeatureEnabled(boolean); + field public static final String DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY = "idle_flush_frequency"; + field public static final String DEVICE_CONFIG_PROPERTY_LOGGING_LEVEL = "logging_level"; + field public static final String DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE = "log_history_size"; + field public static final String DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE = "max_buffer_size"; field public static final String DEVICE_CONFIG_PROPERTY_SERVICE_EXPLICITLY_ENABLED = "service_explicitly_enabled"; + field public static final String DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY = "text_change_flush_frequency"; + field public static final int LOGGING_LEVEL_DEBUG = 1; // 0x1 + field public static final int LOGGING_LEVEL_OFF = 0; // 0x0 + field public static final int LOGGING_LEVEL_VERBOSE = 2; // 0x2 } public final class ViewNode extends android.app.assist.AssistStructure.ViewNode { diff --git a/core/java/android/view/contentcapture/ContentCaptureHelper.java b/core/java/android/view/contentcapture/ContentCaptureHelper.java index 508880feb3c3a..1cf27fc56a8c7 100644 --- a/core/java/android/view/contentcapture/ContentCaptureHelper.java +++ b/core/java/android/view/contentcapture/ContentCaptureHelper.java @@ -15,16 +15,29 @@ */ package android.view.contentcapture; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_LOGGING_LEVEL; +import static android.view.contentcapture.ContentCaptureManager.LOGGING_LEVEL_DEBUG; +import static android.view.contentcapture.ContentCaptureManager.LOGGING_LEVEL_OFF; +import static android.view.contentcapture.ContentCaptureManager.LOGGING_LEVEL_VERBOSE; + +import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.Build; +import android.provider.DeviceConfig; +import android.util.Log; +import android.view.contentcapture.ContentCaptureManager.LoggingLevel; /** - * Helpe class for this package. + * Helper class for this package and server's. + * + * @hide */ -final class ContentCaptureHelper { +public final class ContentCaptureHelper { - // TODO(b/121044306): define a way to dynamically set them(for example, using settings?) - static final boolean VERBOSE = false; - static final boolean DEBUG = true; // STOPSHIP if not set to false + private static final String TAG = ContentCaptureHelper.class.getSimpleName(); + + public static boolean sVerbose = false; + public static boolean sDebug = true; /** * Used to log text that could contain PII. @@ -34,6 +47,61 @@ final class ContentCaptureHelper { return text == null ? null : text.length() + "_chars"; } + /** + * Gets the value of a device config property from the Content Capture namespace. + */ + public static int getIntDeviceConfigProperty(@NonNull String key, int defaultValue) { + final String value = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONTENT_CAPTURE, key); + if (value == null) return defaultValue; + + try { + return Integer.parseInt(value); + } catch (Exception e) { + Log.w(TAG, "error parsing value (" + value + ") of property " + key + ": " + e); + return defaultValue; + } + } + + /** + * 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 level = getIntDeviceConfigProperty(DEVICE_CONFIG_PROPERTY_LOGGING_LEVEL, + defaultLevel); + Log.i(TAG, "Setting logging level to " + getLoggingLevelAsString(level)); + sVerbose = sDebug = false; + switch (level) { + case LOGGING_LEVEL_VERBOSE: + sVerbose = true; + // fall through + case LOGGING_LEVEL_DEBUG: + sDebug = true; + return; + case LOGGING_LEVEL_OFF: + // You log nothing, Jon Snow! + return; + default: + Log.w(TAG, "setLoggingLevel(): invalud level: " + level); + } + } + + /** + * Gets a user-friendly value for a content capture logging level. + */ + public static String getLoggingLevelAsString(@LoggingLevel int level) { + switch (level) { + case LOGGING_LEVEL_OFF: + return "OFF"; + case LOGGING_LEVEL_DEBUG: + return "DEBUG"; + case LOGGING_LEVEL_VERBOSE: + return "VERBOSE"; + default: + return "UNKNOWN-" + level; + } + } + private ContentCaptureHelper() { throw new UnsupportedOperationException("contains only static methods"); } diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 0157d2634b39f..99063083e2295 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -15,9 +15,10 @@ */ package android.view.contentcapture; -import static android.view.contentcapture.ContentCaptureHelper.DEBUG; -import static android.view.contentcapture.ContentCaptureHelper.VERBOSE; +import static android.view.contentcapture.ContentCaptureHelper.sDebug; +import static android.view.contentcapture.ContentCaptureHelper.sVerbose; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -38,6 +39,8 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.SyncResultReceiver; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * TODO(b/123577059): add javadocs / mention it can be null @@ -78,6 +81,75 @@ public final class ContentCaptureManager { public static final String DEVICE_CONFIG_PROPERTY_SERVICE_EXPLICITLY_ENABLED = "service_explicitly_enabled"; + /** + * Maximum number of events that are buffered before sent to the app. + * + * @hide + */ + @TestApi + public static final String DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE = "max_buffer_size"; + + /** + * Frequency (in ms) of buffer flushes when no events are received. + * + * @hide + */ + @TestApi + public static final String DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY = "idle_flush_frequency"; + + /** + * Frequency (in ms) of buffer flushes when no events are received and the last one was a + * text change event. + * + * @hide + */ + @TestApi + public static final String DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY = + "text_change_flush_frequency"; + + /** + * Size of events that are logging on {@code dump}. + * + *

Set it to {@code 0} or less to disable history. + * + * @hide + */ + @TestApi + public static final String DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE = "log_history_size"; + + /** + * Sets the logging level for {@code logcat} statements. + * + *

Valid values are: {@link #LOGGING_LEVEL_OFF}, {@value #LOGGING_LEVEL_DEBUG}, and + * {@link #LOGGING_LEVEL_VERBOSE}. + * + * @hide + */ + @TestApi + public static final String DEVICE_CONFIG_PROPERTY_LOGGING_LEVEL = "logging_level"; + + + /** @hide */ + @TestApi + public static final int LOGGING_LEVEL_OFF = 0; + + /** @hide */ + @TestApi + public static final int LOGGING_LEVEL_DEBUG = 1; + + /** @hide */ + @TestApi + public static final int LOGGING_LEVEL_VERBOSE = 2; + + /** @hide */ + @IntDef(flag = false, value = { + LOGGING_LEVEL_OFF, + LOGGING_LEVEL_DEBUG, + LOGGING_LEVEL_VERBOSE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LoggingLevel {} + private final Object mLock = new Object(); @NonNull @@ -101,10 +173,16 @@ public final class ContentCaptureManager { /** @hide */ public ContentCaptureManager(@NonNull Context context, @NonNull IContentCaptureManager service) { - if (VERBOSE) Log.v(TAG, "Constructor for " + context.getPackageName()); mContext = Preconditions.checkNotNull(context, "context cannot be null"); mService = Preconditions.checkNotNull(service, "service 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(); + + if (sVerbose) Log.v(TAG, "Constructor for " + context.getPackageName()); + // TODO(b/119220549): we might not even need a handler, as the IPCs are oneway. But if we // do, then we should optimize it to run the tests after the Choreographer finishes the most // important steps of the frame. @@ -126,7 +204,7 @@ public final class ContentCaptureManager { synchronized (mLock) { if (mMainSession == null) { mMainSession = new MainContentCaptureSession(mContext, this, mHandler, mService); - if (VERBOSE) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession); + if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession); } return mMainSession; } @@ -210,7 +288,7 @@ public final class ContentCaptureManager { * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}. */ public void setContentCaptureEnabled(boolean enabled) { - if (DEBUG) { + if (sDebug) { Log.d(TAG, "setContentCaptureEnabled(): setting to " + enabled + " for " + mContext); } @@ -264,7 +342,7 @@ public final class ContentCaptureManager { @SystemApi @TestApi public void setContentCaptureFeatureEnabled(boolean enabled) { - if (DEBUG) Log.d(TAG, "setContentCaptureFeatureEnabled(): setting to " + enabled); + if (sDebug) Log.d(TAG, "setContentCaptureFeatureEnabled(): setting to " + enabled); final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); final int resultCode; @@ -308,10 +386,12 @@ public final class ContentCaptureManager { synchronized (mLock) { pw.print(prefix2); pw.print("isContentCaptureEnabled(): "); pw.println(isContentCaptureEnabled()); - 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(prefix); 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); if (mMainSession != null) { final String prefix3 = prefix2 + " "; pw.print(prefix2); pw.println("Main session:"); diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index b8d3fa6f4404c..ec3b44a268e56 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -15,8 +15,8 @@ */ package android.view.contentcapture; -import static android.view.contentcapture.ContentCaptureHelper.DEBUG; -import static android.view.contentcapture.ContentCaptureHelper.VERBOSE; +import static android.view.contentcapture.ContentCaptureHelper.sDebug; +import static android.view.contentcapture.ContentCaptureHelper.sVerbose; import android.annotation.CallSuper; import android.annotation.IntDef; @@ -233,7 +233,7 @@ public abstract class ContentCaptureSession implements AutoCloseable { public final ContentCaptureSession createContentCaptureSession( @NonNull ContentCaptureContext context) { final ContentCaptureSession child = newChild(context); - if (DEBUG) { + if (sDebug) { Log.d(TAG, "createContentCaptureSession(" + context + ": parent=" + mId + ", child=" + child.mId); } @@ -285,20 +285,20 @@ public abstract class ContentCaptureSession implements AutoCloseable { public final void destroy() { synchronized (mLock) { if (mDestroyed) { - if (DEBUG) Log.d(TAG, "destroy(" + mId + "): already destroyed"); + if (sDebug) Log.d(TAG, "destroy(" + mId + "): already destroyed"); return; } mDestroyed = true; // TODO(b/111276913): check state (for example, how to handle if it's waiting for remote // id) and send it to the cache of batched commands - if (VERBOSE) { + if (sVerbose) { Log.v(TAG, "destroy(): state=" + getStateAsString(mState) + ", mId=" + mId); } // Finish children first if (mChildren != null) { final int numberChildren = mChildren.size(); - if (VERBOSE) Log.v(TAG, "Destroying " + numberChildren + " children first"); + if (sVerbose) Log.v(TAG, "Destroying " + numberChildren + " children first"); for (int i = 0; i < numberChildren; i++) { final ContentCaptureSession child = mChildren.get(i); try { diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index f0f2c49f31ee1..f4021b11f317a 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -23,9 +23,13 @@ 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.DEBUG; -import static android.view.contentcapture.ContentCaptureHelper.VERBOSE; +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; @@ -64,6 +68,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { private static final String TAG = MainContentCaptureSession.class.getSimpleName(); + // For readability purposes... private static final boolean FORCE_FLUSH = true; /** @@ -71,17 +76,9 @@ public final class MainContentCaptureSession extends ContentCaptureSession { */ private static final int MSG_FLUSH = 1; - /** - * Maximum number of events that are buffered before sent to the app. - */ - // TODO(b/121044064): use settings - private static final int MAX_BUFFER_SIZE = 100; - - /** - * Frequency the buffer is flushed if stale. - */ - // TODO(b/121044064): use settings - private static final int FLUSHING_FREQUENCY_MS = 5_000; + 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. @@ -131,11 +128,21 @@ public final class MainContentCaptureSession extends ContentCaptureSession { @Nullable private ArrayList 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; - // TODO(b/121044064): use settings to set size - private final LocalLog mFlushHistory = new LocalLog(10); + @Nullable + private final LocalLog mFlushHistory; /** @hide */ protected MainContentCaptureSession(@NonNull Context context, @@ -145,6 +152,18 @@ public final class MainContentCaptureSession extends ContentCaptureSession { mManager = manager; 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); + + mFlushHistory = logHistorySize > 0 ? new LocalLog(logHistorySize) : null; } @Override @@ -169,14 +188,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { int flags) { if (!isContentCaptureEnabled()) return; - if (VERBOSE) { + if (sVerbose) { Log.v(TAG, "start(): token=" + token + ", comp=" + ComponentName.flattenToShortString(component)); } if (hasStarted()) { // TODO(b/122959591): make sure this is expected (and when), or use Log.w - if (DEBUG) { + if (sDebug) { Log.d(TAG, "ignoring handleStartSession(" + token + "/" + ComponentName.flattenToShortString(component) + " while on state " + getStateAsString(mState)); @@ -187,7 +206,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { mApplicationToken = token; mComponentName = component; - if (VERBOSE) { + if (sVerbose) { Log.v(TAG, "handleStartSession(): token=" + token + ", act=" + getDebugState() + ", id=" + mId); } @@ -252,7 +271,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { mState = resultCode; mDisabled.set(false); } - if (VERBOSE) { + if (sVerbose) { Log.v(TAG, "handleSessionStarted() result: id=" + mId + " resultCode=" + resultCode + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get() + ", binder=" + binder + ", events=" + (mEvents == null ? 0 : mEvents.size())); @@ -267,7 +286,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { @UiThread private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { final int eventType = event.getType(); - if (VERBOSE) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event); + if (sVerbose) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event); if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED && eventType != ContentCaptureEvent.TYPE_CONTEXT_UPDATED) { // TODO(b/120494182): comment when this could happen (dialogs?) @@ -280,14 +299,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { // This happens when the event was queued in the handler before the sesison was ready, // then handleSessionStarted() returned and set it as disabled - we need to drop it, // otherwise it will keep triggering handleScheduleFlush() - if (VERBOSE) Log.v(TAG, "handleSendEvent(): ignoring when disabled"); + if (sVerbose) Log.v(TAG, "handleSendEvent(): ignoring when disabled"); return; } if (mEvents == null) { - if (VERBOSE) { - Log.v(TAG, "handleSendEvent(): creating buffer for " + MAX_BUFFER_SIZE + " events"); + if (sVerbose) { + Log.v(TAG, "handleSendEvent(): creating buffer for " + mMaxBufferSize + " events"); } - mEvents = new ArrayList<>(MAX_BUFFER_SIZE); + mEvents = new ArrayList<>(mMaxBufferSize); } // Some type of events can be merged together @@ -299,7 +318,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { // TODO(b/121045053): check if flags match if (lastEvent.getType() == TYPE_VIEW_TEXT_CHANGED && lastEvent.getId().equals(event.getId())) { - if (VERBOSE) { + if (sVerbose) { Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text=" + getSanitizedString(event.getText())); } @@ -313,7 +332,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1); if (lastEvent.getType() == TYPE_VIEW_DISAPPEARED && event.getSessionId().equals(lastEvent.getSessionId())) { - if (VERBOSE) { + if (sVerbose) { Log.v(TAG, "Buffering TYPE_VIEW_DISAPPEARED events for session " + lastEvent.getSessionId()); } @@ -328,20 +347,20 @@ public final class MainContentCaptureSession extends ContentCaptureSession { final int numberEvents = mEvents.size(); - final boolean bufferEvent = numberEvents < MAX_BUFFER_SIZE; + final boolean bufferEvent = numberEvents < mMaxBufferSize; if (bufferEvent && !forceFlush) { scheduleFlush(FLUSH_REASON_IDLE_TIMEOUT, /* checkExisting= */ true); return; } - if (mState != STATE_ACTIVE && numberEvents >= MAX_BUFFER_SIZE) { + if (mState != STATE_ACTIVE && numberEvents >= mMaxBufferSize) { // 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 // not complete instead. Similarly, the manager service should return right away // when the user does not have a service set - if (DEBUG) { + if (sDebug) { Log.d(TAG, "Closing session for " + getDebugState() + " after " + numberEvents + " delayed events"); } @@ -396,12 +415,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { @UiThread private void scheduleFlush(@FlushReason int reason, boolean checkExisting) { - if (VERBOSE) { + if (sVerbose) { Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason) + ", checkExisting=" + checkExisting); } if (!hasStarted()) { - if (VERBOSE) Log.v(TAG, "handleScheduleFlush(): session not started yet"); + if (sVerbose) Log.v(TAG, "handleScheduleFlush(): session not started yet"); return; } @@ -416,19 +435,19 @@ public final class MainContentCaptureSession extends ContentCaptureSession { // "Renew" the flush message by removing the previous one mHandler.removeMessages(MSG_FLUSH); } - mNextFlush = System.currentTimeMillis() + FLUSHING_FREQUENCY_MS; - if (VERBOSE) { + mNextFlush = System.currentTimeMillis() + mIdleFlushingFrequencyMs; + if (sVerbose) { Log.v(TAG, "handleScheduleFlush(): scheduled to flush in " - + FLUSHING_FREQUENCY_MS + "ms: " + TimeUtils.logTimeOfDay(mNextFlush)); + + mIdleFlushingFrequencyMs + "ms: " + TimeUtils.logTimeOfDay(mNextFlush)); } // Post using a Runnable directly to trim a few μs from PooledLambda.obtainMessage() - mHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, FLUSHING_FREQUENCY_MS); + mHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, mIdleFlushingFrequencyMs); } @UiThread private void flushIfNeeded(@FlushReason int reason) { if (mEvents == null || mEvents.isEmpty()) { - if (VERBOSE) Log.v(TAG, "Nothing to flush"); + if (sVerbose) Log.v(TAG, "Nothing to flush"); return; } flush(reason); @@ -446,7 +465,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } if (mDirectServiceInterface == null) { - if (VERBOSE) { + if (sVerbose) { Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, " + "client not ready: " + mEvents); } @@ -458,14 +477,16 @@ public final class MainContentCaptureSession extends ContentCaptureSession { final int numberEvents = mEvents.size(); final String reasonString = getflushReasonAsString(reason); - if (DEBUG) { + if (sDebug) { Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState(reason)); } - // Logs reason, size, max size, idle timeout - final String logRecord = "r=" + reasonString + " s=" + numberEvents - + " m=" + MAX_BUFFER_SIZE + " i=" + FLUSHING_FREQUENCY_MS; - try { + if (mFlushHistory != null) { + // Logs reason, size, max size, idle timeout + final String logRecord = "r=" + reasonString + " s=" + numberEvents + + " m=" + mMaxBufferSize + " i=" + mIdleFlushingFrequencyMs; mFlushHistory.log(logRecord); + } + try { mHandler.removeMessages(MSG_FLUSH); final ParceledListSlice events = clearEvents(); @@ -498,7 +519,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { @UiThread private void destroySession() { - if (DEBUG) { + if (sDebug) { Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with " + (mEvents == null ? 0 : mEvents.size()) + " event(s) for " + getDebugState()); @@ -516,7 +537,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { // clearings out. @UiThread private void resetSession(int newState) { - if (VERBOSE) { + if (sVerbose) { Log.v(TAG, "handleResetSession(" + getActivityName() + "): from " + getStateAsString(mState) + " to " + getStateAsString(newState)); } @@ -646,8 +667,8 @@ 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(MAX_BUFFER_SIZE); - if (VERBOSE && numberEvents > 0) { + pw.print('/'); pw.println(mMaxBufferSize); + if (sVerbose && numberEvents > 0) { final String prefix3 = prefix + " "; for (int i = 0; i < numberEvents; i++) { final ContentCaptureEvent event = mEvents.get(i); @@ -655,13 +676,19 @@ public final class MainContentCaptureSession extends ContentCaptureSession { pw.println(); } } - pw.print(prefix); pw.print("flush frequency: "); pw.println(FLUSHING_FREQUENCY_MS); + pw.print(prefix); pw.print("flush frequency: "); pw.println(mIdleFlushingFrequencyMs); pw.print(prefix); pw.print("next flush: "); TimeUtils.formatDuration(mNextFlush - System.currentTimeMillis(), pw); pw.print(" ("); pw.print(TimeUtils.logTimeOfDay(mNextFlush)); pw.println(")"); } - pw.print(prefix); pw.println("flush history:"); - mFlushHistory.reverseDump(/* fd= */ null, pw, /* args= */ null); pw.println(); + if (mFlushHistory != null) { + pw.print(prefix); pw.println("flush history:"); + mFlushHistory.reverseDump(/* fd= */ null, pw, /* args= */ null); pw.println(); + } else { + pw.print(prefix); pw.println("not logging flush history"); + } + + super.dump(prefix, pw); } /** diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 4afbc641ea6c0..4bd50ec21d849 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -45,6 +45,7 @@ import android.provider.Settings; import android.util.LocalLog; import android.util.Slog; import android.util.SparseBooleanArray; +import android.view.contentcapture.ContentCaptureHelper; import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.IContentCaptureManager; import android.view.contentcapture.UserDataRemovalRequest; @@ -79,7 +80,8 @@ public final class ContentCaptureManagerService extends private final LocalService mLocalService = new LocalService(); - private final LocalLog mRequestsHistory = new LocalLog(20); + @Nullable + final LocalLog mRequestsHistory; @GuardedBy("mLock") private ActivityManagerInternal mAm; @@ -105,15 +107,19 @@ public final class ContentCaptureManagerService extends UserManager.DISALLOW_CONTENT_CAPTURE); DeviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_CONTENT_CAPTURE, ActivityThread.currentApplication().getMainExecutor(), - (namespace, key, value) -> { - if (!ContentCaptureManager.DEVICE_CONFIG_PROPERTY_SERVICE_EXPLICITLY_ENABLED - .equals(key)) { - Slog.i(mTag, "Ignoring change on " + key); - return; - } - setDisabledByDeviceConfig(value); - }); - setDisabledByDeviceConfig(); + (namespace, key, value) -> onDeviceConfigChange(key, value)); + setLoggingLevelFromDeviceConfig(); + setDisabledFromDeviceConfig(); + + 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); + } else { + if (debug) Slog.d(mTag, "disabled log history because size is " + loggingSize); + mRequestsHistory = null; + } // Sets which services are disabled final UserManager um = getContext().getSystemService(UserManager.class); @@ -213,7 +219,33 @@ public final class ContentCaptureManagerService extends return false; } - private void setDisabledByDeviceConfig() { + private void onDeviceConfigChange(@NonNull String key, @Nullable String value) { + switch (key) { + case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_SERVICE_EXPLICITLY_ENABLED: + setDisabledByDeviceConfig(value); + return; + case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_LOGGING_LEVEL: + setLoggingLevelFromDeviceConfig(); + return; + case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE: + 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"); + return; + default: + Slog.i(mTag, "Ignoring change on " + key); + } + } + + private void setLoggingLevelFromDeviceConfig() { + ContentCaptureHelper.setLoggingLevel(); + verbose = ContentCaptureHelper.sVerbose; + debug = ContentCaptureHelper.sDebug; + } + + private void setDisabledFromDeviceConfig() { final String value = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONTENT_CAPTURE, ContentCaptureManager.DEVICE_CONFIG_PROPERTY_SERVICE_EXPLICITLY_ENABLED); setDisabledByDeviceConfig(value); @@ -327,13 +359,6 @@ public final class ContentCaptureManagerService extends } } - /** - * Logs a request so it's dumped later... - */ - void logRequestLocked(@NonNull String historyItem) { - mRequestsHistory.log(historyItem); - } - private ActivityManagerInternal getAmInternal() { synchronized (mLock) { if (mAm == null) { @@ -527,9 +552,13 @@ public final class ContentCaptureManagerService extends synchronized (mLock) { dumpLocked("", pw); } - if (showHistory) { - pw.println(); pw.println("Requests history:"); pw.println(); + pw.print("Requests history: "); + if (mRequestsHistory == null) { + pw.println("disabled by device config"); + } else if (showHistory) { + pw.println(); mRequestsHistory.reverseDump(fd, pw, args); + pw.println(); } } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index 7102b82d5e18e..71502645796ab 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -187,13 +187,15 @@ final class ContentCapturePerUserService final ComponentName componentName = activityPresentationInfo.componentName; final ComponentName serviceComponentName = getServiceComponentName(); final boolean enabled = isEnabledLocked(); - final String historyItem = - "id=" + sessionId + " uid=" + uid - + " a=" + ComponentName.flattenToShortString(componentName) - + " t=" + taskId + " d=" + displayId - + " s=" + ComponentName.flattenToShortString(serviceComponentName) - + " u=" + mUserId + " f=" + flags + (enabled ? "" : " (disabled)"); - mMaster.logRequestLocked(historyItem); + if (mMaster.mRequestsHistory != null) { + final String historyItem = + "id=" + sessionId + " uid=" + uid + + " a=" + ComponentName.flattenToShortString(componentName) + + " t=" + taskId + " d=" + displayId + + " s=" + ComponentName.flattenToShortString(serviceComponentName) + + " u=" + mUserId + " f=" + flags + (enabled ? "" : " (disabled)"); + mMaster.mRequestsHistory.log(historyItem); + } if (!enabled) { // TODO: it would be better to split in differet reasons, like diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java index 4ed5c3d263b68..40948432751d9 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java @@ -20,6 +20,7 @@ import android.content.ComponentName; import android.os.IBinder; import android.service.contentcapture.ContentCaptureService; import android.service.contentcapture.SnapshotData; +import android.util.LocalLog; import android.util.Slog; import android.view.contentcapture.ContentCaptureContext; import android.view.contentcapture.ContentCaptureSessionId; @@ -86,7 +87,10 @@ final class ContentCaptureServerSession { */ @GuardedBy("mLock") public void sendActivitySnapshotLocked(@NonNull SnapshotData snapshotData) { - mService.getMaster().logRequestLocked("snapshot: id=" + mId); + final LocalLog logHistory = mService.getMaster().mRequestsHistory; + if (logHistory != null) { + logHistory.log("snapshot: id=" + mId); + } mRemoteService.onActivitySnapshotRequest(mId, snapshotData); }