From 3eb57dc580b7760d10114ade35a5b67a299aed52 Mon Sep 17 00:00:00 2001 From: hkuang Date: Mon, 24 Aug 2020 11:38:31 -0700 Subject: [PATCH] TranscodingAPI: Expose MediaTranscodeManager as System API. Bug: 160260102 Test: Unit test Change-Id: I38e9efc46e23fe1aafe65100bbc7f72eb200720f --- Android.bp | 3 +- api/system-current.txt | 55 ++++ api/test-current.txt | 54 ++++ .../android/app/SystemServiceRegistry.java | 10 + core/java/android/content/Context.java | 11 + .../android/media/MediaTranscodeManager.java | 241 +++++++++--------- .../MediaTranscodeManagerTest.java | 27 +- ...iaTranscodeManagerWithMockServiceTest.java | 2 +- .../MediaTranscodingBenchmark.java | 2 +- non-updatable-api/system-current.txt | 55 ++++ 10 files changed, 338 insertions(+), 122 deletions(-) diff --git a/Android.bp b/Android.bp index e756b34281649..8475fbd87c09e 100644 --- a/Android.bp +++ b/Android.bp @@ -333,6 +333,7 @@ filegroup { ":installd_aidl", ":keystore_aidl", ":libaudioclient_aidl", + ":mediatranscoding_aidl_interface-java-source", ":libbinder_aidl", ":libbluetooth-binder-aidl", ":libcamera_client_aidl", @@ -576,7 +577,6 @@ java_library { // If MimeMap ever becomes its own APEX, then this dependency would need to be removed // in favor of an API stubs dependency in java_library "framework" below. "mimemap", - "mediatranscoding_aidl_interface-java", ], // For backwards compatibility. stem: "framework", @@ -623,7 +623,6 @@ java_library { static_libs: [ "exoplayer2-extractor", "android.hardware.wifi-V1.0-java-constants", - "mediatranscoding_aidl_interface-java", // Additional dependencies needed to build the ike API classes. "ike-internals", diff --git a/api/system-current.txt b/api/system-current.txt index 00a59bf4f1e30..dbe2e318a7ba1 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1741,6 +1741,7 @@ package android.content { field public static final String ETHERNET_SERVICE = "ethernet"; field public static final String EUICC_CARD_SERVICE = "euicc_card"; field public static final String HDMI_CONTROL_SERVICE = "hdmi_control"; + field public static final String MEDIA_TRANSCODING_SERVICE = "media_transcoding"; field public static final String NETD_SERVICE = "netd"; field public static final String NETWORK_SCORE_SERVICE = "network_score"; field public static final String OEM_LOCK_SERVICE = "oem_lock"; @@ -4333,6 +4334,60 @@ package android.media { field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int RADIO_TUNER = 1998; // 0x7ce } + public final class MediaTranscodeManager implements java.lang.AutoCloseable { + method public void close(); + method @NonNull public android.media.MediaTranscodeManager.TranscodingJob enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener) throws java.io.FileNotFoundException; + method protected void finalize(); + field public static final int PRIORITY_OFFLINE = 2; // 0x2 + field public static final int PRIORITY_REALTIME = 1; // 0x1 + field public static final int TRANSCODING_TYPE_VIDEO = 1; // 0x1 + } + + @java.lang.FunctionalInterface public static interface MediaTranscodeManager.OnTranscodingFinishedListener { + method public void onTranscodingFinished(@NonNull android.media.MediaTranscodeManager.TranscodingJob); + } + + public static final class MediaTranscodeManager.TranscodingJob { + method public void cancel(); + method public int getJobId(); + method @IntRange(from=0, to=100) public int getProgress(); + method public int getResult(); + method public int getStatus(); + method public boolean retry(); + method public void setOnProgressUpdateListener(@NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener); + method public void setOnProgressUpdateListener(int, @NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener); + field public static final int RESULT_CANCELED = 4; // 0x4 + field public static final int RESULT_ERROR = 3; // 0x3 + field public static final int RESULT_NONE = 1; // 0x1 + field public static final int RESULT_SUCCESS = 2; // 0x2 + field public static final int STATUS_FINISHED = 3; // 0x3 + field public static final int STATUS_PAUSED = 4; // 0x4 + field public static final int STATUS_PENDING = 1; // 0x1 + field public static final int STATUS_RUNNING = 2; // 0x2 + } + + @java.lang.FunctionalInterface public static interface MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener { + method public void onProgressUpdate(@IntRange(from=0, to=100) int); + } + + public static final class MediaTranscodeManager.TranscodingRequest { + method @NonNull public android.net.Uri getDestinationUri(); + method public int getPriority(); + method @NonNull public android.net.Uri getSourceUri(); + method public int getType(); + method @Nullable public android.media.MediaFormat getVideoTrackFormat(); + } + + public static final class MediaTranscodeManager.TranscodingRequest.Builder { + ctor public MediaTranscodeManager.TranscodingRequest.Builder(); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest build(); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setDestinationUri(@NonNull android.net.Uri); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setPriority(int); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setSourceUri(@NonNull android.net.Uri); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setType(int); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setVideoTrackFormat(@NonNull android.media.MediaFormat); + } + public class PlayerProxy { method public void pause(); method public void setPan(float); diff --git a/api/test-current.txt b/api/test-current.txt index 5741fe7e90a49..2dd7409242757 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1819,6 +1819,60 @@ package android.media { method @NonNull public String getOriginalId(); } + public final class MediaTranscodeManager implements java.lang.AutoCloseable { + method public void close(); + method @NonNull public android.media.MediaTranscodeManager.TranscodingJob enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener) throws java.io.FileNotFoundException; + method protected void finalize(); + field public static final int PRIORITY_OFFLINE = 2; // 0x2 + field public static final int PRIORITY_REALTIME = 1; // 0x1 + field public static final int TRANSCODING_TYPE_VIDEO = 1; // 0x1 + } + + @java.lang.FunctionalInterface public static interface MediaTranscodeManager.OnTranscodingFinishedListener { + method public void onTranscodingFinished(@NonNull android.media.MediaTranscodeManager.TranscodingJob); + } + + public static final class MediaTranscodeManager.TranscodingJob { + method public void cancel(); + method public int getJobId(); + method @IntRange(from=0, to=100) public int getProgress(); + method public int getResult(); + method public int getStatus(); + method public boolean retry(); + method public void setOnProgressUpdateListener(@NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener); + method public void setOnProgressUpdateListener(int, @NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener); + field public static final int RESULT_CANCELED = 4; // 0x4 + field public static final int RESULT_ERROR = 3; // 0x3 + field public static final int RESULT_NONE = 1; // 0x1 + field public static final int RESULT_SUCCESS = 2; // 0x2 + field public static final int STATUS_FINISHED = 3; // 0x3 + field public static final int STATUS_PAUSED = 4; // 0x4 + field public static final int STATUS_PENDING = 1; // 0x1 + field public static final int STATUS_RUNNING = 2; // 0x2 + } + + @java.lang.FunctionalInterface public static interface MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener { + method public void onProgressUpdate(@IntRange(from=0, to=100) int); + } + + public static final class MediaTranscodeManager.TranscodingRequest { + method @NonNull public android.net.Uri getDestinationUri(); + method public int getPriority(); + method @NonNull public android.net.Uri getSourceUri(); + method public int getType(); + method @Nullable public android.media.MediaFormat getVideoTrackFormat(); + } + + public static final class MediaTranscodeManager.TranscodingRequest.Builder { + ctor public MediaTranscodeManager.TranscodingRequest.Builder(); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest build(); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setDestinationUri(@NonNull android.net.Uri); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setPriority(int); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setSourceUri(@NonNull android.net.Uri); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setType(int); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setVideoTrackFormat(@NonNull android.media.MediaFormat); + } + public final class PlaybackParams implements android.os.Parcelable { method public int getAudioStretchMode(); method public android.media.PlaybackParams setAudioStretchMode(int); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 59997ccab687d..3b11b0d80da5d 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -103,6 +103,7 @@ import android.location.LocationManager; import android.media.AudioManager; import android.media.MediaFrameworkInitializer; import android.media.MediaRouter; +import android.media.MediaTranscodeManager; import android.media.midi.IMidiManager; import android.media.midi.MidiManager; import android.media.projection.MediaProjectionManager; @@ -305,6 +306,15 @@ public final class SystemServiceRegistry { return new AudioManager(ctx); }}); + registerService(Context.MEDIA_TRANSCODING_SERVICE, MediaTranscodeManager.class, + new CachedServiceFetcher() { + @Override + public MediaTranscodeManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + return new MediaTranscodeManager(ctx); + } + }); + registerService(Context.MEDIA_ROUTER_SERVICE, MediaRouter.class, new CachedServiceFetcher() { @Override diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 52b04675b7a50..98f78878ec153 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4194,6 +4194,17 @@ public abstract class Context { */ public static final String AUDIO_SERVICE = "audio"; + /** + * Use with {@link #getSystemService(String)} to retrieve a {@link + * android.media.MediaTranscodeManager} for transcoding media. + * + * @hide + * @see #getSystemService(String) + * @see android.media.MediaTranscodeManager + */ + @SystemApi + public static final String MEDIA_TRANSCODING_SERVICE = "media_transcoding"; + /** * AuthService orchestrates biometric and PIN/pattern/password authentication. * diff --git a/media/java/android/media/MediaTranscodeManager.java b/media/java/android/media/MediaTranscodeManager.java index 4e2ae5c471f42..cf61152c4775e 100644 --- a/media/java/android/media/MediaTranscodeManager.java +++ b/media/java/android/media/MediaTranscodeManager.java @@ -18,8 +18,11 @@ package android.media; import android.annotation.CallbackExecutor; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; @@ -97,6 +100,8 @@ import java.util.concurrent.Executors; TODO(hkuang): Clarify whether supports framerate conversion. @hide */ +@TestApi +@SystemApi public final class MediaTranscodeManager implements AutoCloseable { private static final String TAG = "MediaTranscodeManager"; @@ -127,20 +132,23 @@ public final class MediaTranscodeManager implements AutoCloseable { public static final int TRANSCODING_TYPE_IMAGE = 2; @Override - public void close() throws Exception { + public void close() { release(); } /** * Releases the MediaTranscodeManager. */ - //TODO(hkuang): add test for it. - private void release() throws Exception { + private void release() { synchronized (mLock) { - if (mTranscodingClient != null) { - mTranscodingClient.unregister(); - } else { - throw new UnsupportedOperationException("Failed to release"); + try { + if (mTranscodingClient != null) { + mTranscodingClient.unregister(); + } + } catch (Exception ex) { + Log.e(TAG, "Failed to unregister the client"); + } finally { + mTranscodingClient = null; } } } @@ -482,24 +490,22 @@ public final class MediaTranscodeManager implements AutoCloseable { throw new UnsupportedOperationException("Failed to register new client"); } - /* Private constructor. */ - private MediaTranscodeManager(@NonNull Context context, - IMediaTranscodingService transcodingService) { + /** + * @hide + */ + public MediaTranscodeManager(@NonNull Context context) { mContext = context; mContentResolver = mContext.getContentResolver(); mPackageName = mContext.getPackageName(); mPid = Os.getuid(); mUid = Os.getpid(); - mTranscodingClient = registerClient(transcodingService); + IMediaTranscodingService service = getService(false /*retry*/); + mTranscodingClient = registerClient(service); } @Override protected void finalize() { - try { - release(); - } catch (Exception ex) { - Log.e(TAG, "Failed to release"); - } + release(); } public static final class TranscodingRequest { @@ -555,25 +561,25 @@ public final class MediaTranscodeManager implements AutoCloseable { /** Return the type of the transcoding. */ @TranscodingType - int getType() { + public int getType() { return mType; } /** Return source uri of the transcoding. */ @NonNull - Uri getSourceUri() { + public Uri getSourceUri() { return mSourceUri; } /** Return destination uri of the transcoding. */ @NonNull - Uri getDestinationUri() { + public Uri getDestinationUri() { return mDestinationUri; } /** Return priority of the transcoding. */ @TranscodingPriority - int getPriority() { + public int getPriority() { return mPriority; } @@ -581,10 +587,20 @@ public final class MediaTranscodeManager implements AutoCloseable { * Return the video track format of the transcoding. * This will be null is the transcoding is not for video transcoding. */ - MediaFormat getVideoTrackFormat() { + @Nullable + public MediaFormat getVideoTrackFormat() { return mVideoTrackFormat; } + /** + * Return TestConfig of the transcoding. + * @hide + */ + @Nullable + public TranscodingTestConfig getTestConfig() { + return mTestConfig; + } + /* Writes the TranscodingRequest to a parcel. */ private TranscodingRequestParcel writeToParcel() { TranscodingRequestParcel parcel = new TranscodingRequestParcel(); @@ -665,7 +681,7 @@ public final class MediaTranscodeManager implements AutoCloseable { * Builder class for {@link TranscodingRequest} objects. * Use this class to configure and create a TranscodingRequest instance. */ - public static class Builder { + public static final class Builder { private @NonNull Uri mSourceUri; private @NonNull Uri mDestinationUri; private @TranscodingType int mType = TRANSCODING_TYPE_UNKNOWN; @@ -684,7 +700,7 @@ public final class MediaTranscodeManager implements AutoCloseable { */ // TODO(hkuang): Add documentation on how the app could generate the correct Uri. @NonNull - public Builder setSourceUri(@NonNull Uri sourceUri) throws IllegalArgumentException { + public Builder setSourceUri(@NonNull Uri sourceUri) { if (sourceUri == null || Uri.EMPTY.equals(sourceUri)) { throw new IllegalArgumentException( "You must specify a non-empty source Uri."); @@ -701,8 +717,7 @@ public final class MediaTranscodeManager implements AutoCloseable { * @throws IllegalArgumentException if Uri is null or empty. */ @NonNull - public Builder setDestinationUri(@NonNull Uri destinationUri) - throws IllegalArgumentException { + public Builder setDestinationUri(@NonNull Uri destinationUri) { if (destinationUri == null || Uri.EMPTY.equals(destinationUri)) { throw new IllegalArgumentException( "You must specify a non-empty destination Uri."); @@ -719,8 +734,7 @@ public final class MediaTranscodeManager implements AutoCloseable { * @throws IllegalArgumentException if flags is invalid. */ @NonNull - public Builder setPriority(@TranscodingPriority int priority) - throws IllegalArgumentException { + public Builder setPriority(@TranscodingPriority int priority) { if (priority != PRIORITY_OFFLINE && priority != PRIORITY_REALTIME) { throw new IllegalArgumentException("Invalid priority: " + priority); } @@ -738,8 +752,7 @@ public final class MediaTranscodeManager implements AutoCloseable { * @throws IllegalArgumentException if flags is invalid. */ @NonNull - public Builder setType(@TranscodingType int type) - throws IllegalArgumentException { + public Builder setType(@TranscodingType int type) { if (type != TRANSCODING_TYPE_VIDEO && type != TRANSCODING_TYPE_IMAGE) { throw new IllegalArgumentException("Invalid transcoding type"); } @@ -763,8 +776,7 @@ public final class MediaTranscodeManager implements AutoCloseable { * @throws IllegalArgumentException if videoFormat is invalid. */ @NonNull - public Builder setVideoTrackFormat(@NonNull MediaFormat videoFormat) - throws IllegalArgumentException { + public Builder setVideoTrackFormat(@NonNull MediaFormat videoFormat) { if (videoFormat == null) { throw new IllegalArgumentException("videoFormat must not be null"); } @@ -784,9 +796,11 @@ public final class MediaTranscodeManager implements AutoCloseable { * Sets the delay in processing this request. * @param config test config. * @return The same builder instance. + * @hide */ @VisibleForTesting - public Builder setTestConfig(TranscodingTestConfig config) { + @NonNull + public Builder setTestConfig(@NonNull TranscodingTestConfig config) { mTestConfig = config; return this; } @@ -799,7 +813,7 @@ public final class MediaTranscodeManager implements AutoCloseable { * device. */ @NonNull - public TranscodingRequest build() throws UnsupportedOperationException { + public TranscodingRequest build() { if (mSourceUri == null) { throw new UnsupportedOperationException("Source URI must not be null"); } @@ -843,6 +857,7 @@ public final class MediaTranscodeManager implements AutoCloseable { /** The job is paused. */ public static final int STATUS_PAUSED = 4; + /** @hide */ @IntDef(prefix = { "STATUS_" }, value = { STATUS_PENDING, STATUS_RUNNING, @@ -861,6 +876,7 @@ public final class MediaTranscodeManager implements AutoCloseable { /** The job was canceled by the caller. */ public static final int RESULT_CANCELED = 4; + /** @hide */ @IntDef(prefix = { "RESULT_" }, value = { RESULT_NONE, RESULT_SUCCESS, @@ -878,24 +894,26 @@ public final class MediaTranscodeManager implements AutoCloseable { * where 0 means that the job has not yet started and 100 means that it has finished. * @param progress The new progress ranging from 0 ~ 100 inclusive. */ - void onProgressUpdate(int progress); + void onProgressUpdate(@IntRange(from = 0, to = 100) int progress); } private final ITranscodingClient mJobOwner; private final Executor mListenerExecutor; private final OnTranscodingFinishedListener mListener; private int mJobId = -1; - @GuardedBy("this") + // Lock for internal state. + private final Object mLock = new Object(); + @GuardedBy("mLock") private Executor mProgressUpdateExecutor = null; - @GuardedBy("this") + @GuardedBy("mLock") private OnProgressUpdateListener mProgressUpdateListener = null; - @GuardedBy("this") + @GuardedBy("mLock") private int mProgress = 0; - @GuardedBy("this") + @GuardedBy("mLock") private int mProgressUpdateInterval = 0; - @GuardedBy("this") + @GuardedBy("mLock") private @Status int mStatus = STATUS_PENDING; - @GuardedBy("this") + @GuardedBy("mLock") private @Result int mResult = RESULT_NONE; private TranscodingJob( @@ -932,20 +950,24 @@ public final class MediaTranscodeManager implements AutoCloseable { * @param executor The executor on which listener will be invoked. * @param listener The progress listener. */ - public synchronized void setOnProgressUpdateListener( + public void setOnProgressUpdateListener( int minProgressUpdateInterval, @NonNull @CallbackExecutor Executor executor, @Nullable OnProgressUpdateListener listener) { - Objects.requireNonNull(executor, "listenerExecutor must not be null"); - Objects.requireNonNull(listener, "listener must not be null"); - mProgressUpdateExecutor = executor; - mProgressUpdateListener = listener; + synchronized (mLock) { + Objects.requireNonNull(executor, "listenerExecutor must not be null"); + Objects.requireNonNull(listener, "listener must not be null"); + mProgressUpdateExecutor = executor; + mProgressUpdateListener = listener; + } } - private synchronized void updateStatusAndResult(@Status int jobStatus, + private void updateStatusAndResult(@Status int jobStatus, @Result int jobResult) { - mStatus = jobStatus; - mResult = jobResult; + synchronized (mLock) { + mStatus = jobStatus; + mResult = jobResult; + } } /** @@ -953,8 +975,10 @@ public final class MediaTranscodeManager implements AutoCloseable { * * @return true if successfully resubmit the job to the service. False otherwise. */ - public synchronized boolean retry() { - // TODO(hkuang): Implement this. + public boolean retry() { + synchronized (mLock) { + // TODO(hkuang): Implement this. + } return true; } @@ -963,38 +987,46 @@ public final class MediaTranscodeManager implements AutoCloseable { * If the job happened to finish before being canceled this call is effectively a no-op and * will not update the result in that case. */ - public synchronized void cancel() { - // Check if the job is finished already. - if (mStatus != STATUS_FINISHED) { - try { - mJobOwner.cancelJob(mJobId); - } catch (RemoteException re) { - //TODO(hkuang): Find out what to do if failing to cancel the job. - Log.e(TAG, "Failed to cancel the job due to exception: " + re); - } - mStatus = STATUS_FINISHED; - mResult = RESULT_CANCELED; + public void cancel() { + synchronized (mLock) { + // Check if the job is finished already. + if (mStatus != STATUS_FINISHED) { + try { + mJobOwner.cancelJob(mJobId); + } catch (RemoteException re) { + //TODO(hkuang): Find out what to do if failing to cancel the job. + Log.e(TAG, "Failed to cancel the job due to exception: " + re); + } + mStatus = STATUS_FINISHED; + mResult = RESULT_CANCELED; - // Notifies client the job is canceled. - mListenerExecutor.execute(() -> mListener.onTranscodingFinished(this)); + // Notifies client the job is canceled. + mListenerExecutor.execute(() -> mListener.onTranscodingFinished(this)); + } } } /** - * Gets the progress of the transcoding job. The progress is between 0 and 1, where 0 means - * that the job has not yet started and 1 means that it is finished. + * Gets the progress of the transcoding job. The progress is between 0 and 100, where 0 + * means that the job has not yet started and 100 means that it is finished. For the + * cancelled job, the progress will be the last updated progress before it is cancelled. * @return The progress. */ - public synchronized int getProgress() { - return mProgress; + @IntRange(from = 0, to = 100) + public int getProgress() { + synchronized (mLock) { + return mProgress; + } } /** * Gets the status of the transcoding job. * @return The status. */ - public synchronized @Status int getStatus() { - return mStatus; + public @Status int getStatus() { + synchronized (mLock) { + return mStatus; + } } /** @@ -1009,53 +1041,22 @@ public final class MediaTranscodeManager implements AutoCloseable { * Gets the result of the transcoding job. * @return The result. */ - public synchronized @Result int getResult() { - return mResult; - } - - private synchronized void updateProgress(int newProgress) { - mProgress = newProgress; - } - - private synchronized void updateStatus(int newStatus) { - mStatus = newStatus; - } - } - - /** - * Gets the MediaTranscodeManager singleton instance. - * - * @param context The application context. - * @return the {@link MediaTranscodeManager} singleton instance. - * @throws UnsupportedOperationException if failing to acquire the MediaTranscodeManager. - */ - public static MediaTranscodeManager getInstance(@NonNull Context context) { - // Acquires the MediaTranscoding service. - IMediaTranscodingService service = getService(false /*retry*/); - return getInstance(context, service); - } - - /** Similar as above, but wait till the service is ready. */ - @VisibleForTesting - public static MediaTranscodeManager getInstance(@NonNull Context context, boolean retry) { - // Acquires the MediaTranscoding service. - IMediaTranscodingService service = getService(retry); - return getInstance(context, service); - } - - /** Similar as above, but allow injecting transcodingService for testing. */ - @VisibleForTesting - public static MediaTranscodeManager getInstance(@NonNull Context context, - IMediaTranscodingService transcodingService) { - Objects.requireNonNull(context, "context must not be null"); - - synchronized (MediaTranscodeManager.class) { - if (sMediaTranscodeManager == null) { - sMediaTranscodeManager = new MediaTranscodeManager(context.getApplicationContext(), - transcodingService); + public @Result int getResult() { + synchronized (mLock) { + return mResult; } + } - return sMediaTranscodeManager; + private void updateProgress(int newProgress) { + synchronized (mLock) { + mProgress = newProgress; + } + } + + private void updateStatus(int newStatus) { + synchronized (mLock) { + mStatus = newStatus; + } } } @@ -1077,7 +1078,7 @@ public final class MediaTranscodeManager implements AutoCloseable { @NonNull TranscodingRequest transcodingRequest, @NonNull @CallbackExecutor Executor listenerExecutor, @NonNull OnTranscodingFinishedListener listener) - throws UnsupportedOperationException, FileNotFoundException { + throws FileNotFoundException { Log.i(TAG, "enqueueRequest called."); Objects.requireNonNull(transcodingRequest, "transcodingRequest must not be null"); Objects.requireNonNull(listenerExecutor, "listenerExecutor must not be null"); @@ -1092,8 +1093,14 @@ public final class MediaTranscodeManager implements AutoCloseable { // Synchronizes the access to mPendingTranscodingJobs to make sure the job Id is // inserted in the mPendingTranscodingJobs in the callback handler. synchronized (mPendingTranscodingJobs) { - if (!mTranscodingClient.submitRequest(requestParcel, jobParcel)) { - throw new UnsupportedOperationException("Failed to enqueue request"); + synchronized (mLock) { + if (mTranscodingClient == null) { + // TODO(hkuang): Handle the case if client is temporarily unavailable. + } + + if (!mTranscodingClient.submitRequest(requestParcel, jobParcel)) { + throw new UnsupportedOperationException("Failed to enqueue request"); + } } // Wraps the TranscodingJobParcel into a TranscodingJob and returns it to client for diff --git a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java index 009a41ee004a7..a707c7a7955b0 100644 --- a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java +++ b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java @@ -69,6 +69,13 @@ public class MediaTranscodeManagerTest private static final String TAG = "MediaTranscodeManagerTest"; /** The time to wait for the transcode operation to complete before failing the test. */ private static final int TRANSCODE_TIMEOUT_SECONDS = 10; + + /** Maximum number of retry to connect to the service. */ + private static final int CONNECT_SERVICE_RETRY_COUNT = 100; + + /** Interval between trying to reconnect to the service. */ + private static final int INTERVAL_CONNECT_SERVICE_RETRY_MS = 40; + private Context mContext; private MediaTranscodeManager mMediaTranscodeManager = null; private Uri mSourceHEVCVideoUri = null; @@ -129,13 +136,31 @@ public class MediaTranscodeManagerTest return format; } + private MediaTranscodeManager getManager() { + for (int count = 1; count <= CONNECT_SERVICE_RETRY_COUNT; count++) { + Log.d(TAG, "Trying to connect to service. Try count: " + count); + MediaTranscodeManager manager = mContext.getSystemService(MediaTranscodeManager.class); + if (manager != null) { + return manager; + } + try { + // Sleep a bit before retry. + Thread.sleep(INTERVAL_CONNECT_SERVICE_RETRY_MS); + } catch (InterruptedException ie) { + /* ignore */ + } + } + + throw new UnsupportedOperationException("Failed to acquire MediaTranscodeManager"); + } + @Override public void setUp() throws Exception { Log.d(TAG, "setUp"); super.setUp(); mContext = getInstrumentation().getContext(); - mMediaTranscodeManager = MediaTranscodeManager.getInstance(mContext, true /*retry*/); + mMediaTranscodeManager = getManager(); assertNotNull(mMediaTranscodeManager); androidx.test.InstrumentationRegistry.registerInstance(getInstrumentation(), new Bundle()); diff --git a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerWithMockServiceTest.java b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerWithMockServiceTest.java index 748c21acebabc..167e474eb1437 100644 --- a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerWithMockServiceTest.java +++ b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerWithMockServiceTest.java @@ -259,7 +259,7 @@ public class MediaTranscodeManagerWithMockServiceTest super.setUp(); mTranscodingService = new MockTranscodingService(); mContext = getInstrumentation().getContext(); - mMediaTranscodeManager = MediaTranscodeManager.getInstance(mContext, mTranscodingService); + mMediaTranscodeManager = mContext.getSystemService(MediaTranscodeManager.class); assertNotNull(mMediaTranscodeManager); // Setup source HEVC file uri. diff --git a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java index 5c87d30068b26..04909efd06b04 100644 --- a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java +++ b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java @@ -89,7 +89,7 @@ public class MediaTranscodingBenchmark Log.d(TAG, "setUp"); super.setUp(); mContext = getInstrumentation().getContext(); - mMediaTranscodeManager = MediaTranscodeManager.getInstance(mContext); + mMediaTranscodeManager = mContext.getSystemService(MediaTranscodeManager.class); } @Override diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt index 7cce0f2f4b3e0..f64be2b1ff037 100644 --- a/non-updatable-api/system-current.txt +++ b/non-updatable-api/system-current.txt @@ -1681,6 +1681,7 @@ package android.content { field public static final String ETHERNET_SERVICE = "ethernet"; field public static final String EUICC_CARD_SERVICE = "euicc_card"; field public static final String HDMI_CONTROL_SERVICE = "hdmi_control"; + field public static final String MEDIA_TRANSCODING_SERVICE = "media_transcoding"; field public static final String NETD_SERVICE = "netd"; field public static final String NETWORK_SCORE_SERVICE = "network_score"; field public static final String OEM_LOCK_SERVICE = "oem_lock"; @@ -4273,6 +4274,60 @@ package android.media { field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int RADIO_TUNER = 1998; // 0x7ce } + public final class MediaTranscodeManager implements java.lang.AutoCloseable { + method public void close(); + method @NonNull public android.media.MediaTranscodeManager.TranscodingJob enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener) throws java.io.FileNotFoundException; + method protected void finalize(); + field public static final int PRIORITY_OFFLINE = 2; // 0x2 + field public static final int PRIORITY_REALTIME = 1; // 0x1 + field public static final int TRANSCODING_TYPE_VIDEO = 1; // 0x1 + } + + @java.lang.FunctionalInterface public static interface MediaTranscodeManager.OnTranscodingFinishedListener { + method public void onTranscodingFinished(@NonNull android.media.MediaTranscodeManager.TranscodingJob); + } + + public static final class MediaTranscodeManager.TranscodingJob { + method public void cancel(); + method public int getJobId(); + method @IntRange(from=0, to=100) public int getProgress(); + method public int getResult(); + method public int getStatus(); + method public boolean retry(); + method public void setOnProgressUpdateListener(@NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener); + method public void setOnProgressUpdateListener(int, @NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener); + field public static final int RESULT_CANCELED = 4; // 0x4 + field public static final int RESULT_ERROR = 3; // 0x3 + field public static final int RESULT_NONE = 1; // 0x1 + field public static final int RESULT_SUCCESS = 2; // 0x2 + field public static final int STATUS_FINISHED = 3; // 0x3 + field public static final int STATUS_PAUSED = 4; // 0x4 + field public static final int STATUS_PENDING = 1; // 0x1 + field public static final int STATUS_RUNNING = 2; // 0x2 + } + + @java.lang.FunctionalInterface public static interface MediaTranscodeManager.TranscodingJob.OnProgressUpdateListener { + method public void onProgressUpdate(@IntRange(from=0, to=100) int); + } + + public static final class MediaTranscodeManager.TranscodingRequest { + method @NonNull public android.net.Uri getDestinationUri(); + method public int getPriority(); + method @NonNull public android.net.Uri getSourceUri(); + method public int getType(); + method @Nullable public android.media.MediaFormat getVideoTrackFormat(); + } + + public static final class MediaTranscodeManager.TranscodingRequest.Builder { + ctor public MediaTranscodeManager.TranscodingRequest.Builder(); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest build(); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setDestinationUri(@NonNull android.net.Uri); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setPriority(int); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setSourceUri(@NonNull android.net.Uri); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setType(int); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setVideoTrackFormat(@NonNull android.media.MediaFormat); + } + public class PlayerProxy { method public void pause(); method public void setPan(float);