Skip to content

Commit

Permalink
Metrics for content capture.
Browse files Browse the repository at this point in the history
Bug: 119613670
Test: statsd_testdrive & manual test
Change-Id: Ib2c61d2a3c08a9db779790417eb0177c2420d8fd
Merged-In: If43465ccee7454a7ebf9e15caa23fce7bae33cfe
  • Loading branch information
Adam He committed May 15, 2019
1 parent d20761b commit ff21853
Show file tree
Hide file tree
Showing 10 changed files with 432 additions and 14 deletions.
93 changes: 93 additions & 0 deletions cmds/statsd/src/atoms.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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));
}
};

Expand Down Expand Up @@ -424,37 +430,68 @@ private void handleOnCreateSession(@NonNull ContentCaptureContext context,
}

private void handleSendEvents(int uid,
@NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents) {
@NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents, int reason,
@Nullable ContentCaptureOptions options) {
final List<ContentCaptureEvent> 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<ContentCaptureEvent> events = parceledEvents.getList();
for (int i = 0; i < events.size(); i++) {
final ContentCaptureEvent event = events.get(i);
if (!handleIsRightCallerFor(event, uid)) continue;
int sessionIdInt = event.getSessionId();
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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
}
20 changes: 20 additions & 0 deletions core/java/android/service/contentcapture/FlushMetrics.aidl
Original file line number Diff line number Diff line change
@@ -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;
79 changes: 79 additions & 0 deletions core/java/android/service/contentcapture/FlushMetrics.java
Original file line number Diff line number Diff line change
@@ -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<FlushMetrics> CREATOR = new Creator<FlushMetrics>() {
@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];
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -30,4 +32,8 @@ oneway interface IContentCaptureServiceCallback {
void setContentCaptureWhitelist(in List<String> packages, in List<ComponentName> activities);
void setContentCaptureConditions(String packageName, in List<ContentCaptureCondition> 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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}
Loading

0 comments on commit ff21853

Please sign in to comment.