diff --git a/core/api/current.txt b/core/api/current.txt index 9cbf12f7d5113..2f09ce11aeff0 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -24074,13 +24074,49 @@ package android.media.effect { package android.media.metrics { + public abstract class Event { + ctor protected Event(long); + method @IntRange(from=0xffffffff) public long getTimeSinceCreatedMillis(); + } + public class MediaMetricsManager { method @NonNull public android.media.metrics.PlaybackSession createPlaybackSession(); + field public static final long INVALID_TIMESTAMP = -1L; // 0xffffffffffffffffL } public final class PlaybackSession implements java.lang.AutoCloseable { method public void close(); method @NonNull public String getId(); + method public void reportPlaybackStateEvent(@NonNull android.media.metrics.PlaybackStateEvent); + } + + public final class PlaybackStateEvent extends android.media.metrics.Event implements android.os.Parcelable { + method public int describeContents(); + method public int getState(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int STATE_ABANDONED = 15; // 0xf + field public static final int STATE_BUFFERING = 6; // 0x6 + field public static final int STATE_ENDED = 11; // 0xb + field public static final int STATE_FAILED = 13; // 0xd + field public static final int STATE_INTERRUPTED_BY_AD = 14; // 0xe + field public static final int STATE_JOINING_BACKGROUND = 1; // 0x1 + field public static final int STATE_JOINING_FOREGROUND = 2; // 0x2 + field public static final int STATE_NOT_STARTED = 0; // 0x0 + field public static final int STATE_PAUSED = 4; // 0x4 + field public static final int STATE_PAUSED_BUFFERING = 7; // 0x7 + field public static final int STATE_PLAYING = 3; // 0x3 + field public static final int STATE_SEEKING = 5; // 0x5 + field public static final int STATE_STOPPED = 12; // 0xc + field public static final int STATE_SUPPRESSED = 9; // 0x9 + field public static final int STATE_SUPPRESSED_BUFFERING = 10; // 0xa + } + + public static final class PlaybackStateEvent.Builder { + ctor public PlaybackStateEvent.Builder(); + method @NonNull public android.media.metrics.PlaybackStateEvent build(); + method @NonNull public android.media.metrics.PlaybackStateEvent.Builder setState(int); + method @NonNull public android.media.metrics.PlaybackStateEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long); } } diff --git a/media/java/android/media/metrics/Event.java b/media/java/android/media/metrics/Event.java new file mode 100644 index 0000000000000..5646dcdb6c9cf --- /dev/null +++ b/media/java/android/media/metrics/Event.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 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.media.metrics; + +import android.annotation.IntRange; + +/** + * Abstract class for metrics events. + */ +public abstract class Event { + private final long mTimeSinceCreatedMillis; + + // hide default constructor + /* package */ Event() { + mTimeSinceCreatedMillis = MediaMetricsManager.INVALID_TIMESTAMP; + } + + protected Event(long timeSinceCreatedMillis) { + mTimeSinceCreatedMillis = timeSinceCreatedMillis; + } + + /** + * Gets time since the corresponding instance is created in millisecond. + * @return the timestamp since the instance is created, or -1 if unknown. + */ + @IntRange(from = -1) + public long getTimeSinceCreatedMillis() { + return mTimeSinceCreatedMillis; + } +} diff --git a/media/java/android/media/metrics/MediaMetricsManager.java b/media/java/android/media/metrics/MediaMetricsManager.java index f2eae5fad3c38..de780f672b28e 100644 --- a/media/java/android/media/metrics/MediaMetricsManager.java +++ b/media/java/android/media/metrics/MediaMetricsManager.java @@ -26,7 +26,8 @@ import android.os.RemoteException; */ @SystemService(Context.MEDIA_METRICS_SERVICE) public class MediaMetricsManager { - // TODO: unhide APIs. + public static final long INVALID_TIMESTAMP = -1; + private static final String TAG = "MediaMetricsManager"; private IMediaMetricsManager mService; diff --git a/media/java/android/media/metrics/PlaybackSession.java b/media/java/android/media/metrics/PlaybackSession.java index 3056e9820f9d8..7f3450bcd12ff 100644 --- a/media/java/android/media/metrics/PlaybackSession.java +++ b/media/java/android/media/metrics/PlaybackSession.java @@ -69,9 +69,8 @@ public final class PlaybackSession implements AutoCloseable { /** * Reports playback state event. - * @hide */ - public void reportPlaybackStateEvent(PlaybackStateEvent event) { + public void reportPlaybackStateEvent(@NonNull PlaybackStateEvent event) { mManager.reportPlaybackStateEvent(mId, event); } diff --git a/media/java/android/media/metrics/PlaybackStateEvent.java b/media/java/android/media/metrics/PlaybackStateEvent.java index 6ce5bf0f0f331..8ca5b75dec988 100644 --- a/media/java/android/media/metrics/PlaybackStateEvent.java +++ b/media/java/android/media/metrics/PlaybackStateEvent.java @@ -17,6 +17,7 @@ package android.media.metrics; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; @@ -27,10 +28,8 @@ import java.util.Objects; /** * Playback state event. - * @hide */ -public final class PlaybackStateEvent implements Parcelable { - // TODO: more states +public final class PlaybackStateEvent extends Event implements Parcelable { /** Playback has not started (initial state) */ public static final int STATE_NOT_STARTED = 0; /** Playback is buffering in the background for initial playback start */ @@ -41,23 +40,57 @@ public final class PlaybackStateEvent implements Parcelable { public static final int STATE_PLAYING = 3; /** Playback is paused but ready to play */ public static final int STATE_PAUSED = 4; + /** Playback is handling a seek. */ + public static final int STATE_SEEKING = 5; + /** Playback is buffering to resume active playback. */ + public static final int STATE_BUFFERING = 6; + /** Playback is buffering while paused. */ + public static final int STATE_PAUSED_BUFFERING = 7; + /** Playback is suppressed (e.g. due to audio focus loss). */ + public static final int STATE_SUPPRESSED = 9; + /** + * Playback is suppressed (e.g. due to audio focus loss) while buffering to resume a playback. + */ + public static final int STATE_SUPPRESSED_BUFFERING = 10; + /** Playback has reached the end of the media. */ + public static final int STATE_ENDED = 11; + /** Playback is stopped and can be restarted. */ + public static final int STATE_STOPPED = 12; + /** Playback is stopped due a fatal error and can be retried. */ + public static final int STATE_FAILED = 13; + /** Playback is interrupted by an ad. */ + public static final int STATE_INTERRUPTED_BY_AD = 14; + /** Playback is abandoned before reaching the end of the media. */ + public static final int STATE_ABANDONED = 15; - private int mState; - private long mTimeSincePlaybackCreatedMillis; + private final int mState; + private final long mTimeSinceCreatedMillis; // These track ExoPlayer states. See the ExoPlayer documentation for the state transitions. + /** @hide */ @IntDef(prefix = "STATE_", value = { STATE_NOT_STARTED, STATE_JOINING_BACKGROUND, STATE_JOINING_FOREGROUND, STATE_PLAYING, - STATE_PAUSED + STATE_PAUSED, + STATE_SEEKING, + STATE_BUFFERING, + STATE_PAUSED_BUFFERING, + STATE_SUPPRESSED, + STATE_SUPPRESSED_BUFFERING, + STATE_ENDED, + STATE_STOPPED, + STATE_FAILED, + STATE_INTERRUPTED_BY_AD, + STATE_ABANDONED, }) @Retention(java.lang.annotation.RetentionPolicy.SOURCE) public @interface State {} /** * Converts playback state to string. + * @hide */ public static String stateToString(@State int value) { switch (value) { @@ -71,6 +104,26 @@ public final class PlaybackStateEvent implements Parcelable { return "STATE_PLAYING"; case STATE_PAUSED: return "STATE_PAUSED"; + case STATE_SEEKING: + return "STATE_SEEKING"; + case STATE_BUFFERING: + return "STATE_BUFFERING"; + case STATE_PAUSED_BUFFERING: + return "STATE_PAUSED_BUFFERING"; + case STATE_SUPPRESSED: + return "STATE_SUPPRESSED"; + case STATE_SUPPRESSED_BUFFERING: + return "STATE_SUPPRESSED_BUFFERING"; + case STATE_ENDED: + return "STATE_ENDED"; + case STATE_STOPPED: + return "STATE_STOPPED"; + case STATE_FAILED: + return "STATE_FAILED"; + case STATE_INTERRUPTED_BY_AD: + return "STATE_INTERRUPTED_BY_AD"; + case STATE_ABANDONED: + return "STATE_ABANDONED"; default: return Integer.toHexString(value); } @@ -83,14 +136,13 @@ public final class PlaybackStateEvent implements Parcelable { */ public PlaybackStateEvent( int state, - long timeSincePlaybackCreatedMillis) { + long timeSinceCreatedMillis) { + this.mTimeSinceCreatedMillis = timeSinceCreatedMillis; this.mState = state; - this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis; } /** * Gets playback state. - * @return */ public int getState() { return mState; @@ -98,9 +150,12 @@ public final class PlaybackStateEvent implements Parcelable { /** * Gets time since the corresponding playback is created in millisecond. + * @return the timestamp since the playback is created, or -1 if unknown. */ - public long getTimeSincePlaybackCreatedMillis() { - return mTimeSincePlaybackCreatedMillis; + @Override + @IntRange(from = -1) + public long getTimeSinceCreatedMillis() { + return mTimeSinceCreatedMillis; } @Override @@ -109,18 +164,18 @@ public final class PlaybackStateEvent implements Parcelable { if (o == null || getClass() != o.getClass()) return false; PlaybackStateEvent that = (PlaybackStateEvent) o; return mState == that.mState - && mTimeSincePlaybackCreatedMillis == that.mTimeSincePlaybackCreatedMillis; + && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis; } @Override public int hashCode() { - return Objects.hash(mState, mTimeSincePlaybackCreatedMillis); + return Objects.hash(mState, mTimeSinceCreatedMillis); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mState); - dest.writeLong(mTimeSincePlaybackCreatedMillis); + dest.writeLong(mTimeSinceCreatedMillis); } @Override @@ -131,10 +186,10 @@ public final class PlaybackStateEvent implements Parcelable { /** @hide */ /* package-private */ PlaybackStateEvent(@NonNull Parcel in) { int state = in.readInt(); - long timeSincePlaybackCreatedMillis = in.readLong(); + long timeSinceCreatedMillis = in.readLong(); this.mState = state; - this.mTimeSincePlaybackCreatedMillis = timeSincePlaybackCreatedMillis; + this.mTimeSinceCreatedMillis = timeSinceCreatedMillis; } public static final @NonNull Parcelable.Creator CREATOR = @@ -150,4 +205,43 @@ public final class PlaybackStateEvent implements Parcelable { } }; + /** + * A builder for {@link PlaybackStateEvent} + */ + public static final class Builder { + private int mState = STATE_NOT_STARTED; + private long mTimeSinceCreatedMillis = -1; + + /** + * Creates a new Builder. + */ + public Builder() { + } + + /** + * Sets playback state. + */ + public @NonNull Builder setState(@State int value) { + mState = value; + return this; + } + + /** + * Sets timestamp since the creation in milliseconds. + * @param value the timestamp since the creation in milliseconds. + * -1 indicates the value is unknown. + */ + public @NonNull Builder setTimeSinceCreatedMillis(@IntRange(from = -1) long value) { + mTimeSinceCreatedMillis = value; + return this; + } + + /** Builds the instance. */ + public @NonNull PlaybackStateEvent build() { + PlaybackStateEvent o = new PlaybackStateEvent( + mState, + mTimeSinceCreatedMillis); + return o; + } + } } diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java index d48b9a4e78798..76bdf44e9df21 100644 --- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java +++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java @@ -83,7 +83,14 @@ public final class MediaMetricsManagerService extends SystemService { @Override public void reportPlaybackStateEvent( String sessionId, PlaybackStateEvent event, int userId) { - // TODO: log it to statsd + StatsEvent statsEvent = StatsEvent.newBuilder() + .setAtomId(322) + .writeString(sessionId) + .writeInt(event.getState()) + .writeLong(event.getTimeSinceCreatedMillis()) + .usePooledBuffer() + .build(); + StatsLog.write(statsEvent); } @Override diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index bd2046490ec9e..24c84f075f5d4 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -138,6 +138,7 @@ import com.android.server.integrity.AppIntegrityManagerService; import com.android.server.lights.LightsService; import com.android.server.location.LocationManagerService; import com.android.server.media.MediaRouterService; +import com.android.server.media.metrics.MediaMetricsManagerService; import com.android.server.media.projection.MediaProjectionManagerService; import com.android.server.net.NetworkPolicyManagerService; import com.android.server.net.NetworkStatsService; @@ -2392,6 +2393,10 @@ public final class SystemServer implements Dumpable { t.traceBegin("StartPeopleService"); mSystemServiceManager.startService(PeopleService.class); t.traceEnd(); + + t.traceBegin("StartMediaMetricsManager"); + mSystemServiceManager.startService(MediaMetricsManagerService.class); + t.traceEnd(); } if (!isWatch) {