Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bfc5f1c9 authored by Hall Liu's avatar Hall Liu
Browse files

API council suggested edits, part 3

* MbmsException no longer thrown from the common methods, and the error
codes are now containined in MbmsError.
* In order to avoid ANRs while processing download-done broadcasts,
don't perform arbitrary moves when a download is done. Instead, move
into a staging directory and let the app handle it.

Bug: 30981736
Test: testapps
Change-Id: I9416f28b4f24f89af1da6f56a93ea0f0e4ea3878
parent 1a5b1304
Loading
Loading
Loading
Loading
+40 −55
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ import android.telephony.mbms.InternalDownloadSessionCallback;
import android.telephony.mbms.InternalDownloadStateCallback;
import android.telephony.mbms.MbmsDownloadSessionCallback;
import android.telephony.mbms.MbmsDownloadReceiver;
import android.telephony.mbms.MbmsException;
import android.telephony.mbms.MbmsErrors;
import android.telephony.mbms.MbmsTempFileProvider;
import android.telephony.mbms.MbmsUtils;
import android.telephony.mbms.vendor.IMbmsDownloadService;
@@ -98,13 +98,25 @@ public class MbmsDownloadSession implements AutoCloseable {
    /**
     * {@link Uri} extra that Android will attach to the intent supplied via
     * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
     * Indicates the location of the successfully
     * downloaded file. Will always be set to a non-null value if
     * Indicates the location of the successfully downloaded file within the temp file root set
     * via {@link #setTempFileRootDirectory(File)}.
     * While you may use this file in-place, it is highly encouraged that you move
     * this file to a different location after receiving the download completion intent, as this
     * file resides within the temp file directory.
     *
     * Will always be set to a non-null value if
     * {@link #EXTRA_MBMS_DOWNLOAD_RESULT} is set to {@link #RESULT_SUCCESSFUL}.
     */
    public static final String EXTRA_MBMS_COMPLETED_FILE_URI =
            "android.telephony.extra.MBMS_COMPLETED_FILE_URI";

    /**
     * Extra containing the {@link DownloadRequest} for which the download result or file
     * descriptor request is for. Must not be null.
     */
    public static final String EXTRA_MBMS_DOWNLOAD_REQUEST =
            "android.telephony.extra.MBMS_DOWNLOAD_REQUEST";

    /**
     * The default directory name for all MBMS temp files. If you call
     * {@link #download(DownloadRequest)} without first calling
@@ -176,7 +188,7 @@ public class MbmsDownloadSession implements AutoCloseable {
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification");
            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, "Received death notification");
        }
    };

@@ -243,7 +255,7 @@ public class MbmsDownloadSession implements AutoCloseable {
        MbmsDownloadSession session =
                new MbmsDownloadSession(context, callback, subscriptionId, handler);
        final int result = session.bindAndInitialize();
        if (result != MbmsException.SUCCESS) {
        if (result != MbmsErrors.SUCCESS) {
            sIsInitialized.set(false);
            handler.post(new Runnable() {
                @Override
@@ -273,12 +285,12 @@ public class MbmsDownloadSession implements AutoCloseable {
                        } catch (RuntimeException e) {
                            Log.e(LOG_TAG, "Runtime exception during initialization");
                            sendErrorToApp(
                                    MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
                                    MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
                                    e.toString());
                            sIsInitialized.set(false);
                            return;
                        }
                        if (result != MbmsException.SUCCESS) {
                        if (result != MbmsErrors.SUCCESS) {
                            sendErrorToApp(result, "Error returned during initialization");
                            sIsInitialized.set(false);
                            return;
@@ -286,7 +298,7 @@ public class MbmsDownloadSession implements AutoCloseable {
                        try {
                            downloadService.asBinder().linkToDeath(mDeathRecipient, 0);
                        } catch (RemoteException e) {
                            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST,
                            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST,
                                    "Middleware lost during initialization");
                            sIsInitialized.set(false);
                            return;
@@ -328,13 +340,13 @@ public class MbmsDownloadSession implements AutoCloseable {
        }
        try {
            int returnCode = downloadService.requestUpdateFileServices(mSubscriptionId, classList);
            if (returnCode != MbmsException.SUCCESS) {
            if (returnCode != MbmsErrors.SUCCESS) {
                sendErrorToApp(returnCode, null);
            }
        } catch (RemoteException e) {
            Log.w(LOG_TAG, "Remote process died");
            mService.set(null);
            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
        }
    }

@@ -353,7 +365,7 @@ public class MbmsDownloadSession implements AutoCloseable {
     * Before calling this method, the app must cancel all of its pending
     * {@link DownloadRequest}s via {@link #cancelDownload(DownloadRequest)}. If this is not done,
     * you will receive an asynchronous error with code
     * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} unless the
     * {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} unless the
     * provided directory is the same as what has been previously configured.
     *
     * The {@link File} supplied as a root temp file directory must already exist. If not, an
@@ -382,12 +394,12 @@ public class MbmsDownloadSession implements AutoCloseable {

        try {
            int result = downloadService.setTempFileRootDirectory(mSubscriptionId, filePath);
            if (result != MbmsException.SUCCESS) {
            if (result != MbmsErrors.SUCCESS) {
                sendErrorToApp(result, null);
            }
        } catch (RemoteException e) {
            mService.set(null);
            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
            return;
        }

@@ -465,13 +477,12 @@ public class MbmsDownloadSession implements AutoCloseable {
            setTempFileRootDirectory(tempRootDirectory);
        }

        checkValidDownloadDestination(request);
        writeDownloadRequestToken(request);
        try {
            downloadService.download(request);
        } catch (RemoteException e) {
            mService.set(null);
            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
        }
    }

@@ -492,7 +503,7 @@ public class MbmsDownloadSession implements AutoCloseable {
            return downloadService.listPendingDownloads(mSubscriptionId);
        } catch (RemoteException e) {
            mService.set(null);
            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
            return Collections.emptyList();
        }
    }
@@ -524,8 +535,8 @@ public class MbmsDownloadSession implements AutoCloseable {

        try {
            int result = downloadService.registerStateCallback(request, internalCallback);
            if (result != MbmsException.SUCCESS) {
                if (result == MbmsException.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
            if (result != MbmsErrors.SUCCESS) {
                if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
                    throw new IllegalArgumentException("Unknown download request.");
                }
                sendErrorToApp(result, null);
@@ -533,7 +544,7 @@ public class MbmsDownloadSession implements AutoCloseable {
            }
        } catch (RemoteException e) {
            mService.set(null);
            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
            return;
        }
        mInternalDownloadCallbacks.put(callback, internalCallback);
@@ -564,15 +575,15 @@ public class MbmsDownloadSession implements AutoCloseable {

            try {
                int result = downloadService.unregisterStateCallback(request, internalCallback);
                if (result != MbmsException.SUCCESS) {
                    if (result == MbmsException.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
                if (result != MbmsErrors.SUCCESS) {
                    if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
                        throw new IllegalArgumentException("Unknown download request.");
                    }
                    sendErrorToApp(result, null);
                }
            } catch (RemoteException e) {
                mService.set(null);
                sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
                sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
            }
        } finally {
            InternalDownloadStateCallback internalCallback =
@@ -599,8 +610,8 @@ public class MbmsDownloadSession implements AutoCloseable {

        try {
            int result = downloadService.cancelDownload(downloadRequest);
            if (result != MbmsException.SUCCESS) {
                if (result == MbmsException.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
            if (result != MbmsErrors.SUCCESS) {
                if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
                    throw new IllegalArgumentException("Unknown download request.");
                }
                sendErrorToApp(result, null);
@@ -608,7 +619,7 @@ public class MbmsDownloadSession implements AutoCloseable {
            }
        } catch (RemoteException e) {
            mService.set(null);
            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
            return;
        }
        deleteDownloadRequestToken(downloadRequest);
@@ -636,7 +647,7 @@ public class MbmsDownloadSession implements AutoCloseable {
            return downloadService.getDownloadStatus(downloadRequest, fileInfo);
        } catch (RemoteException e) {
            mService.set(null);
            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
            return STATUS_UNKNOWN;
        }
    }
@@ -669,15 +680,15 @@ public class MbmsDownloadSession implements AutoCloseable {

        try {
            int result = downloadService.resetDownloadKnowledge(downloadRequest);
            if (result != MbmsException.SUCCESS) {
                if (result == MbmsException.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
            if (result != MbmsErrors.SUCCESS) {
                if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
                    throw new IllegalArgumentException("Unknown download request.");
                }
                sendErrorToApp(result, null);
            }
        } catch (RemoteException e) {
            mService.set(null);
            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
        }
    }

@@ -753,32 +764,6 @@ public class MbmsDownloadSession implements AutoCloseable {
        return new File(tempFileLocation, downloadTokenFileName);
    }

    /**
     * Verifies the following:
     * If a request is multi-part,
     *     1. Destination Uri must exist and be a directory
     *     2. Directory specified must contain no files.
     * Otherwise
     *     1. The file specified by the destination Uri must not exist.
     */
    private void checkValidDownloadDestination(DownloadRequest request) {
        File toFile = new File(request.getDestinationUri().getSchemeSpecificPart());
        if (request.isMultipartDownload()) {
            if (!toFile.isDirectory()) {
                throw new IllegalArgumentException("Multipart download must specify valid " +
                        "destination directory.");
            }
            if (toFile.listFiles().length > 0) {
                throw new IllegalArgumentException("Destination directory must be clear of all " +
                        "files.");
            }
        } else {
            if (toFile.exists()) {
                throw new IllegalArgumentException("Destination file must not exist.");
            }
        }
    }

    private void sendErrorToApp(int errorCode, String message) {
        try {
            mInternalCallback.onError(errorCode, message);
+13 −13
Original line number Diff line number Diff line
@@ -29,7 +29,7 @@ import android.os.Looper;
import android.os.RemoteException;
import android.telephony.mbms.InternalStreamingSessionCallback;
import android.telephony.mbms.InternalStreamingServiceCallback;
import android.telephony.mbms.MbmsException;
import android.telephony.mbms.MbmsErrors;
import android.telephony.mbms.MbmsStreamingSessionCallback;
import android.telephony.mbms.MbmsUtils;
import android.telephony.mbms.StreamingService;
@@ -70,7 +70,7 @@ public class MbmsStreamingSession implements AutoCloseable {
        @Override
        public void binderDied() {
            sIsInitialized.set(false);
            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification");
            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, "Received death notification");
        }
    };

@@ -125,7 +125,7 @@ public class MbmsStreamingSession implements AutoCloseable {
                subscriptionId, handler);

        final int result = session.bindAndInitialize();
        if (result != MbmsException.SUCCESS) {
        if (result != MbmsErrors.SUCCESS) {
            sIsInitialized.set(false);
            handler.post(new Runnable() {
                @Override
@@ -204,14 +204,14 @@ public class MbmsStreamingSession implements AutoCloseable {
        try {
            int returnCode = streamingService.requestUpdateStreamingServices(
                    mSubscriptionId, serviceClassList);
            if (returnCode != MbmsException.SUCCESS) {
            if (returnCode != MbmsErrors.SUCCESS) {
                sendErrorToApp(returnCode, null);
            }
        } catch (RemoteException e) {
            Log.w(LOG_TAG, "Remote process died");
            mService.set(null);
            sIsInitialized.set(false);
            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
        }
    }

@@ -225,8 +225,8 @@ public class MbmsStreamingSession implements AutoCloseable {
     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
     *
     * Asynchronous errors through the callback include any of the errors in
     * {@link android.telephony.mbms.MbmsException.GeneralErrors} or
     * {@link android.telephony.mbms.MbmsException.StreamingErrors}.
     * {@link MbmsErrors.GeneralErrors} or
     * {@link MbmsErrors.StreamingErrors}.
     *
     * @param serviceInfo The information about the service to stream.
     * @param callback A callback that'll be called when something about the stream changes.
@@ -251,7 +251,7 @@ public class MbmsStreamingSession implements AutoCloseable {
        try {
            int returnCode = streamingService.startStreaming(
                    mSubscriptionId, serviceInfo.getServiceId(), serviceCallback);
            if (returnCode != MbmsException.SUCCESS) {
            if (returnCode != MbmsErrors.SUCCESS) {
                sendErrorToApp(returnCode, null);
                return null;
            }
@@ -259,7 +259,7 @@ public class MbmsStreamingSession implements AutoCloseable {
            Log.w(LOG_TAG, "Remote process died");
            mService.set(null);
            sIsInitialized.set(false);
            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
            return null;
        }

@@ -285,19 +285,19 @@ public class MbmsStreamingSession implements AutoCloseable {
                        } catch (RemoteException e) {
                            Log.e(LOG_TAG, "Service died before initialization");
                            sendErrorToApp(
                                    MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
                                    MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
                                    e.toString());
                            sIsInitialized.set(false);
                            return;
                        } catch (RuntimeException e) {
                            Log.e(LOG_TAG, "Runtime exception during initialization");
                            sendErrorToApp(
                                    MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
                                    MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
                                    e.toString());
                            sIsInitialized.set(false);
                            return;
                        }
                        if (result != MbmsException.SUCCESS) {
                        if (result != MbmsErrors.SUCCESS) {
                            sendErrorToApp(result, "Error returned during initialization");
                            sIsInitialized.set(false);
                            return;
@@ -305,7 +305,7 @@ public class MbmsStreamingSession implements AutoCloseable {
                        try {
                            streamingService.asBinder().linkToDeath(mDeathRecipient, 0);
                        } catch (RemoteException e) {
                            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST,
                            sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST,
                                    "Middleware lost during initialization");
                            sIsInitialized.set(false);
                            return;
+6 −41
Original line number Diff line number Diff line
@@ -56,12 +56,10 @@ public final class DownloadRequest implements Parcelable {

    /** @hide */
    private static class OpaqueDataContainer implements Serializable {
        private final String destinationUri;
        private final String appIntent;
        private final int version;

        public OpaqueDataContainer(String destinationUri, String appIntent, int version) {
            this.destinationUri = destinationUri;
        public OpaqueDataContainer(String appIntent, int version) {
            this.appIntent = appIntent;
            this.version = version;
        }
@@ -70,7 +68,6 @@ public final class DownloadRequest implements Parcelable {
    public static class Builder {
        private String fileServiceId;
        private Uri source;
        private Uri dest;
        private int subscriptionId;
        private String appIntent;
        private int version = CURRENT_VERSION;
@@ -104,21 +101,6 @@ public final class DownloadRequest implements Parcelable {
            return this;
        }

        /**
         * Sets the destination URI for the download request to be built. The middleware should
         * not set this directly.
         * @param dest A URI obtained from {@link Uri#fromFile(File)}, denoting the requested
         *             final destination of the download.
         */
        public Builder setDest(Uri dest) {
            if (dest.toString().length() > MAX_DESTINATION_URI_SIZE) {
                throw new IllegalArgumentException("Destination uri must not exceed length " +
                        MAX_DESTINATION_URI_SIZE);
            }
            this.dest = dest;
            return this;
        }

        /**
         * Set the subscription ID on which the file(s) should be downloaded.
         * @param subscriptionId
@@ -160,7 +142,6 @@ public final class DownloadRequest implements Parcelable {
                OpaqueDataContainer dataContainer = (OpaqueDataContainer) stream.readObject();
                version = dataContainer.version;
                appIntent = dataContainer.appIntent;
                dest = Uri.parse(dataContainer.destinationUri);
            } catch (IOException e) {
                // Really should never happen
                Log.e(LOG_TAG, "Got IOException trying to parse opaque data");
@@ -173,24 +154,21 @@ public final class DownloadRequest implements Parcelable {
        }

        public DownloadRequest build() {
            return new DownloadRequest(fileServiceId, source, dest,
                    subscriptionId, appIntent, version);
            return new DownloadRequest(fileServiceId, source, subscriptionId, appIntent, version);
        }
    }

    private final String fileServiceId;
    private final Uri sourceUri;
    private final Uri destinationUri;
    private final int subscriptionId;
    private final String serializedResultIntentForApp;
    private final int version;

    private DownloadRequest(String fileServiceId,
            Uri source, Uri dest,
            int sub, String appIntent, int version) {
            Uri source, int sub,
            String appIntent, int version) {
        this.fileServiceId = fileServiceId;
        sourceUri = source;
        destinationUri = dest;
        subscriptionId = sub;
        serializedResultIntentForApp = appIntent;
        this.version = version;
@@ -203,7 +181,6 @@ public final class DownloadRequest implements Parcelable {
    private DownloadRequest(DownloadRequest dr) {
        fileServiceId = dr.fileServiceId;
        sourceUri = dr.sourceUri;
        destinationUri = dr.destinationUri;
        subscriptionId = dr.subscriptionId;
        serializedResultIntentForApp = dr.serializedResultIntentForApp;
        version = dr.version;
@@ -212,7 +189,6 @@ public final class DownloadRequest implements Parcelable {
    private DownloadRequest(Parcel in) {
        fileServiceId = in.readString();
        sourceUri = in.readParcelable(getClass().getClassLoader());
        destinationUri = in.readParcelable(getClass().getClassLoader());
        subscriptionId = in.readInt();
        serializedResultIntentForApp = in.readString();
        version = in.readInt();
@@ -225,7 +201,6 @@ public final class DownloadRequest implements Parcelable {
    public void writeToParcel(Parcel out, int flags) {
        out.writeString(fileServiceId);
        out.writeParcelable(sourceUri, flags);
        out.writeParcelable(destinationUri, flags);
        out.writeInt(subscriptionId);
        out.writeString(serializedResultIntentForApp);
        out.writeInt(version);
@@ -245,14 +220,6 @@ public final class DownloadRequest implements Parcelable {
        return sourceUri;
    }

    /**
     * For use by the client app only.
     * @return The URI of the final destination of the download.
     */
    public Uri getDestinationUri() {
        return destinationUri;
    }

    /**
     * @return The subscription ID on which to perform MBMS operations.
     */
@@ -286,7 +253,7 @@ public final class DownloadRequest implements Parcelable {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream stream = new ObjectOutputStream(byteArrayOutputStream);
            OpaqueDataContainer container = new OpaqueDataContainer(
                    destinationUri.toString(), serializedResultIntentForApp, version);
                    serializedResultIntentForApp, version);
            stream.writeObject(container);
            stream.flush();
            return byteArrayOutputStream.toByteArray();
@@ -352,7 +319,6 @@ public final class DownloadRequest implements Parcelable {
        if (version >= 1) {
            // Hash the source URI, destination URI, and the app intent
            digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8));
            digest.update(destinationUri.toString().getBytes(StandardCharsets.UTF_8));
            digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
        }
        // Add updates for future versions here
@@ -373,13 +339,12 @@ public final class DownloadRequest implements Parcelable {
                version == request.version &&
                Objects.equals(fileServiceId, request.fileServiceId) &&
                Objects.equals(sourceUri, request.sourceUri) &&
                Objects.equals(destinationUri, request.destinationUri) &&
                Objects.equals(serializedResultIntentForApp, request.serializedResultIntentForApp);
    }

    @Override
    public int hashCode() {
        return Objects.hash(fileServiceId, sourceUri, destinationUri,
        return Objects.hash(fileServiceId, sourceUri,
                subscriptionId, serializedResultIntentForApp, version);
    }
}
+43 −58

File changed.

Preview size limit exceeded, changes collapsed.

+6 −5
Original line number Diff line number Diff line
@@ -29,7 +29,7 @@ public class MbmsDownloadSessionCallback {

    /**
     * Indicates that the middleware has encountered an asynchronous error.
     * @param errorCode Any error code listed in {@link MbmsException}
     * @param errorCode Any error code listed in {@link MbmsErrors}
     * @param message A message, intended for debugging purposes, describing the error in further
     *                detail.
     */
@@ -43,7 +43,8 @@ public class MbmsDownloadSessionCallback {
     * This will only be called after the application has requested a list of file services and
     * specified a service class list of interest via
     * {@link MbmsDownloadSession#requestUpdateFileServices(List)}. If there are subsequent calls to
     * {@link MbmsDownloadSession#requestUpdateFileServices(List)}, this method may not be called again if
     * {@link MbmsDownloadSession#requestUpdateFileServices(List)},
     * this method may not be called again if
     * the list of service classes would remain the same.
     *
     * @param services The most recently updated list of available file services.
@@ -56,9 +57,9 @@ public class MbmsDownloadSessionCallback {
     * Called to indicate that the middleware has been initialized and is ready.
     *
     * Before this method is called, calling any method on an instance of
     * {@link MbmsDownloadSession} will result in an {@link MbmsException}
     * being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
     * or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
     * {@link MbmsDownloadSession} will result in an {@link IllegalStateException}
     * being thrown or {@link #onError(int, String)} being called with error code
     * {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
     */
    public void onMiddlewareReady() {
        // default implementation empty
Loading