diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index f53ac1b770217..e9682637aa1e4 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -299,6 +299,10 @@ message Atom { CarPowerStateChanged car_power_state_changed = 203; GarageModeInfo garage_mode_info = 204; TestAtomReported test_atom_reported = 205 [(log_from_module) = "cts"]; + ContentCaptureCallerMismatchReported content_capture_caller_mismatch_reported = 206; + ContentCaptureServiceEvents content_capture_service_events = 207; + ContentCaptureSessionEvents content_capture_session_events = 208; + ContentCaptureFlushed content_capture_flushed = 209; } // Pulled events will start at field 10000. @@ -4829,6 +4833,95 @@ message BuildInformation { optional string tags = 9; } +/** + * Logs information about mismatched caller for content capture. + * + * Logged from: + * frameworks/base/core/java/android/service/contentcapture/ContentCaptureService.java + */ +message ContentCaptureCallerMismatchReported { + optional string intended_package = 1; + optional string calling_package = 2; +} + +/** + * Logs information about content capture service events. + * + * Logged from: + * frameworks/base/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureMetricsLogger.java + */ +message ContentCaptureServiceEvents { + // The type of event. + enum Event { + UNKNOWN = 0; + ON_CONNECTED = 1; + ON_DISCONNECTED = 2; + SET_WHITELIST = 3; + SET_DISABLED = 4; + ON_USER_DATA_REMOVED = 5; + } + optional Event event = 1; + // component/package of content capture service. + optional string service_info = 2; + // component/package of target. + // it's a concatenated list of component/package for SET_WHITELIST event + // separated by " ". + optional string target_info = 3; +} + +/** + * Logs information about content capture session events. + * + * Logged from: + * frameworks/base/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureMetricsLogger.java + */ +message ContentCaptureSessionEvents { + // The type of event. + enum Event { + UNKNOWN = 0; + ON_SESSION_STARTED = 1; + ON_SESSION_FINISHED = 2; + SESSION_NOT_CREATED = 3; + } + optional int32 session_id = 1; + optional Event event = 2; + // (n/a on session finished) + optional int32 state_flags = 3; + // component/package of content capture service. + optional string service_info = 4; + // component/package of app. + // (n/a on session finished) + optional string app_info = 5; + optional bool is_child_session = 6; +} + +/** + * Logs information about session being flushed. + * + * Logged from: + * frameworks/base/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureMetricsLogger.java + */ +message ContentCaptureFlushed { + optional int32 session_id = 1; + // component/package of content capture service. + optional string service_info = 2; + // component/package of app. + optional string app_info = 3; + // session start/finish events + optional int32 child_session_started = 4; + optional int32 child_session_finished = 5; + // count of view events. + optional int32 view_appeared_count = 6; + optional int32 view_disappeared_count = 7; + optional int32 view_text_changed_count = 8; + + // Flush stats. + optional int32 max_events = 9; + optional int32 idle_flush_freq = 10; + optional int32 text_flush_freq = 11; + optional int32 flush_reason = 12; +} + /** * Pulls on-device BatteryStats power use calculations for the overall device. */ diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java index 02ce87324a4f9..08d9733ac8150 100644 --- a/core/java/android/service/contentcapture/ContentCaptureService.java +++ b/core/java/android/service/contentcapture/ContentCaptureService.java @@ -29,6 +29,7 @@ import android.annotation.TestApi; import android.app.Service; import android.content.ComponentName; +import android.content.ContentCaptureOptions; import android.content.Intent; import android.content.pm.ParceledListSlice; import android.os.Binder; @@ -40,6 +41,7 @@ import android.util.Log; import android.util.Slog; import android.util.SparseIntArray; +import android.util.StatsLog; import android.view.contentcapture.ContentCaptureCondition; import android.view.contentcapture.ContentCaptureContext; import android.view.contentcapture.ContentCaptureEvent; @@ -114,6 +116,9 @@ public abstract class ContentCaptureService extends Service { private Handler mHandler; private IContentCaptureServiceCallback mCallback; + private long mCallerMismatchTimeout = 1000; + private long mLastCallerMismatchLog; + /** * Binder that receives calls from the system server. */ @@ -176,9 +181,10 @@ public void onActivityEvent(ActivityEvent event) { new IContentCaptureDirectManager.Stub() { @Override - public void sendEvents(@SuppressWarnings("rawtypes") ParceledListSlice events) { + public void sendEvents(@SuppressWarnings("rawtypes") ParceledListSlice events, int reason, + ContentCaptureOptions options) { mHandler.sendMessage(obtainMessage(ContentCaptureService::handleSendEvents, - ContentCaptureService.this, Binder.getCallingUid(), events)); + ContentCaptureService.this, Binder.getCallingUid(), events, reason, options)); } }; @@ -424,14 +430,23 @@ private void handleOnCreateSession(@NonNull ContentCaptureContext context, } private void handleSendEvents(int uid, - @NonNull ParceledListSlice parceledEvents) { + @NonNull ParceledListSlice parceledEvents, int reason, + @Nullable ContentCaptureOptions options) { + final List events = parceledEvents.getList(); + if (events.isEmpty()) { + Log.w(TAG, "handleSendEvents() received empty list of events"); + return; + } + + // Metrics. + final FlushMetrics metrics = new FlushMetrics(); + ComponentName activityComponent = null; // Most events belong to the same session, so we can keep a reference to the last one // to avoid creating too many ContentCaptureSessionId objects int lastSessionId = NO_SESSION_ID; ContentCaptureSessionId sessionId = null; - final List events = parceledEvents.getList(); for (int i = 0; i < events.size(); i++) { final ContentCaptureEvent event = events.get(i); if (!handleIsRightCallerFor(event, uid)) continue; @@ -439,22 +454,44 @@ private void handleSendEvents(int uid, if (sessionIdInt != lastSessionId) { sessionId = new ContentCaptureSessionId(sessionIdInt); lastSessionId = sessionIdInt; + if (i != 0) { + writeFlushMetrics(lastSessionId, activityComponent, metrics, options, reason); + metrics.reset(); + } + } + final ContentCaptureContext clientContext = event.getContentCaptureContext(); + if (activityComponent == null && clientContext != null) { + activityComponent = clientContext.getActivityComponent(); } switch (event.getType()) { case ContentCaptureEvent.TYPE_SESSION_STARTED: - final ContentCaptureContext clientContext = event.getContentCaptureContext(); clientContext.setParentSessionId(event.getParentSessionId()); mSessionUids.put(sessionIdInt, uid); onCreateContentCaptureSession(clientContext, sessionId); + metrics.sessionStarted++; break; case ContentCaptureEvent.TYPE_SESSION_FINISHED: mSessionUids.delete(sessionIdInt); onDestroyContentCaptureSession(sessionId); + metrics.sessionFinished++; + break; + case ContentCaptureEvent.TYPE_VIEW_APPEARED: + onContentCaptureEvent(sessionId, event); + metrics.viewAppearedCount++; + break; + case ContentCaptureEvent.TYPE_VIEW_DISAPPEARED: + onContentCaptureEvent(sessionId, event); + metrics.viewDisappearedCount++; + break; + case ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED: + onContentCaptureEvent(sessionId, event); + metrics.viewTextChangedCount++; break; default: onContentCaptureEvent(sessionId, event); } } + writeFlushMetrics(lastSessionId, activityComponent, metrics, options, reason); } private void handleOnActivitySnapshot(int sessionId, @NonNull SnapshotData snapshotData) { @@ -499,7 +536,13 @@ private boolean handleIsRightCallerFor(@NonNull ContentCaptureEvent event, int u if (rightUid != uid) { Log.e(TAG, "invalid call from UID " + uid + ": session " + sessionId + " belongs to " + rightUid); - //TODO(b/111276913): log metrics as this could be a malicious app forging a sessionId + long now = System.currentTimeMillis(); + if (now - mLastCallerMismatchLog > mCallerMismatchTimeout) { + StatsLog.write(StatsLog.CONTENT_CAPTURE_CALLER_MISMATCH_REPORTED, + getPackageManager().getNameForUid(rightUid), + getPackageManager().getNameForUid(uid)); + mLastCallerMismatchLog = now; + } return false; } return true; @@ -530,4 +573,22 @@ public static void setClientState(@NonNull IResultReceiver clientReceiver, Slog.w(TAG, "Error async reporting result to client: " + e); } } + + /** + * Logs the metrics for content capture events flushing. + */ + private void writeFlushMetrics(int sessionId, @Nullable ComponentName app, + @NonNull FlushMetrics flushMetrics, @Nullable ContentCaptureOptions options, + int flushReason) { + if (mCallback == null) { + Log.w(TAG, "writeSessionFlush(): no server callback"); + return; + } + + try { + mCallback.writeSessionFlush(sessionId, app, flushMetrics, options, flushReason); + } catch (RemoteException e) { + Log.e(TAG, "failed to write flush metrics: " + e); + } + } } diff --git a/core/java/android/service/contentcapture/FlushMetrics.aidl b/core/java/android/service/contentcapture/FlushMetrics.aidl new file mode 100644 index 0000000000000..d0b935f3c4cb8 --- /dev/null +++ b/core/java/android/service/contentcapture/FlushMetrics.aidl @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.contentcapture; + +/* @hide */ +parcelable FlushMetrics; diff --git a/core/java/android/service/contentcapture/FlushMetrics.java b/core/java/android/service/contentcapture/FlushMetrics.java new file mode 100644 index 0000000000000..01f3a12ecea94 --- /dev/null +++ b/core/java/android/service/contentcapture/FlushMetrics.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.contentcapture; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Holds metrics for content capture events flushing. + * + * @hide + */ +public final class FlushMetrics implements Parcelable { + public int viewAppearedCount; + public int viewDisappearedCount; + public int viewTextChangedCount; + public int sessionStarted; + public int sessionFinished; + + /** + * Resets all flush metrics. + */ + public void reset() { + viewAppearedCount = 0; + viewDisappearedCount = 0; + viewTextChangedCount = 0; + sessionStarted = 0; + sessionFinished = 0; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(sessionStarted); + out.writeInt(sessionFinished); + out.writeInt(viewAppearedCount); + out.writeInt(viewDisappearedCount); + out.writeInt(viewTextChangedCount); + } + + @NonNull + public static final Creator CREATOR = new Creator() { + @NonNull + @Override + public FlushMetrics createFromParcel(Parcel in) { + final FlushMetrics flushMetrics = new FlushMetrics(); + flushMetrics.sessionStarted = in.readInt(); + flushMetrics.sessionFinished = in.readInt(); + flushMetrics.viewAppearedCount = in.readInt(); + flushMetrics.viewDisappearedCount = in.readInt(); + flushMetrics.viewTextChangedCount = in.readInt(); + return flushMetrics; + } + + @Override + public FlushMetrics[] newArray(int size) { + return new FlushMetrics[size]; + } + }; +} diff --git a/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl b/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl index 0550ad3ea20c0..ea6e76b47853a 100644 --- a/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl +++ b/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl @@ -18,6 +18,8 @@ package android.service.contentcapture; import android.content.ComponentName; import android.view.contentcapture.ContentCaptureCondition; +import android.service.contentcapture.FlushMetrics; +import android.content.ContentCaptureOptions; import java.util.List; @@ -30,4 +32,8 @@ oneway interface IContentCaptureServiceCallback { void setContentCaptureWhitelist(in List packages, in List activities); void setContentCaptureConditions(String packageName, in List conditions); void disableSelf(); - } + + // Logs aggregated content capture flush metrics to Statsd + void writeSessionFlush(int sessionId, in ComponentName app, in FlushMetrics flushMetrics, + in ContentCaptureOptions options, int flushReason); +} diff --git a/core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl b/core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl index 8d8117bf9ca18..959bf13c55fc4 100644 --- a/core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl +++ b/core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl @@ -18,6 +18,7 @@ package android.view.contentcapture; import android.content.pm.ParceledListSlice; import android.view.contentcapture.ContentCaptureEvent; +import android.content.ContentCaptureOptions; /** * Interface between an app (ContentCaptureManager / ContentCaptureSession) and the app providing @@ -26,5 +27,6 @@ import android.view.contentcapture.ContentCaptureEvent; * @hide */ oneway interface IContentCaptureDirectManager { - void sendEvents(in ParceledListSlice events); + // reason and options are used only for metrics logging. + void sendEvents(in ParceledListSlice events, int reason, in ContentCaptureOptions options); } diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 7241664602e9d..c5a5f7360321d 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -498,7 +498,7 @@ void flush(@FlushReason int reason) { } final ParceledListSlice events = clearEvents(); - mDirectServiceInterface.sendEvents(events); + mDirectServiceInterface.sendEvents(events, reason, mManager.mOptions); } catch (RemoteException e) { Log.w(TAG, "Error sending " + numberEvents + " for " + getDebugState() + ": " + e); diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureMetricsLogger.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureMetricsLogger.java new file mode 100644 index 0000000000000..dd1b84b16f68d --- /dev/null +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureMetricsLogger.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.contentcapture; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.ContentCaptureOptions; +import android.service.contentcapture.FlushMetrics; +import android.util.StatsLog; + +import java.util.List; + +/** @hide */ +public final class ContentCaptureMetricsLogger { + /** + * Class only contains static utility functions, and should not be instantiated + */ + private ContentCaptureMetricsLogger() { + } + + /** @hide */ + public static void writeServiceEvent(int eventType, @NonNull String serviceName, + @Nullable String targetPackage) { + StatsLog.write(StatsLog.CONTENT_CAPTURE_SERVICE_EVENTS, eventType, serviceName, + targetPackage); + } + + /** @hide */ + public static void writeServiceEvent(int eventType, @NonNull ComponentName service, + @Nullable ComponentName target) { + writeServiceEvent(eventType, ComponentName.flattenToShortString(service), + ComponentName.flattenToShortString(target)); + } + + /** @hide */ + public static void writeServiceEvent(int eventType, @NonNull ComponentName service, + @Nullable String targetPackage) { + writeServiceEvent(eventType, ComponentName.flattenToShortString(service), targetPackage); + } + + /** @hide */ + public static void writeServiceEvent(int eventType, @NonNull ComponentName service) { + writeServiceEvent(eventType, ComponentName.flattenToShortString(service), null); + } + + /** @hide */ + public static void writeSetWhitelistEvent(@Nullable ComponentName service, + @Nullable List packages, @Nullable List activities) { + final String serviceName = ComponentName.flattenToShortString(service); + StringBuilder stringBuilder = new StringBuilder(); + if (packages != null && packages.size() > 0) { + final int size = packages.size(); + stringBuilder.append(packages.get(0)); + for (int i = 1; i < size; i++) { + stringBuilder.append(" "); + stringBuilder.append(packages.get(i)); + } + } + if (activities != null && activities.size() > 0) { + stringBuilder.append(" "); + stringBuilder.append(activities.get(0).flattenToShortString()); + final int size = activities.size(); + for (int i = 1; i < size; i++) { + stringBuilder.append(" "); + stringBuilder.append(activities.get(i).flattenToShortString()); + } + } + StatsLog.write(StatsLog.CONTENT_CAPTURE_SERVICE_EVENTS, + StatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__SET_WHITELIST, + serviceName, stringBuilder.toString()); + } + + /** @hide */ + public static void writeSessionEvent(int sessionId, int event, int flags, + @NonNull ComponentName service, @Nullable ComponentName app, boolean isChildSession) { + StatsLog.write(StatsLog.CONTENT_CAPTURE_SESSION_EVENTS, sessionId, event, flags, + ComponentName.flattenToShortString(service), + ComponentName.flattenToShortString(app), isChildSession); + } + + /** @hide */ + public static void writeSessionFlush(int sessionId, @NonNull ComponentName service, + @Nullable ComponentName app, @NonNull FlushMetrics fm, + @NonNull ContentCaptureOptions options, int flushReason) { + StatsLog.write(StatsLog.CONTENT_CAPTURE_FLUSHED, sessionId, + ComponentName.flattenToShortString(service), + ComponentName.flattenToShortString(app), fm.sessionStarted, fm.sessionFinished, + fm.viewAppearedCount, fm.viewDisappearedCount, fm.viewTextChangedCount, + options.maxBufferSize, options.idleFlushingFrequencyMs, + options.textChangeFlushingFrequencyMs, flushReason); + } +} diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index 67c3d01cb86b2..a186d4e7f467c 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -24,6 +24,9 @@ import static android.view.contentcapture.ContentCaptureSession.STATE_NOT_WHITELISTED; import static android.view.contentcapture.ContentCaptureSession.STATE_NO_SERVICE; +import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeServiceEvent; +import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeSessionEvent; +import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeSetWhitelistEvent; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE; @@ -35,6 +38,7 @@ 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; @@ -48,6 +52,7 @@ import android.service.contentcapture.ActivityEvent.ActivityEventType; import android.service.contentcapture.ContentCaptureService; import android.service.contentcapture.ContentCaptureServiceInfo; +import android.service.contentcapture.FlushMetrics; import android.service.contentcapture.IContentCaptureServiceCallback; import android.service.contentcapture.SnapshotData; import android.util.ArrayMap; @@ -55,6 +60,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.util.StatsLog; import android.view.contentcapture.ContentCaptureCondition; import android.view.contentcapture.DataRemovalRequest; @@ -231,7 +237,6 @@ void onPackageUpdatedLocked() { resurrectSessionsLocked(); } - // TODO(b/119613670): log metrics @GuardedBy("mLock") public void startSessionLocked(@NonNull IBinder activityToken, @NonNull ActivityPresentationInfo activityPresentationInfo, int sessionId, int uid, @@ -263,9 +268,14 @@ public void startSessionLocked(@NonNull IBinder activityToken, if (!enabled) { // TODO: it would be better to split in differet reasons, like - // STATE_DISABLED_NO_SERVICE and STATE_DISABLED_BY_DEVICE_POLICY + // STATE_DISABLED_NO and STATE_DISABLED_BY_DEVICE_POLICY setClientState(clientReceiver, STATE_DISABLED | STATE_NO_SERVICE, /* binder= */ null); + // Log metrics. + writeSessionEvent(sessionId, + StatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__SESSION_NOT_CREATED, + STATE_DISABLED | STATE_NO_SERVICE, serviceComponentName, + componentName, /* isChildSession= */ false); return; } if (serviceComponentName == null) { @@ -285,6 +295,11 @@ public void startSessionLocked(@NonNull IBinder activityToken, } setClientState(clientReceiver, STATE_DISABLED | STATE_NOT_WHITELISTED, /* binder= */ null); + // Log metrics. + writeSessionEvent(sessionId, + StatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__SESSION_NOT_CREATED, + STATE_DISABLED | STATE_NOT_WHITELISTED, serviceComponentName, + componentName, /* isChildSession= */ false); return; } @@ -294,6 +309,11 @@ public void startSessionLocked(@NonNull IBinder activityToken, + ": ignoring because it already exists for " + existingSession.mActivityToken); setClientState(clientReceiver, STATE_DISABLED | STATE_DUPLICATED_ID, /* binder=*/ null); + // Log metrics. + writeSessionEvent(sessionId, + StatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__SESSION_NOT_CREATED, + STATE_DISABLED | STATE_DUPLICATED_ID, + serviceComponentName, componentName, /* isChildSession= */ false); return; } @@ -302,11 +322,15 @@ public void startSessionLocked(@NonNull IBinder activityToken, } if (mRemoteService == null) { - // TODO(b/119613670): log metrics Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken + ": ignoring because service is not set"); setClientState(clientReceiver, STATE_DISABLED | STATE_NO_SERVICE, /* binder= */ null); + // Log metrics. + writeSessionEvent(sessionId, + StatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__SESSION_NOT_CREATED, + STATE_DISABLED | STATE_NO_SERVICE, serviceComponentName, + componentName, /* isChildSession= */ false); return; } @@ -324,7 +348,6 @@ public void startSessionLocked(@NonNull IBinder activityToken, newSession.notifySessionStartedLocked(clientReceiver); } - // TODO(b/119613670): log metrics @GuardedBy("mLock") public void finishSessionLocked(int sessionId) { if (!isEnabledLocked()) { @@ -553,6 +576,7 @@ public void setContentCaptureWhitelist(List packages, + " for user " + mUserId); } mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities); + writeSetWhitelistEvent(getServiceComponentName(), packages, activities); // Must disable session that are not the whitelist anymore... final int numSessions = mSessions.size(); @@ -602,7 +626,6 @@ public void setContentCaptureConditions(String packageName, mConditionsByPkg.put(packageName, new ArraySet<>(conditions)); } } - // TODO(b/119613670): log metrics } @Override @@ -616,6 +639,15 @@ public void disableSelf() { } finally { Binder.restoreCallingIdentity(token); } + writeServiceEvent(StatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__SET_DISABLED, + getServiceComponentName()); + } + + @Override + public void writeSessionFlush(int sessionId, ComponentName app, FlushMetrics flushMetrics, + ContentCaptureOptions options, int flushReason) { + ContentCaptureMetricsLogger.writeSessionFlush(sessionId, getServiceComponentName(), app, + flushMetrics, options, flushReason); } } } diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java index 2171033c5a288..18daf325db8c9 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java @@ -18,6 +18,9 @@ import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; +import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeServiceEvent; +import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeSessionEvent; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; @@ -28,6 +31,7 @@ import android.service.contentcapture.IContentCaptureServiceCallback; import android.service.contentcapture.SnapshotData; import android.util.Slog; +import android.util.StatsLog; import android.view.contentcapture.ContentCaptureContext; import android.view.contentcapture.DataRemovalRequest; @@ -77,6 +81,8 @@ protected void handleOnConnectedStateChanged(boolean connected) { if (connected) { try { mService.onConnected(mServerCallback, sVerbose, sDebug); + writeServiceEvent(StatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_CONNECTED, + mComponentName); } finally { // Update the system-service state, in case the service reconnected after // dying @@ -84,6 +90,8 @@ protected void handleOnConnectedStateChanged(boolean connected) { } } else { mService.onDisconnected(); + writeServiceEvent(StatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_DISCONNECTED, + mComponentName); } } catch (Exception e) { Slog.w(mTag, "Exception calling onConnectedStateChanged(" + connected + "): " + e); @@ -102,6 +110,10 @@ public void onSessionStarted(@Nullable ContentCaptureContext context, int sessio @NonNull IResultReceiver clientReceiver, int initialState) { scheduleAsyncRequest( (s) -> s.onSessionStarted(context, sessionId, uid, clientReceiver, initialState)); + // Metrics logging. + writeSessionEvent(sessionId, + StatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__ON_SESSION_STARTED, initialState, + getComponentName(), context.getActivityComponent(), /* is_child_session= */ false); } /** @@ -110,6 +122,11 @@ public void onSessionStarted(@Nullable ContentCaptureContext context, int sessio */ public void onSessionFinished(int sessionId) { scheduleAsyncRequest((s) -> s.onSessionFinished(sessionId)); + // Metrics logging. + writeSessionEvent(sessionId, + StatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__ON_SESSION_FINISHED, + /* flags= */ 0, getComponentName(), /* app= */ null, + /* is_child_session= */ false); } /** @@ -124,6 +141,8 @@ public void onActivitySnapshotRequest(int sessionId, @NonNull SnapshotData snaps */ public void onDataRemovalRequest(@NonNull DataRemovalRequest request) { scheduleAsyncRequest((s) -> s.onDataRemovalRequest(request)); + writeServiceEvent(StatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_USER_DATA_REMOVED, + mComponentName); } /**