All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.android.telephony.mbms.vendor.MbmsDownloadServiceBase Maven / Gradle / Ivy

/*
 * Copyright (C) 2017 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.telephony.mbms.vendor;

import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.telephony.MbmsDownloadSession;
import android.telephony.mbms.DownloadProgressListener;
import android.telephony.mbms.DownloadRequest;
import android.telephony.mbms.DownloadStatusListener;
import android.telephony.mbms.FileInfo;
import android.telephony.mbms.FileServiceInfo;
import android.telephony.mbms.IDownloadProgressListener;
import android.telephony.mbms.IDownloadStatusListener;
import android.telephony.mbms.IMbmsDownloadSessionCallback;
import android.telephony.mbms.MbmsDownloadSessionCallback;
import android.telephony.mbms.MbmsErrors;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Base class for MbmsDownloadService. The middleware should return an instance of this object from
 * its {@link android.app.Service#onBind(Intent)} method.
 * @hide
 */
@SystemApi
public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
    private final Map mDownloadStatusListenerBinderMap =
            new HashMap<>();
    private final Map mDownloadProgressListenerBinderMap =
            new HashMap<>();
    private final Map mDownloadCallbackDeathRecipients = new HashMap<>();

    private abstract static class VendorDownloadStatusListener extends DownloadStatusListener {
        private final IDownloadStatusListener mListener;
        public VendorDownloadStatusListener(IDownloadStatusListener listener) {
            mListener = listener;
        }

        @Override
        public void onStatusUpdated(DownloadRequest request, FileInfo fileInfo,
                @MbmsDownloadSession.DownloadStatus int state) {
            try {
                mListener.onStatusUpdated(request, fileInfo, state);
            } catch (RemoteException e) {
                onRemoteException(e);
            }
        }

        protected abstract void onRemoteException(RemoteException e);
    }

    private abstract static class VendorDownloadProgressListener extends DownloadProgressListener {
        private final IDownloadProgressListener mListener;

        public VendorDownloadProgressListener(IDownloadProgressListener listener) {
            mListener = listener;
        }

        @Override
        public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo,
                int currentDownloadSize, int fullDownloadSize, int currentDecodedSize,
                int fullDecodedSize) {
            try {
                mListener.onProgressUpdated(request, fileInfo, currentDownloadSize,
                        fullDownloadSize, currentDecodedSize, fullDecodedSize);
            } catch (RemoteException e) {
                onRemoteException(e);
            }
        }

        protected abstract void onRemoteException(RemoteException e);
    }

    /**
     * Initialize the download service for this app and subId, registering the listener.
     *
     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}, which
     * will be intercepted and passed to the app as
     * {@link MbmsErrors.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
     *
     * May return any value from {@link MbmsErrors.InitializationErrors}
     * or {@link MbmsErrors#SUCCESS}. Non-successful error codes will be passed to the app via
     * {@link IMbmsDownloadSessionCallback#onError(int, String)}.
     *
     * @param callback The callback to use to communicate with the app.
     * @param subscriptionId The subscription ID to use.
     */
    public int initialize(int subscriptionId, MbmsDownloadSessionCallback callback)
            throws RemoteException {
        return 0;
    }

    /**
     * Actual AIDL implementation -- hides the callback AIDL from the API.
     * @hide
     */
    @Override
    public final int initialize(final int subscriptionId,
            final IMbmsDownloadSessionCallback callback) throws RemoteException {
        if (callback == null) {
            throw new NullPointerException("Callback must not be null");
        }

        final int uid = Binder.getCallingUid();

        int result = initialize(subscriptionId, new MbmsDownloadSessionCallback() {
            @Override
            public void onError(int errorCode, String message) {
                try {
                    if (errorCode == MbmsErrors.UNKNOWN) {
                        throw new IllegalArgumentException(
                                "Middleware cannot send an unknown error.");
                    }
                    callback.onError(errorCode, message);
                } catch (RemoteException e) {
                    onAppCallbackDied(uid, subscriptionId);
                }
            }

            @Override
            public void onFileServicesUpdated(List services) {
                try {
                    callback.onFileServicesUpdated(services);
                } catch (RemoteException e) {
                    onAppCallbackDied(uid, subscriptionId);
                }
            }

            @Override
            public void onMiddlewareReady() {
                try {
                    callback.onMiddlewareReady();
                } catch (RemoteException e) {
                    onAppCallbackDied(uid, subscriptionId);
                }
            }
        });

        if (result == MbmsErrors.SUCCESS) {
            callback.asBinder().linkToDeath(new DeathRecipient() {
                @Override
                public void binderDied() {
                    onAppCallbackDied(uid, subscriptionId);
                }
            }, 0);
        }

        return result;
    }

    /**
     * Registers serviceClasses of interest with the appName/subId key.
     * Starts async fetching data on streaming services of matching classes to be reported
     * later via {@link IMbmsDownloadSessionCallback#onFileServicesUpdated(List)}
     *
     * Note that subsequent calls with the same uid and subId will replace
     * the service class list.
     *
     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
     *
     * @param subscriptionId The subscription id to use.
     * @param serviceClasses The service classes that the app wishes to get info on. The strings
     *                       may contain arbitrary data as negotiated between the app and the
     *                       carrier.
     * @return One of {@link MbmsErrors#SUCCESS} or
     *         {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY},
     */
    @Override
    public int requestUpdateFileServices(int subscriptionId, List serviceClasses)
            throws RemoteException {
        return 0;
    }

    /**
     * Sets the temp file root directory for this app/subscriptionId combination. The middleware
     * should persist {@code rootDirectoryPath} and send it back when sending intents to the
     * app's {@link android.telephony.mbms.MbmsDownloadReceiver}.
     *
     * If the calling app (as identified by the calling UID) currently has any pending download
     * requests that have not been canceled, the middleware must return
     * {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} here.
     *
     * @param subscriptionId The subscription id the download is operating under.
     * @param rootDirectoryPath The path to the app's temp file root directory.
     * @return {@link MbmsErrors#SUCCESS},
     *         {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} or
     *         {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}
     */
    @Override
    public int setTempFileRootDirectory(int subscriptionId,
            String rootDirectoryPath) throws RemoteException {
        return 0;
    }

    /**
     * Called when the client application wishes to receive file information according to a
     * service announcement descriptor received from a group call server.
     *
     * The service announcement descriptor is in the format of a multipart MIME file with XML parts,
     * though no validation is performed on the contents of the {@code contents} argument --
     * implementing middleware applications should perform their own validation and return
     * {@link MbmsErrors.DownloadErrors#ERROR_MALFORMED_SERVICE_ANNOUNCEMENT} if the descriptor is
     * malformed.
     *
     * @param subscriptionId The subscription id the service announcement applies to.
     * @param contents The contents of the service announcement descriptor.
     * @return {@link MbmsErrors#SUCCESS}, or
     *         {@link MbmsErrors.DownloadErrors#ERROR_MALFORMED_SERVICE_ANNOUNCEMENT}
     */
    // TODO: are there any public specifications of what the file format is that I can link to?
    @Override
    public @MbmsErrors.MbmsError int addServiceAnnouncement(
            int subscriptionId, @NonNull byte[] contents) {
        throw new UnsupportedOperationException("addServiceAnnouncement not supported by"
                + " this middleware.");
    }

    /**
     * Issues a request to download a set of files.
     *
     * The middleware should expect that {@link #setTempFileRootDirectory(int, String)} has been
     * called for this app between when the app was installed and when this method is called. If
     * this is not the case, an {@link IllegalStateException} may be thrown.
     *
     * @param downloadRequest An object describing the set of files to be downloaded.
     * @return Any error from {@link MbmsErrors.GeneralErrors}
     *         or {@link MbmsErrors#SUCCESS}
     */
    @Override
    public int download(DownloadRequest downloadRequest) throws RemoteException {
        return 0;
    }

    /**
     * Registers a download status listener for the provided {@link DownloadRequest}.
     *
     * This method is called by the app when it wants to request updates on the status of
     * the download.
     *
     * If the middleware is not aware of a download having been requested with the provided
     * {@link DownloadRequest} in the past,
     * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
     * must be returned.
     *
     * @param downloadRequest The {@link DownloadRequest} that was used to initiate the download
     *                        for which progress updates are being requested.
     * @param listener The listener object to use.
     */
    public int addStatusListener(DownloadRequest downloadRequest,
            DownloadStatusListener listener) throws RemoteException {
        return 0;
    }

    /**
     * Actual AIDL implementation -- hides the listener AIDL from the API.
     * @hide
     */
    @Override
    public final int addStatusListener(final DownloadRequest downloadRequest,
            final IDownloadStatusListener listener) throws RemoteException {
        final int uid = Binder.getCallingUid();
        if (downloadRequest == null) {
            throw new NullPointerException("Download request must not be null");
        }
        if (listener == null) {
            throw new NullPointerException("Callback must not be null");
        }

        DownloadStatusListener exposedCallback = new VendorDownloadStatusListener(listener) {
            @Override
            protected void onRemoteException(RemoteException e) {
                onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
            }
        };

        int result = addStatusListener(downloadRequest, exposedCallback);

        if (result == MbmsErrors.SUCCESS) {
            DeathRecipient deathRecipient = new DeathRecipient() {
                @Override
                public void binderDied() {
                    onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
                    mDownloadStatusListenerBinderMap.remove(listener.asBinder());
                    mDownloadCallbackDeathRecipients.remove(listener.asBinder());
                }
            };
            mDownloadCallbackDeathRecipients.put(listener.asBinder(), deathRecipient);
            listener.asBinder().linkToDeath(deathRecipient, 0);
            mDownloadStatusListenerBinderMap.put(listener.asBinder(), exposedCallback);
        }

        return result;
    }

    /**
     * Un-registers a download status listener for the provided {@link DownloadRequest}.
     *
     * This method is called by the app when it no longer wants to request status updates on the
     * download.
     *
     * If the middleware is not aware of a download having been requested with the provided
     * {@link DownloadRequest} in the past,
     * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
     * must be returned.
     *
     * @param downloadRequest The {@link DownloadRequest} that was used to register the callback
     * @param listener The callback object that
     *                 {@link #addStatusListener(DownloadRequest, DownloadStatusListener)}
     *                 was called with.
     */
    public int removeStatusListener(DownloadRequest downloadRequest,
            DownloadStatusListener listener) throws RemoteException {
        return 0;
    }

    /**
     * Actual AIDL implementation -- hides the listener AIDL from the API.
     * @hide
     */
    public final int removeStatusListener(
            final DownloadRequest downloadRequest, final IDownloadStatusListener listener)
            throws RemoteException {
        if (downloadRequest == null) {
            throw new NullPointerException("Download request must not be null");
        }
        if (listener == null) {
            throw new NullPointerException("Callback must not be null");
        }

        DeathRecipient deathRecipient =
                mDownloadCallbackDeathRecipients.remove(listener.asBinder());
        if (deathRecipient == null) {
            throw new IllegalArgumentException("Unknown listener");
        }

        listener.asBinder().unlinkToDeath(deathRecipient, 0);

        DownloadStatusListener exposedCallback =
                mDownloadStatusListenerBinderMap.remove(listener.asBinder());
        if (exposedCallback == null) {
            throw new IllegalArgumentException("Unknown listener");
        }

        return removeStatusListener(downloadRequest, exposedCallback);
    }

    /**
     * Registers a download progress listener for the provided {@link DownloadRequest}.
     *
     * This method is called by the app when it wants to request updates on the progress of
     * the download.
     *
     * If the middleware is not aware of a download having been requested with the provided
     * {@link DownloadRequest} in the past,
     * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
     * must be returned.
     *
     * @param downloadRequest The {@link DownloadRequest} that was used to initiate the download
     *                        for which progress updates are being requested.
     * @param listener The listener object to use.
     */
    public int addProgressListener(DownloadRequest downloadRequest,
            DownloadProgressListener listener) throws RemoteException {
        return 0;
    }

    /**
     * Actual AIDL implementation -- hides the listener AIDL from the API.
     * @hide
     */
    @Override
    public final int addProgressListener(final DownloadRequest downloadRequest,
            final IDownloadProgressListener listener) throws RemoteException {
        final int uid = Binder.getCallingUid();
        if (downloadRequest == null) {
            throw new NullPointerException("Download request must not be null");
        }
        if (listener == null) {
            throw new NullPointerException("Callback must not be null");
        }

        DownloadProgressListener exposedCallback = new VendorDownloadProgressListener(listener) {
            @Override
            protected void onRemoteException(RemoteException e) {
                onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
            }
        };

        int result = addProgressListener(downloadRequest, exposedCallback);

        if (result == MbmsErrors.SUCCESS) {
            DeathRecipient deathRecipient = new DeathRecipient() {
                @Override
                public void binderDied() {
                    onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
                    mDownloadProgressListenerBinderMap.remove(listener.asBinder());
                    mDownloadCallbackDeathRecipients.remove(listener.asBinder());
                }
            };
            mDownloadCallbackDeathRecipients.put(listener.asBinder(), deathRecipient);
            listener.asBinder().linkToDeath(deathRecipient, 0);
            mDownloadProgressListenerBinderMap.put(listener.asBinder(), exposedCallback);
        }

        return result;
    }

    /**
     * Un-registers a download progress listener for the provided {@link DownloadRequest}.
     *
     * This method is called by the app when it no longer wants to request progress updates on the
     * download.
     *
     * If the middleware is not aware of a download having been requested with the provided
     * {@link DownloadRequest} in the past,
     * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
     * must be returned.
     *
     * @param downloadRequest The {@link DownloadRequest} that was used to register the callback
     * @param listener The callback object that
     *                 {@link #addProgressListener(DownloadRequest, DownloadProgressListener)}
     *                 was called with.
     */
    public int removeProgressListener(DownloadRequest downloadRequest,
            DownloadProgressListener listener) throws RemoteException {
        return 0;
    }

    /**
     * Actual AIDL implementation -- hides the listener AIDL from the API.
     * @hide
     */
    public final int removeProgressListener(
            final DownloadRequest downloadRequest, final IDownloadProgressListener listener)
            throws RemoteException {
        if (downloadRequest == null) {
            throw new NullPointerException("Download request must not be null");
        }
        if (listener == null) {
            throw new NullPointerException("Callback must not be null");
        }

        DeathRecipient deathRecipient =
                mDownloadCallbackDeathRecipients.remove(listener.asBinder());
        if (deathRecipient == null) {
            throw new IllegalArgumentException("Unknown listener");
        }

        listener.asBinder().unlinkToDeath(deathRecipient, 0);

        DownloadProgressListener exposedCallback =
                mDownloadProgressListenerBinderMap.remove(listener.asBinder());
        if (exposedCallback == null) {
            throw new IllegalArgumentException("Unknown listener");
        }

        return removeProgressListener(downloadRequest, exposedCallback);
    }

    /**
     * Returns a list of pending {@link DownloadRequest}s that originated from the calling
     * application, identified by its uid. A pending request is one that was issued via
     * {@link #download(DownloadRequest)} but not cancelled through
     * {@link #cancelDownload(DownloadRequest)}.
     * The middleware must return a non-null result synchronously or throw an exception
     * inheriting from {@link RuntimeException}.
     * @return A list, possibly empty, of {@link DownloadRequest}s
     */
    @Override
    public @NonNull List listPendingDownloads(int subscriptionId)
            throws RemoteException {
        return null;
    }

    /**
     * Issues a request to cancel the specified download request.
     *
     * If the middleware is unable to cancel the request for whatever reason, it should return
     * synchronously with an error. If this method returns {@link MbmsErrors#SUCCESS}, the app
     * will no longer be expecting any more file-completed intents from the middleware for this
     * {@link DownloadRequest}.
     * @param downloadRequest The request to cancel
     * @return {@link MbmsErrors#SUCCESS},
     *         {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST},
     *         {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
     */
    @Override
    public int cancelDownload(DownloadRequest downloadRequest) throws RemoteException {
        return 0;
    }

    /**
     * Requests information about the state of a file pending download.
     *
     * If the middleware has no records of the
     * file indicated by {@code fileInfo} being associated with {@code downloadRequest},
     * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_FILE_INFO} must be returned.
     *
     * @param downloadRequest The download request to query.
     * @param fileInfo The particular file within the request to get information on.
     * @return {@link MbmsErrors#SUCCESS} if the request was successful, an error code otherwise.
     */
    @Override
    public int requestDownloadState(DownloadRequest downloadRequest, FileInfo fileInfo)
            throws RemoteException {
        return 0;
    }

    /**
     * Resets the middleware's knowledge of previously-downloaded files in this download request.
     *
     * When this method is called, the middleware must attempt to re-download all the files
     * specified by the {@link DownloadRequest}, even if the files have not changed on the server.
     * In addition, current in-progress downloads must not be interrupted.
     *
     * If the middleware is not aware of the specified download request, return
     * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
     *
     * @param downloadRequest The request to re-download files for.
     */
    @Override
    public int resetDownloadKnowledge(DownloadRequest downloadRequest)
            throws RemoteException {
        return 0;
    }

    /**
     * Signals that the app wishes to dispose of the session identified by the
     * {@code subscriptionId} argument and the caller's uid. No notification back to the
     * app is required for this operation, and the corresponding callback provided via
     * {@link #initialize(int, IMbmsDownloadSessionCallback)} should no longer be used
     * after this method has been called by the app.
     *
     * Any download requests issued by the app should remain in effect until the app calls
     * {@link #cancelDownload(DownloadRequest)} on another session.
     *
     * May throw an {@link IllegalStateException}
     *
     * @param subscriptionId The subscription id to use.
     */
    @Override
    public void dispose(int subscriptionId) throws RemoteException {
    }

    /**
     * Indicates that the app identified by the given UID and subscription ID has died.
     * @param uid the UID of the app, as returned by {@link Binder#getCallingUid()}.
     * @param subscriptionId The subscription ID the app is using.
     */
    public void onAppCallbackDied(int uid, int subscriptionId) {
    }

    // Following two methods exist to workaround b/124210145
    /** @hide */
    @SystemApi
    @Override
    public android.os.IBinder asBinder() {
        return super.asBinder();
    }

    /** @hide */
    @SystemApi
    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply,
            int flags) throws RemoteException {
        return super.onTransact(code, data, reply, flags);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy