diff options
Diffstat (limited to 'core/java/android')
6 files changed, 177 insertions, 9 deletions
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java index 02ce87324a4f..08d9733ac815 100644 --- a/core/java/android/service/contentcapture/ContentCaptureService.java +++ b/core/java/android/service/contentcapture/ContentCaptureService.java @@ -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); + } + } } diff --git a/core/java/android/service/contentcapture/FlushMetrics.aidl b/core/java/android/service/contentcapture/FlushMetrics.aidl new file mode 100644 index 000000000000..d0b935f3c4cb --- /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 000000000000..01f3a12ecea9 --- /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<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]; + } + }; +} diff --git a/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl b/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl index 0550ad3ea20c..ea6e76b47853 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<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); +} diff --git a/core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl b/core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl index 8d8117bf9ca1..959bf13c55fc 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 7241664602e9..c5a5f7360321 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -498,7 +498,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } final ParceledListSlice<ContentCaptureEvent> events = clearEvents(); - mDirectServiceInterface.sendEvents(events); + mDirectServiceInterface.sendEvents(events, reason, mManager.mOptions); } catch (RemoteException e) { Log.w(TAG, "Error sending " + numberEvents + " for " + getDebugState() + ": " + e); |
