diff --git a/api/current.txt b/api/current.txt index 922e89c3efd9f..592a909f7e14c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -27325,6 +27325,12 @@ package android.media { field public static final android.media.MediaTimestamp TIMESTAMP_UNKNOWN; } + public class MediaTranscodingException extends java.lang.Exception { + } + + public static final class MediaTranscodingException.ServiceNotAvailableException extends android.media.MediaTranscodingException { + } + public interface MicrophoneDirection { method public boolean setPreferredMicrophoneDirection(int); method public boolean setPreferredMicrophoneFieldDimension(@FloatRange(from=-1.0, to=1.0) float); diff --git a/api/system-current.txt b/api/system-current.txt index b8fa299b01eb7..e6adcd0422b85 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4363,7 +4363,7 @@ package android.media { } public final class MediaTranscodeManager { - 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 @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, android.media.MediaTranscodingException.ServiceNotAvailableException; field public static final int PRIORITY_REALTIME = 1; // 0x1 field public static final int TRANSCODING_TYPE_VIDEO = 1; // 0x1 } @@ -4378,7 +4378,6 @@ package android.media { 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 diff --git a/api/test-current.txt b/api/test-current.txt index d57269e48e645..9698ab65f6d0e 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1817,7 +1817,7 @@ package android.media { } public final class MediaTranscodeManager { - 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 @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, android.media.MediaTranscodingException.ServiceNotAvailableException; field public static final int PRIORITY_REALTIME = 1; // 0x1 field public static final int TRANSCODING_TYPE_VIDEO = 1; // 0x1 } @@ -1832,7 +1832,6 @@ package android.media { 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 diff --git a/media/java/android/media/MediaTranscodeManager.java b/media/java/android/media/MediaTranscodeManager.java index 451677f6a8bcb..e092411375410 100644 --- a/media/java/android/media/MediaTranscodeManager.java +++ b/media/java/android/media/MediaTranscodeManager.java @@ -274,7 +274,7 @@ public final class MediaTranscodeManager { private static IMediaTranscodingService getService(boolean retry) { int retryCount = !retry ? 1 : CONNECT_SERVICE_RETRY_COUNT; - Log.i(TAG, "get service with rety " + retryCount); + Log.i(TAG, "get service with retry " + retryCount); for (int count = 1; count <= retryCount; count++) { Log.d(TAG, "Trying to connect to service. Try count: " + count); IMediaTranscodingService service = IMediaTranscodingService.Stub.asInterface( @@ -356,7 +356,15 @@ public final class MediaTranscodeManager { for (TranscodingJob job : retryJobs) { // Notify the job failure if we fails to connect to the service or fail // to retry the job. - if (!haveTranscodingClient || !job.retry()) { + if (!haveTranscodingClient) { + // TODO(hkuang): Return correct error code to the client. + handleTranscodingFailed(job.getJobId(), 0 /*unused */); + } + + try { + // Do not set hasRetried for retry initiated by MediaTranscodeManager. + job.retryInternal(false /*setHasRetried*/); + } catch (Exception re) { // TODO(hkuang): Return correct error code to the client. handleTranscodingFailed(job.getJobId(), 0 /*unused */); } @@ -1010,9 +1018,9 @@ public final class MediaTranscodeManager { @IntRange(from = 0, to = 100) int progress); } - private final ITranscodingClient mJobOwner; - private final Executor mListenerExecutor; - private final OnTranscodingFinishedListener mListener; + private final MediaTranscodeManager mManager; + private Executor mListenerExecutor; + private OnTranscodingFinishedListener mListener; private int mJobId = -1; // Lock for internal state. private final Object mLock = new Object(); @@ -1028,20 +1036,26 @@ public final class MediaTranscodeManager { private @Status int mStatus = STATUS_PENDING; @GuardedBy("mLock") private @Result int mResult = RESULT_NONE; + @GuardedBy("mLock") + private boolean mHasRetried = false; + // The original request that associated with this job. + private final TranscodingRequest mRequest; private TranscodingJob( - @NonNull ITranscodingClient jobOwner, + @NonNull MediaTranscodeManager manager, + @NonNull TranscodingRequest request, @NonNull TranscodingJobParcel parcel, @NonNull @CallbackExecutor Executor executor, @NonNull OnTranscodingFinishedListener listener) { - Objects.requireNonNull(jobOwner, "JobOwner must not be null"); - Objects.requireNonNull(parcel, "TranscodingJobParcel must not be null"); + Objects.requireNonNull(manager, "manager must not be null"); + Objects.requireNonNull(parcel, "parcel must not be null"); Objects.requireNonNull(executor, "listenerExecutor must not be null"); Objects.requireNonNull(listener, "listener must not be null"); - mJobOwner = jobOwner; + mManager = manager; mJobId = parcel.jobId; mListenerExecutor = executor; mListener = listener; + mRequest = request; } /** @@ -1085,14 +1099,61 @@ public final class MediaTranscodeManager { /** * Resubmit the transcoding job to the service. + * Note that only the job that fails or gets cancelled could be retried and each job could + * be retried only once. After that, Client need to enqueue a new request if they want to + * try again. * - * @return true if successfully resubmit the job to the service. False otherwise. + * @throws MediaTranscodingException.ServiceNotAvailableException if the service + * is temporarily unavailable due to internal service rebooting. Client could retry + * again after receiving this exception. + * @throws UnsupportedOperationException if the retry could not be fulfilled. + * @hide */ - public boolean retry() { + public void retry() throws MediaTranscodingException.ServiceNotAvailableException { + retryInternal(true /*setHasRetried*/); + } + + // TODO(hkuang): Add more test for it. + private void retryInternal(boolean setHasRetried) + throws MediaTranscodingException.ServiceNotAvailableException { synchronized (mLock) { - // TODO(hkuang): Implement this. + if (mStatus == STATUS_PENDING || mStatus == STATUS_RUNNING) { + throw new UnsupportedOperationException( + "Failed to retry as job is in processing"); + } + + if (mHasRetried) { + throw new UnsupportedOperationException("Job has been retried already"); + } + + // Get the client interface. + ITranscodingClient client = mManager.getTranscodingClient(); + if (client == null) { + throw new MediaTranscodingException.ServiceNotAvailableException( + "Service rebooting. Try again later"); + } + + synchronized (mManager.mPendingTranscodingJobs) { + try { + // Submits the request to MediaTranscoding service. + TranscodingJobParcel jobParcel = new TranscodingJobParcel(); + if (!client.submitRequest(mRequest.writeToParcel(), jobParcel)) { + mHasRetried = true; + throw new UnsupportedOperationException("Failed to enqueue request"); + } + + // Replace the old job id wit the new one. + mJobId = jobParcel.jobId; + // Adds the new job back into pending jobs. + mManager.mPendingTranscodingJobs.put(mJobId, this); + } catch (RemoteException re) { + throw new MediaTranscodingException.ServiceNotAvailableException( + "Failed to resubmit request to Transcoding service"); + } + mStatus = STATUS_PENDING; + mHasRetried = setHasRetried ? true : false; + } } - return true; } /** @@ -1105,7 +1166,11 @@ public final class MediaTranscodeManager { // Check if the job is finished already. if (mStatus != STATUS_FINISHED) { try { - mJobOwner.cancelJob(mJobId); + ITranscodingClient client = mManager.getTranscodingClient(); + // The client may be gone. + if (client != null) { + client.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); @@ -1173,6 +1238,12 @@ public final class MediaTranscodeManager { } } + private ITranscodingClient getTranscodingClient() { + synchronized (mLock) { + return mTranscodingClient; + } + } + /** * Enqueues a TranscodingRequest for execution. *
Upon successfully accepting the request, MediaTranscodeManager will return a @@ -1185,13 +1256,17 @@ public final class MediaTranscodeManager { * @return A TranscodingJob for this operation. * @throws FileNotFoundException if the source Uri or destination Uri could not be opened. * @throws UnsupportedOperationException if the request could not be fulfilled. + * @throws MediaTranscodingException.ServiceNotAvailableException if the service + * is temporarily unavailable due to internal service rebooting. Client could retry + * again after receiving this exception. */ @NonNull public TranscodingJob enqueueRequest( @NonNull TranscodingRequest transcodingRequest, @NonNull @CallbackExecutor Executor listenerExecutor, @NonNull OnTranscodingFinishedListener listener) - throws FileNotFoundException { + throws FileNotFoundException, + MediaTranscodingException.ServiceNotAvailableException { Log.i(TAG, "enqueueRequest called."); Objects.requireNonNull(transcodingRequest, "transcodingRequest must not be null"); Objects.requireNonNull(listenerExecutor, "listenerExecutor must not be null"); @@ -1208,7 +1283,14 @@ public final class MediaTranscodeManager { synchronized (mPendingTranscodingJobs) { synchronized (mLock) { if (mTranscodingClient == null) { - // TODO(hkuang): Handle the case if client is temporarily unavailable. + // Try to register with the service again. + IMediaTranscodingService service = getService(false /*retry*/); + mTranscodingClient = registerClient(service); + // If still fails, throws an exception to tell client to try later. + if (mTranscodingClient == null) { + throw new MediaTranscodingException.ServiceNotAvailableException( + "Service rebooting. Try again later"); + } } if (!mTranscodingClient.submitRequest(requestParcel, jobParcel)) { @@ -1218,7 +1300,8 @@ public final class MediaTranscodeManager { // Wraps the TranscodingJobParcel into a TranscodingJob and returns it to client for // tracking. - TranscodingJob job = new TranscodingJob(mTranscodingClient, jobParcel, + TranscodingJob job = new TranscodingJob(this, transcodingRequest, + jobParcel, listenerExecutor, listener); diff --git a/media/java/android/media/MediaTranscodingException.java b/media/java/android/media/MediaTranscodingException.java new file mode 100644 index 0000000000000..50cc9c4bae248 --- /dev/null +++ b/media/java/android/media/MediaTranscodingException.java @@ -0,0 +1,38 @@ +/* + * 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; + +/** + * Base class for MediaTranscoding exceptions + */ +public class MediaTranscodingException extends Exception { + private MediaTranscodingException(String detailMessage) { + super(detailMessage); + } + + /** + * Exception thrown when the service is rebooting and MediaTranscodeManager is temporarily + * unavailable for accepting new request. It's likely that retrying will be successful. + */ + public static final class ServiceNotAvailableException extends + MediaTranscodingException { + /** @hide */ + public ServiceNotAvailableException(String detailMessage) { + super(detailMessage); + } + } +} diff --git a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerDiedTest.java b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerDiedTest.java index f00c14d79bece..6b5abccccb8ba 100644 --- a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerDiedTest.java +++ b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerDiedTest.java @@ -22,6 +22,7 @@ import android.media.MediaFormat; import android.media.MediaTranscodeManager; import android.media.MediaTranscodeManager.TranscodingJob; import android.media.MediaTranscodeManager.TranscodingRequest; +import android.media.MediaTranscodingException; import android.net.Uri; import android.os.Bundle; import android.os.FileUtils; @@ -250,6 +251,27 @@ public class MediaTranscodeManagerDiedTest TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS); assertTrue("Invalid job status", job.getStatus() == TranscodingJob.STATUS_FINISHED); assertTrue("Invalid job result", job.getResult()== TranscodingJob.RESULT_ERROR); + + + boolean retryJob = false; + // Wait till service is available again. + Log.d(TAG, "Retry the failed transcoding job"); + while (!retryJob) { + try { + job.retry(); + // Break out when job retry succeeds. + break; + } catch (MediaTranscodingException.ServiceNotAvailableException ex) { + // Sleep for 10 milliseconds to wait. + Thread.sleep(10); + } + } + + finishedOnTime = transcodeCompleteSemaphore.tryAcquire( + TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS); + // Check the make sure job is successfully finished after retry. + assertTrue("Invalid job status", job.getStatus() == TranscodingJob.STATUS_FINISHED); + assertTrue("Invalid job result", job.getResult() == TranscodingJob.RESULT_SUCCESS); } } diff --git a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java index 73e1d98cb2e2b..10262d98d617d 100644 --- a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java +++ b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java @@ -22,6 +22,7 @@ import android.media.MediaFormat; import android.media.MediaTranscodeManager; import android.media.MediaTranscodeManager.TranscodingJob; import android.media.MediaTranscodeManager.TranscodingRequest; +import android.media.MediaTranscodingException; import android.net.Uri; import android.test.ActivityInstrumentationTestCase2; import android.util.Log; @@ -101,7 +102,8 @@ public class MediaTranscodingBenchmark * Transcode the sourceFileName to destinationFileName with LOOP_COUNT. */ private void transcode(final String sourceFileName, final String destinationFileName) - throws IOException, InterruptedException { + throws IOException, InterruptedException, + MediaTranscodingException.ServiceNotAvailableException { AtomicLong totalTimeMs = new AtomicLong(); AtomicLong transcodingTime = new AtomicLong(); Uri srcUri = getUri(sourceFileName); diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt index 8a2976fb9e852..5f147c93f9566 100644 --- a/non-updatable-api/current.txt +++ b/non-updatable-api/current.txt @@ -27138,6 +27138,12 @@ package android.media { field public static final android.media.MediaTimestamp TIMESTAMP_UNKNOWN; } + public class MediaTranscodingException extends java.lang.Exception { + } + + public static final class MediaTranscodingException.ServiceNotAvailableException extends android.media.MediaTranscodingException { + } + public interface MicrophoneDirection { method public boolean setPreferredMicrophoneDirection(int); method public boolean setPreferredMicrophoneFieldDimension(@FloatRange(from=-1.0, to=1.0) float); diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt index 38f745e95d223..88c228a82a54e 100644 --- a/non-updatable-api/system-current.txt +++ b/non-updatable-api/system-current.txt @@ -4303,7 +4303,7 @@ package android.media { } public final class MediaTranscodeManager { - 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 @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, android.media.MediaTranscodingException.ServiceNotAvailableException; field public static final int PRIORITY_REALTIME = 1; // 0x1 field public static final int TRANSCODING_TYPE_VIDEO = 1; // 0x1 } @@ -4318,7 +4318,6 @@ package android.media { 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