Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ff218534 authored by Adam He's avatar Adam He
Browse files

Metrics for content capture.

Bug: 119613670
Test: statsd_testdrive & manual test
Change-Id: Ib2c61d2a3c08a9db779790417eb0177c2420d8fd
Merged-In: If43465ccee7454a7ebf9e15caa23fce7bae33cfe
parent d20761b1
Loading
Loading
Loading
Loading
+93 −0
Original line number Diff line number Diff line
@@ -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.
 */
+67 −6
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.annotation.SystemApi;
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.os.RemoteException;
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 abstract class ContentCaptureService extends Service {
            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 @@ public abstract class ContentCaptureService extends Service {
    }

    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;
@@ -439,22 +454,44 @@ public abstract class ContentCaptureService extends Service {
            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 @@ public abstract class ContentCaptureService extends Service {
        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 abstract class ContentCaptureService extends Service {
            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 −0
Original line number Diff line number Diff line
/**
 * 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 −0
Original line number Diff line number Diff line
/*
 * 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];
        }
    };
}
+7 −1
Original line number Diff line number Diff line
@@ -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<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);
}
Loading