1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.media.metrics;
17 
18 import static com.android.media.editing.flags.Flags.FLAG_ADD_MEDIA_METRICS_EDITING;
19 
20 import android.annotation.FlaggedApi;
21 import android.annotation.FloatRange;
22 import android.annotation.IntDef;
23 import android.annotation.IntRange;
24 import android.annotation.LongDef;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.os.Bundle;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 
31 import java.lang.annotation.Retention;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.Objects;
35 
36 /** Event for an editing operation having ended. */
37 @FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
38 public final class EditingEndedEvent extends Event implements Parcelable {
39 
40     // The special value 0 is reserved for the field being unspecified in the proto.
41 
42     /** The editing operation was successful. */
43     public static final int FINAL_STATE_SUCCEEDED = 1;
44 
45     /** The editing operation was canceled. */
46     public static final int FINAL_STATE_CANCELED = 2;
47 
48     /** The editing operation failed due to an error. */
49     public static final int FINAL_STATE_ERROR = 3;
50 
51     /** @hide */
52     @IntDef(
53             prefix = {"FINAL_STATE_"},
54             value = {
55                 FINAL_STATE_SUCCEEDED,
56                 FINAL_STATE_CANCELED,
57                 FINAL_STATE_ERROR,
58             })
59     @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
60     public @interface FinalState {}
61 
62     private final @FinalState int mFinalState;
63 
64     private final float mFinalProgressPercent;
65 
66     // The special value 0 is reserved for the field being unspecified in the proto.
67 
68     /** Special value representing that no error occurred. */
69     public static final int ERROR_CODE_NONE = 1;
70 
71     /** Error code for unexpected runtime errors. */
72     public static final int ERROR_CODE_FAILED_RUNTIME_CHECK = 2;
73 
74     /** Error code for non-specific errors during input/output. */
75     public static final int ERROR_CODE_IO_UNSPECIFIED = 3;
76 
77     /** Error code for network connection failures. */
78     public static final int ERROR_CODE_IO_NETWORK_CONNECTION_FAILED = 4;
79 
80     /** Error code for network timeouts. */
81     public static final int ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT = 5;
82 
83     /** Caused by an HTTP server returning an unexpected HTTP response status code. */
84     public static final int ERROR_CODE_IO_BAD_HTTP_STATUS = 6;
85 
86     /** Caused by a non-existent file. */
87     public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 7;
88 
89     /**
90      * Caused by lack of permission to perform an IO operation. For example, lack of permission to
91      * access internet or external storage.
92      */
93     public static final int ERROR_CODE_IO_NO_PERMISSION = 8;
94 
95     /**
96      * Caused by failing to load data via cleartext HTTP, when the app's network security
97      * configuration does not permit it.
98      */
99     public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 9;
100 
101     /** Caused by reading data out of the data bounds. */
102     public static final int ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE = 10;
103 
104     /** Caused by a decoder initialization failure. */
105     public static final int ERROR_CODE_DECODER_INIT_FAILED = 11;
106 
107     /** Caused by a failure while trying to decode media samples. */
108     public static final int ERROR_CODE_DECODING_FAILED = 12;
109 
110     /** Caused by trying to decode content whose format is not supported. */
111     public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 13;
112 
113     /** Caused by an encoder initialization failure. */
114     public static final int ERROR_CODE_ENCODER_INIT_FAILED = 14;
115 
116     /** Caused by a failure while trying to encode media samples. */
117     public static final int ERROR_CODE_ENCODING_FAILED = 15;
118 
119     /** Caused by trying to encode content whose format is not supported. */
120     public static final int ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED = 16;
121 
122     /** Caused by a video frame processing failure. */
123     public static final int ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED = 17;
124 
125     /** Caused by an audio processing failure. */
126     public static final int ERROR_CODE_AUDIO_PROCESSING_FAILED = 18;
127 
128     /** Caused by a failure while muxing media samples. */
129     public static final int ERROR_CODE_MUXING_FAILED = 19;
130 
131     /** @hide */
132     @IntDef(
133             prefix = {"ERROR_CODE_"},
134             value = {
135                 ERROR_CODE_NONE,
136                 ERROR_CODE_FAILED_RUNTIME_CHECK,
137                 ERROR_CODE_IO_UNSPECIFIED,
138                 ERROR_CODE_IO_NETWORK_CONNECTION_FAILED,
139                 ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT,
140                 ERROR_CODE_IO_BAD_HTTP_STATUS,
141                 ERROR_CODE_IO_FILE_NOT_FOUND,
142                 ERROR_CODE_IO_NO_PERMISSION,
143                 ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED,
144                 ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE,
145                 ERROR_CODE_DECODER_INIT_FAILED,
146                 ERROR_CODE_DECODING_FAILED,
147                 ERROR_CODE_DECODING_FORMAT_UNSUPPORTED,
148                 ERROR_CODE_ENCODER_INIT_FAILED,
149                 ERROR_CODE_ENCODING_FAILED,
150                 ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED,
151                 ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED,
152                 ERROR_CODE_AUDIO_PROCESSING_FAILED,
153                 ERROR_CODE_MUXING_FAILED,
154             })
155     @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
156     public @interface ErrorCode {}
157 
158     /** Special value for unknown {@linkplain #getTimeSinceCreatedMillis() time since creation}. */
159     public static final int TIME_SINCE_CREATED_UNKNOWN = -1;
160 
161     /** Special value for unknown {@linkplain #getFinalProgressPercent() final progress}. */
162     public static final int PROGRESS_PERCENT_UNKNOWN = -1;
163 
164     private final @ErrorCode int mErrorCode;
165     @SuppressWarnings("HidingField") // Hiding field from superclass as for playback events.
166     private final long mTimeSinceCreatedMillis;
167 
168     @Nullable private final String mExporterName;
169     @Nullable private final String mMuxerName;
170     private final ArrayList<MediaItemInfo> mInputMediaItemInfos;
171     @Nullable private final MediaItemInfo mOutputMediaItemInfo;
172 
173     /** @hide */
174     @LongDef(
175             prefix = {"OPERATION_TYPE_"},
176             flag = true,
177             value = {
178                 OPERATION_TYPE_VIDEO_TRANSCODE,
179                 OPERATION_TYPE_AUDIO_TRANSCODE,
180                 OPERATION_TYPE_VIDEO_EDIT,
181                 OPERATION_TYPE_AUDIO_EDIT,
182                 OPERATION_TYPE_VIDEO_TRANSMUX,
183                 OPERATION_TYPE_AUDIO_TRANSMUX,
184                 OPERATION_TYPE_PAUSED,
185                 OPERATION_TYPE_RESUMED,
186             })
187     @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
188     public @interface OperationType {}
189 
190     /** Input video was decoded and re-encoded. */
191     public static final long OPERATION_TYPE_VIDEO_TRANSCODE = 1;
192 
193     /** Input audio was decoded and re-encoded. */
194     public static final long OPERATION_TYPE_AUDIO_TRANSCODE = 1L << 1;
195 
196     /** Input video was edited. */
197     public static final long OPERATION_TYPE_VIDEO_EDIT = 1L << 2;
198 
199     /** Input audio was edited. */
200     public static final long OPERATION_TYPE_AUDIO_EDIT = 1L << 3;
201 
202     /** Input video samples were written (muxed) directly to the output file without transcoding. */
203     public static final long OPERATION_TYPE_VIDEO_TRANSMUX = 1L << 4;
204 
205     /** Input audio samples were written (muxed) directly to the output file without transcoding. */
206     public static final long OPERATION_TYPE_AUDIO_TRANSMUX = 1L << 5;
207 
208     /** The editing operation was paused before it completed. */
209     public static final long OPERATION_TYPE_PAUSED = 1L << 6;
210 
211     /** The editing operation resumed a previous (paused) operation. */
212     public static final long OPERATION_TYPE_RESUMED = 1L << 7;
213 
214     private final @OperationType long mOperationTypes;
215 
EditingEndedEvent( @inalState int finalState, float finalProgressPercent, @ErrorCode int errorCode, long timeSinceCreatedMillis, @Nullable String exporterName, @Nullable String muxerName, ArrayList<MediaItemInfo> inputMediaItemInfos, @Nullable MediaItemInfo outputMediaItemInfo, @OperationType long operationTypes, @NonNull Bundle extras)216     private EditingEndedEvent(
217             @FinalState int finalState,
218             float finalProgressPercent,
219             @ErrorCode int errorCode,
220             long timeSinceCreatedMillis,
221             @Nullable String exporterName,
222             @Nullable String muxerName,
223             ArrayList<MediaItemInfo> inputMediaItemInfos,
224             @Nullable MediaItemInfo outputMediaItemInfo,
225             @OperationType long operationTypes,
226             @NonNull Bundle extras) {
227         mFinalState = finalState;
228         mFinalProgressPercent = finalProgressPercent;
229         mErrorCode = errorCode;
230         mTimeSinceCreatedMillis = timeSinceCreatedMillis;
231         mExporterName = exporterName;
232         mMuxerName = muxerName;
233         mInputMediaItemInfos = inputMediaItemInfos;
234         mOutputMediaItemInfo = outputMediaItemInfo;
235         mOperationTypes = operationTypes;
236         mMetricsBundle = extras.deepCopy();
237     }
238 
239     /** Returns the state of the editing session when it ended. */
240     @FinalState
getFinalState()241     public int getFinalState() {
242         return mFinalState;
243     }
244 
245     /**
246      * Returns the progress of the editing operation in percent at the moment that it ended, or
247      * {@link #PROGRESS_PERCENT_UNKNOWN} if unknown.
248      */
getFinalProgressPercent()249     public float getFinalProgressPercent() {
250         return mFinalProgressPercent;
251     }
252 
253     /** Returns the error code for a {@linkplain #FINAL_STATE_ERROR failed} editing session. */
254     @ErrorCode
getErrorCode()255     public int getErrorCode() {
256         return mErrorCode;
257     }
258 
259     /**
260      * Gets the elapsed time since creating of the editing session, in milliseconds, or {@link
261      * #TIME_SINCE_CREATED_UNKNOWN} if unknown.
262      *
263      * @return The elapsed time since creating the editing session, in milliseconds, or {@link
264      *     #TIME_SINCE_CREATED_UNKNOWN} if unknown.
265      * @see LogSessionId
266      * @see EditingSession
267      */
268     @Override
269     @IntRange(from = TIME_SINCE_CREATED_UNKNOWN)
getTimeSinceCreatedMillis()270     public long getTimeSinceCreatedMillis() {
271         return mTimeSinceCreatedMillis;
272     }
273 
274     /**
275      * Returns the name of the library implementing the exporting operation, for example, a Maven
276      * artifact ID like "androidx.media3.media3-transformer:1.3.0-beta01", or {@code null} if
277      * unknown.
278      */
279     @Nullable
getExporterName()280     public String getExporterName() {
281         return mExporterName;
282     }
283 
284     /**
285      * Returns the name of the library implementing the media muxing operation, for example, a Maven
286      * artifact ID like "androidx.media3.media3-muxer:1.3.0-beta01", or {@code null} if unknown.
287      */
288     @Nullable
getMuxerName()289     public String getMuxerName() {
290         return mMuxerName;
291     }
292 
293     /** Gets information about the input media items, or an empty list if unspecified. */
294     @NonNull
getInputMediaItemInfos()295     public List<MediaItemInfo> getInputMediaItemInfos() {
296         return new ArrayList<>(mInputMediaItemInfos);
297     }
298 
299     /** Gets information about the output media item, or {@code null} if unspecified. */
300     @Nullable
getOutputMediaItemInfo()301     public MediaItemInfo getOutputMediaItemInfo() {
302         return mOutputMediaItemInfo;
303     }
304 
305     /** Gets a set of flags describing the types of operations performed. */
getOperationTypes()306     public @OperationType long getOperationTypes() {
307         return mOperationTypes;
308     }
309 
310     /**
311      * Gets metrics-related information that is not supported by dedicated methods.
312      *
313      * <p>It is intended to be used for backwards compatibility by the metrics infrastructure.
314      */
315     @Override
316     @NonNull
getMetricsBundle()317     public Bundle getMetricsBundle() {
318         return mMetricsBundle;
319     }
320 
321     @Override
322     @NonNull
toString()323     public String toString() {
324         return "EditingEndedEvent { "
325                 + "finalState = "
326                 + mFinalState
327                 + ", "
328                 + "finalProgressPercent = "
329                 + mFinalProgressPercent
330                 + ", "
331                 + "errorCode = "
332                 + mErrorCode
333                 + ", "
334                 + "timeSinceCreatedMillis = "
335                 + mTimeSinceCreatedMillis
336                 + ", "
337                 + "exporterName = "
338                 + mExporterName
339                 + ", "
340                 + "muxerName = "
341                 + mMuxerName
342                 + ", "
343                 + "inputMediaItemInfos = "
344                 + mInputMediaItemInfos
345                 + ", "
346                 + "outputMediaItemInfo = "
347                 + mOutputMediaItemInfo
348                 + ", "
349                 + "operationTypes = "
350                 + mOperationTypes
351                 + " }";
352     }
353 
354     @Override
equals(@ullable Object o)355     public boolean equals(@Nullable Object o) {
356         if (this == o) return true;
357         if (o == null || getClass() != o.getClass()) return false;
358         EditingEndedEvent that = (EditingEndedEvent) o;
359         return mFinalState == that.mFinalState
360                 && mFinalProgressPercent == that.mFinalProgressPercent
361                 && mErrorCode == that.mErrorCode
362                 && Objects.equals(mInputMediaItemInfos, that.mInputMediaItemInfos)
363                 && Objects.equals(mOutputMediaItemInfo, that.mOutputMediaItemInfo)
364                 && mOperationTypes == that.mOperationTypes
365                 && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis
366                 && Objects.equals(mExporterName, that.mExporterName)
367                 && Objects.equals(mMuxerName, that.mMuxerName);
368     }
369 
370     @Override
hashCode()371     public int hashCode() {
372         return Objects.hash(
373                 mFinalState,
374                 mFinalProgressPercent,
375                 mErrorCode,
376                 mInputMediaItemInfos,
377                 mOutputMediaItemInfo,
378                 mOperationTypes,
379                 mTimeSinceCreatedMillis,
380                 mExporterName,
381                 mMuxerName);
382     }
383 
384     @Override
writeToParcel(@onNull Parcel dest, int flags)385     public void writeToParcel(@NonNull Parcel dest, int flags) {
386         dest.writeInt(mFinalState);
387         dest.writeFloat(mFinalProgressPercent);
388         dest.writeInt(mErrorCode);
389         dest.writeLong(mTimeSinceCreatedMillis);
390         dest.writeString(mExporterName);
391         dest.writeString(mMuxerName);
392         dest.writeTypedList(mInputMediaItemInfos);
393         dest.writeTypedObject(mOutputMediaItemInfo, /* parcelableFlags= */ 0);
394         dest.writeLong(mOperationTypes);
395         dest.writeBundle(mMetricsBundle);
396     }
397 
398     @Override
describeContents()399     public int describeContents() {
400         return 0;
401     }
402 
EditingEndedEvent(@onNull Parcel in)403     private EditingEndedEvent(@NonNull Parcel in) {
404         mFinalState = in.readInt();
405         mFinalProgressPercent = in.readFloat();
406         mErrorCode = in.readInt();
407         mTimeSinceCreatedMillis = in.readLong();
408         mExporterName = in.readString();
409         mMuxerName = in.readString();
410         mInputMediaItemInfos = new ArrayList<>();
411         in.readTypedList(mInputMediaItemInfos, MediaItemInfo.CREATOR);
412         mOutputMediaItemInfo = in.readTypedObject(MediaItemInfo.CREATOR);
413         mOperationTypes = in.readLong();
414         mMetricsBundle = in.readBundle();
415     }
416 
417     public static final @NonNull Creator<EditingEndedEvent> CREATOR =
418             new Creator<>() {
419                 @Override
420                 public EditingEndedEvent[] newArray(int size) {
421                     return new EditingEndedEvent[size];
422                 }
423 
424                 @Override
425                 public EditingEndedEvent createFromParcel(@NonNull Parcel in) {
426                     return new EditingEndedEvent(in);
427                 }
428             };
429 
430     /** Builder for {@link EditingEndedEvent} */
431     @FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
432     public static final class Builder {
433         private final @FinalState int mFinalState;
434         private final ArrayList<MediaItemInfo> mInputMediaItemInfos;
435         private float mFinalProgressPercent;
436         private @ErrorCode int mErrorCode;
437         private long mTimeSinceCreatedMillis;
438         @Nullable private String mExporterName;
439         @Nullable private String mMuxerName;
440         @Nullable private MediaItemInfo mOutputMediaItemInfo;
441         private @OperationType long mOperationTypes;
442         private Bundle mMetricsBundle;
443 
444         /**
445          * Creates a new Builder.
446          *
447          * @param finalState The state of the editing session when it ended.
448          */
Builder(@inalState int finalState)449         public Builder(@FinalState int finalState) {
450             mFinalState = finalState;
451             mFinalProgressPercent = PROGRESS_PERCENT_UNKNOWN;
452             mErrorCode = ERROR_CODE_NONE;
453             mTimeSinceCreatedMillis = TIME_SINCE_CREATED_UNKNOWN;
454             mInputMediaItemInfos = new ArrayList<>();
455             mMetricsBundle = new Bundle();
456         }
457 
458         /**
459          * Sets the progress of the editing operation in percent at the moment that it ended.
460          *
461          * @param finalProgressPercent The progress of the editing operation in percent at the
462          *     moment that it ended.
463          * @see #getFinalProgressPercent()
464          */
setFinalProgressPercent( @loatRangefrom = 0, to = 100) float finalProgressPercent)465         public @NonNull Builder setFinalProgressPercent(
466                 @FloatRange(from = 0, to = 100) float finalProgressPercent) {
467             mFinalProgressPercent = finalProgressPercent;
468             return this;
469         }
470 
471         /**
472          * Sets the elapsed time since creating the editing session, in milliseconds.
473          *
474          * @param timeSinceCreatedMillis The elapsed time since creating the editing session, in
475          *     milliseconds, or {@link #TIME_SINCE_CREATED_UNKNOWN} if unknown.
476          * @see #getTimeSinceCreatedMillis()
477          */
setTimeSinceCreatedMillis( @ntRangefrom = TIME_SINCE_CREATED_UNKNOWN) long timeSinceCreatedMillis)478         public @NonNull Builder setTimeSinceCreatedMillis(
479                 @IntRange(from = TIME_SINCE_CREATED_UNKNOWN) long timeSinceCreatedMillis) {
480             mTimeSinceCreatedMillis = timeSinceCreatedMillis;
481             return this;
482         }
483 
484         /**
485          * The name of the library implementing the exporting operation. For example, a Maven
486          * artifact ID like "androidx.media3.media3-transformer:1.3.0-beta01".
487          *
488          * @param exporterName The name of the library implementing the export operation.
489          * @see #getExporterName()
490          */
setExporterName(@onNull String exporterName)491         public @NonNull Builder setExporterName(@NonNull String exporterName) {
492             mExporterName = Objects.requireNonNull(exporterName);
493             return this;
494         }
495 
496         /**
497          * The name of the library implementing the media muxing operation. For example, a Maven
498          * artifact ID like "androidx.media3.media3-muxer:1.3.0-beta01".
499          *
500          * @param muxerName The name of the library implementing the media muxing operation.
501          * @see #getMuxerName()
502          */
setMuxerName(@onNull String muxerName)503         public @NonNull Builder setMuxerName(@NonNull String muxerName) {
504             mMuxerName = Objects.requireNonNull(muxerName);
505             return this;
506         }
507 
508         /** Sets the error code for a {@linkplain #FINAL_STATE_ERROR failed} editing session. */
setErrorCode(@rrorCode int value)509         public @NonNull Builder setErrorCode(@ErrorCode int value) {
510             mErrorCode = value;
511             return this;
512         }
513 
514         /** Adds information about a media item that was input to the editing operation. */
addInputMediaItemInfo(@onNull MediaItemInfo mediaItemInfo)515         public @NonNull Builder addInputMediaItemInfo(@NonNull MediaItemInfo mediaItemInfo) {
516             mInputMediaItemInfos.add(Objects.requireNonNull(mediaItemInfo));
517             return this;
518         }
519 
520         /** Sets information about the output media item. */
setOutputMediaItemInfo(@onNull MediaItemInfo mediaItemInfo)521         public @NonNull Builder setOutputMediaItemInfo(@NonNull MediaItemInfo mediaItemInfo) {
522             mOutputMediaItemInfo = Objects.requireNonNull(mediaItemInfo);
523             return this;
524         }
525 
526         /**
527          * Adds an operation type to the set of operations performed.
528          *
529          * @param operationType A type of operation performed as part of this editing operation.
530          */
addOperationType(@perationType long operationType)531         public @NonNull Builder addOperationType(@OperationType long operationType) {
532             mOperationTypes |= operationType;
533             return this;
534         }
535 
536         /**
537          * Sets metrics-related information that is not supported by dedicated methods.
538          *
539          * <p>Used for backwards compatibility by the metrics infrastructure.
540          */
setMetricsBundle(@onNull Bundle metricsBundle)541         public @NonNull Builder setMetricsBundle(@NonNull Bundle metricsBundle) {
542             mMetricsBundle = Objects.requireNonNull(metricsBundle);
543             return this;
544         }
545 
546         /** Builds an instance. */
build()547         public @NonNull EditingEndedEvent build() {
548             return new EditingEndedEvent(
549                     mFinalState,
550                     mFinalProgressPercent,
551                     mErrorCode,
552                     mTimeSinceCreatedMillis,
553                     mExporterName,
554                     mMuxerName,
555                     mInputMediaItemInfos,
556                     mOutputMediaItemInfo,
557                     mOperationTypes,
558                     mMetricsBundle);
559         }
560     }
561 
562 }
563