Add callback for initialization done in the framework, and listen to it in the testapps. Make initialization asynchronous as well for both download and streaming. Change-Id: Iea7f803df9d2752401b2eca9f6c7375007cac35e
522 lines
22 KiB
Java
522 lines
22 KiB
Java
/*
|
|
* Copyright (C) 2016 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;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.ServiceConnection;
|
|
import android.content.SharedPreferences;
|
|
import android.content.pm.ResolveInfo;
|
|
import android.net.Uri;
|
|
import android.os.IBinder;
|
|
import android.os.RemoteException;
|
|
import android.telephony.mbms.IDownloadCallback;
|
|
import android.telephony.mbms.DownloadRequest;
|
|
import android.telephony.mbms.DownloadStatus;
|
|
import android.telephony.mbms.IMbmsDownloadManagerCallback;
|
|
import android.telephony.mbms.MbmsDownloadManagerCallback;
|
|
import android.telephony.mbms.MbmsDownloadReceiver;
|
|
import android.telephony.mbms.MbmsException;
|
|
import android.telephony.mbms.MbmsTempFileProvider;
|
|
import android.telephony.mbms.MbmsUtils;
|
|
import android.telephony.mbms.vendor.IMbmsDownloadService;
|
|
import android.util.Log;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.util.List;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
|
|
|
/** @hide */
|
|
public class MbmsDownloadManager {
|
|
private static final String LOG_TAG = MbmsDownloadManager.class.getSimpleName();
|
|
|
|
public static final String MBMS_DOWNLOAD_SERVICE_ACTION =
|
|
"android.telephony.action.EmbmsDownload";
|
|
/**
|
|
* The MBMS middleware should send this when a download of single file has completed or
|
|
* failed. Mandatory extras are
|
|
* {@link #EXTRA_RESULT}
|
|
* {@link #EXTRA_FILE_INFO}
|
|
* {@link #EXTRA_REQUEST}
|
|
* {@link #EXTRA_TEMP_LIST}
|
|
* {@link #EXTRA_FINAL_URI}
|
|
*
|
|
* TODO: future systemapi
|
|
*/
|
|
public static final String ACTION_DOWNLOAD_RESULT_INTERNAL =
|
|
"android.telephony.mbms.action.DOWNLOAD_RESULT_INTERNAL";
|
|
|
|
/**
|
|
* The MBMS middleware should send this when it wishes to request {@code content://} URIs to
|
|
* serve as temp files for downloads or when it wishes to resume paused downloads. Mandatory
|
|
* extras are
|
|
* {@link #EXTRA_REQUEST}
|
|
*
|
|
* Optional extras are
|
|
* {@link #EXTRA_FD_COUNT} (0 if not present)
|
|
* {@link #EXTRA_PAUSED_LIST} (empty if not present)
|
|
*
|
|
* TODO: future systemapi
|
|
*/
|
|
public static final String ACTION_FILE_DESCRIPTOR_REQUEST =
|
|
"android.telephony.mbms.action.FILE_DESCRIPTOR_REQUEST";
|
|
|
|
/**
|
|
* The MBMS middleware should send this when it wishes to clean up temp files in the app's
|
|
* filesystem. Mandatory extras are:
|
|
* {@link #EXTRA_TEMP_FILES_IN_USE}
|
|
*
|
|
* TODO: future systemapi
|
|
*/
|
|
public static final String ACTION_CLEANUP =
|
|
"android.telephony.mbms.action.CLEANUP";
|
|
|
|
/**
|
|
* Integer extra indicating the result code of the download. One of
|
|
* {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, or {@link #RESULT_CANCELLED}.
|
|
*/
|
|
public static final String EXTRA_RESULT = "android.telephony.mbms.extra.RESULT";
|
|
|
|
/**
|
|
* Extra containing the {@link android.telephony.mbms.FileInfo} for which the download result
|
|
* is for. Must not be null.
|
|
* TODO: future systemapi (here and and all extras) except the two for the app intent
|
|
*/
|
|
public static final String EXTRA_FILE_INFO = "android.telephony.mbms.extra.FILE_INFO";
|
|
|
|
/**
|
|
* 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_REQUEST = "android.telephony.mbms.extra.REQUEST";
|
|
|
|
/**
|
|
* Extra containing a {@link List} of {@link Uri}s that were used as temp files for this
|
|
* completed file. These {@link Uri}s should have scheme {@code file://}, and the temp
|
|
* files will be deleted upon receipt of the intent.
|
|
* May be null.
|
|
*/
|
|
public static final String EXTRA_TEMP_LIST = "android.telephony.mbms.extra.TEMP_LIST";
|
|
|
|
/**
|
|
* Extra containing a single {@link Uri} indicating the path to the temp file in which the
|
|
* decoded downloaded file resides. Must not be null.
|
|
*/
|
|
public static final String EXTRA_FINAL_URI = "android.telephony.mbms.extra.FINAL_URI";
|
|
|
|
/**
|
|
* Extra containing an integer indicating the number of temp files requested.
|
|
*/
|
|
public static final String EXTRA_FD_COUNT = "android.telephony.mbms.extra.FD_COUNT";
|
|
|
|
/**
|
|
* Extra containing a list of {@link Uri}s that the middleware is requesting access to via
|
|
* {@link #ACTION_FILE_DESCRIPTOR_REQUEST} in order to resume downloading. These {@link Uri}s
|
|
* should have scheme {@code file://}.
|
|
*/
|
|
public static final String EXTRA_PAUSED_LIST = "android.telephony.mbms.extra.PAUSED_LIST";
|
|
|
|
/**
|
|
* Extra containing a list of {@link android.telephony.mbms.UriPathPair}s, used in the
|
|
* response to {@link #ACTION_FILE_DESCRIPTOR_REQUEST}. These are temp files that are meant
|
|
* to be used for new file downloads.
|
|
*/
|
|
public static final String EXTRA_FREE_URI_LIST = "android.telephony.mbms.extra.FREE_URI_LIST";
|
|
|
|
/**
|
|
* Extra containing a list of {@link android.telephony.mbms.UriPathPair}s, used in the
|
|
* response to {@link #ACTION_FILE_DESCRIPTOR_REQUEST}. These
|
|
* {@link android.telephony.mbms.UriPathPair}s contain {@code content://} URIs that provide
|
|
* access to previously paused downloads.
|
|
*/
|
|
public static final String EXTRA_PAUSED_URI_LIST =
|
|
"android.telephony.mbms.extra.PAUSED_URI_LIST";
|
|
|
|
/**
|
|
* Extra containing a string that points to the middleware's knowledge of where the temp file
|
|
* root for the app is. The path should be a canonical path as returned by
|
|
* {@link File#getCanonicalPath()}
|
|
*/
|
|
public static final String EXTRA_TEMP_FILE_ROOT =
|
|
"android.telephony.mbms.extra.TEMP_FILE_ROOT";
|
|
|
|
/**
|
|
* Extra containing a list of {@link Uri}s indicating temp files which the middleware is
|
|
* still using.
|
|
*/
|
|
public static final String EXTRA_TEMP_FILES_IN_USE =
|
|
"android.telephony.mbms.extra.TEMP_FILES_IN_USE";
|
|
|
|
/**
|
|
* Extra containing a single {@link Uri} indicating the location of the successfully
|
|
* downloaded file. Set on the intent provided via
|
|
* {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}.
|
|
* Will always be set to a non-null value if {@link #EXTRA_RESULT} is set to
|
|
* {@link #RESULT_SUCCESSFUL}.
|
|
*/
|
|
public static final String EXTRA_COMPLETED_FILE_URI =
|
|
"android.telephony.mbms.extra.COMPLETED_FILE_URI";
|
|
|
|
public static final int RESULT_SUCCESSFUL = 1;
|
|
public static final int RESULT_CANCELLED = 2;
|
|
public static final int RESULT_EXPIRED = 3;
|
|
// TODO - more results!
|
|
|
|
private final Context mContext;
|
|
private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
|
|
|
|
private AtomicReference<IMbmsDownloadService> mService = new AtomicReference<>(null);
|
|
private final IMbmsDownloadManagerCallback mCallback;
|
|
private final String mDownloadAppName;
|
|
|
|
private MbmsDownloadManager(Context context, IMbmsDownloadManagerCallback callback,
|
|
String downloadAppName, int subId) {
|
|
mContext = context;
|
|
mCallback = callback;
|
|
mDownloadAppName = downloadAppName;
|
|
mSubscriptionId = subId;
|
|
}
|
|
|
|
/**
|
|
* Create a new MbmsDownloadManager using the system default data subscription ID.
|
|
* See {@link #create(Context, IMbmsDownloadManagerCallback, String, int)}
|
|
*
|
|
* @hide
|
|
*/
|
|
public static MbmsDownloadManager create(Context context,
|
|
IMbmsDownloadManagerCallback listener, String downloadAppName)
|
|
throws MbmsException {
|
|
return create(context, listener, downloadAppName,
|
|
SubscriptionManager.getDefaultSubscriptionId());
|
|
}
|
|
|
|
/**
|
|
* Create a new MbmsDownloadManager using the given subscription ID.
|
|
*
|
|
* Note that this call will bind a remote service and that may take a bit. The instance of
|
|
* {@link MbmsDownloadManager} that is returned will not be ready for use until
|
|
* {@link IMbmsDownloadManagerCallback#middlewareReady()} is called on the provided callback.
|
|
* If you attempt to use the manager before it is ready, a {@link MbmsException} will be thrown.
|
|
*
|
|
* This also may throw an {@link IllegalArgumentException} or a {@link MbmsException}.
|
|
*
|
|
* @param context The instance of {@link Context} to use
|
|
* @param listener A callback to get asynchronous error messages and file service updates.
|
|
* @param downloadAppName The app name, as negotiated with the eMBMS provider
|
|
* @param subscriptionId The data subscription ID to use
|
|
* @hide
|
|
*/
|
|
public static MbmsDownloadManager create(Context context,
|
|
IMbmsDownloadManagerCallback listener, String downloadAppName, int subscriptionId)
|
|
throws MbmsException {
|
|
MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, downloadAppName,
|
|
subscriptionId);
|
|
mdm.bindAndInitialize();
|
|
return mdm;
|
|
}
|
|
|
|
private void bindAndInitialize() throws MbmsException {
|
|
MbmsUtils.startBinding(mContext, MBMS_DOWNLOAD_SERVICE_ACTION,
|
|
new ServiceConnection() {
|
|
@Override
|
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
|
IMbmsDownloadService downloadService =
|
|
IMbmsDownloadService.Stub.asInterface(service);
|
|
try {
|
|
downloadService.initialize(
|
|
mDownloadAppName, mSubscriptionId, mCallback);
|
|
} catch (RemoteException e) {
|
|
Log.e(LOG_TAG, "Service died before initialization");
|
|
return;
|
|
}
|
|
mService.set(downloadService);
|
|
}
|
|
|
|
@Override
|
|
public void onServiceDisconnected(ComponentName name) {
|
|
mService.set(null);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* An inspection API to retrieve the list of available
|
|
* {@link android.telephony.mbms.FileServiceInfo}s currently being advertised.
|
|
* The results are returned asynchronously via a call to
|
|
* {@link IMbmsDownloadManagerCallback#fileServicesUpdated(List)}
|
|
*
|
|
* The serviceClasses argument lets the app filter on types of programming and is opaque data
|
|
* negotiated beforehand between the app and the carrier.
|
|
*
|
|
* Multiple calls replace the list of serviceClasses of interest.
|
|
*
|
|
* This may throw an {@link MbmsException} containing one of the following errors:
|
|
* {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
|
|
* {@link MbmsException#ERROR_CONCURRENT_SERVICE_LIMIT_REACHED}
|
|
* {@link MbmsException#ERROR_SERVICE_LOST}
|
|
*
|
|
* Asynchronous error codes via the {@link MbmsDownloadManagerCallback#error(int, String)}
|
|
* callback can include any of the errors except:
|
|
* {@link MbmsException#ERROR_UNABLE_TO_START_SERVICE}
|
|
* {@link MbmsException#ERROR_END_OF_SESSION}
|
|
*/
|
|
public void getFileServices(List<String> classList) throws MbmsException {
|
|
IMbmsDownloadService downloadService = mService.get();
|
|
if (downloadService == null) {
|
|
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
|
|
}
|
|
try {
|
|
int returnCode = downloadService.getFileServices(
|
|
mDownloadAppName, mSubscriptionId, classList);
|
|
if (returnCode != MbmsException.SUCCESS) {
|
|
throw new MbmsException(returnCode);
|
|
}
|
|
} catch (RemoteException e) {
|
|
Log.w(LOG_TAG, "Remote process died");
|
|
mService.set(null);
|
|
throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the temp file root for downloads.
|
|
* All temp files created for the middleware to write to will be contained in the specified
|
|
* directory. Applications that wish to specify a location only need to call this method once
|
|
* as long their data is persisted in storage -- the argument will be stored both in a
|
|
* local instance of {@link android.content.SharedPreferences} and by the middleware.
|
|
*
|
|
* If this method is not called at least once before calling
|
|
* {@link #download(DownloadRequest, IDownloadCallback)}, the framework
|
|
* will default to a directory formed by the concatenation of the app's files directory and
|
|
* {@link android.telephony.mbms.MbmsTempFileProvider#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY}.
|
|
*
|
|
* This method may not be called while any download requests are still active. If this is
|
|
* the case, an {@link MbmsException} will be thrown with code
|
|
* {@link MbmsException#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}
|
|
*
|
|
* The {@link File} supplied as a root temp file directory must already exist. If not, an
|
|
* {@link IllegalArgumentException} will be thrown.
|
|
* @param tempFileRootDirectory A directory to place temp files in.
|
|
*/
|
|
public void setTempFileRootDirectory(@NonNull File tempFileRootDirectory)
|
|
throws MbmsException {
|
|
IMbmsDownloadService downloadService = mService.get();
|
|
if (downloadService == null) {
|
|
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
|
|
}
|
|
if (!tempFileRootDirectory.exists()) {
|
|
throw new IllegalArgumentException("Provided directory does not exist");
|
|
}
|
|
if (!tempFileRootDirectory.isDirectory()) {
|
|
throw new IllegalArgumentException("Provided File is not a directory");
|
|
}
|
|
String filePath;
|
|
try {
|
|
filePath = tempFileRootDirectory.getCanonicalPath();
|
|
} catch (IOException e) {
|
|
throw new IllegalArgumentException("Unable to canonicalize the provided path: " + e);
|
|
}
|
|
|
|
try {
|
|
int result = downloadService.setTempFileRootDirectory(
|
|
mDownloadAppName, mSubscriptionId, filePath);
|
|
if (result != MbmsException.SUCCESS) {
|
|
throw new MbmsException(result);
|
|
}
|
|
} catch (RemoteException e) {
|
|
mService.set(null);
|
|
throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
|
|
}
|
|
|
|
SharedPreferences prefs = mContext.getSharedPreferences(
|
|
MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
|
|
prefs.edit().putString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, filePath).apply();
|
|
}
|
|
|
|
/**
|
|
* Requests a download of a file that is available via multicast.
|
|
*
|
|
* downloadListener is an optional callback object which can be used to get progress reports
|
|
* of a currently occuring download. Note this can only run while the calling app
|
|
* is running, so future downloads will simply result in resultIntents being sent
|
|
* for completed or errored-out downloads. A NULL indicates no callbacks are needed.
|
|
*
|
|
* May throw an {@link IllegalArgumentException}
|
|
*
|
|
* If {@link #setTempFileRootDirectory(File)} has not called after the app has been installed,
|
|
* this method will create a directory at the default location defined at
|
|
* {@link MbmsTempFileProvider#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY} and store that as the temp
|
|
* file root directory.
|
|
*
|
|
* Asynchronous errors through the listener include any of the errors
|
|
*
|
|
* @param request The request that specifies what should be downloaded
|
|
* @param callback Optional callback that will provide progress updates if the app is running.
|
|
*/
|
|
public void download(DownloadRequest request, IDownloadCallback callback)
|
|
throws MbmsException {
|
|
IMbmsDownloadService downloadService = mService.get();
|
|
if (downloadService == null) {
|
|
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
|
|
}
|
|
|
|
// Check to see whether the app's set a temp root dir yet, and set it if not.
|
|
SharedPreferences prefs = mContext.getSharedPreferences(
|
|
MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
|
|
if (prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null) == null) {
|
|
File tempRootDirectory = new File(mContext.getFilesDir(),
|
|
MbmsTempFileProvider.DEFAULT_TOP_LEVEL_TEMP_DIRECTORY);
|
|
tempRootDirectory.mkdirs();
|
|
setTempFileRootDirectory(tempRootDirectory);
|
|
}
|
|
|
|
request.setAppName(mDownloadAppName);
|
|
// Check if the request is a multipart download. If so, validate that the destination is
|
|
// a directory that exists.
|
|
// TODO: figure out what qualifies a request as a multipart download request.
|
|
if (request.getSourceUri().getLastPathSegment() != null &&
|
|
request.getSourceUri().getLastPathSegment().contains("*")) {
|
|
File toFile = new File(request.getDestinationUri().getSchemeSpecificPart());
|
|
if (!toFile.isDirectory()) {
|
|
throw new IllegalArgumentException("Multipart download must specify valid " +
|
|
"destination directory.");
|
|
}
|
|
}
|
|
// TODO: check to make sure destination is clear
|
|
// TODO: write download request token
|
|
try {
|
|
downloadService.download(request, callback);
|
|
} catch (RemoteException e) {
|
|
mService.set(null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a list DownloadRequests that originated from this application (UID).
|
|
*
|
|
* May throw a RemoteException.
|
|
*
|
|
* Asynchronous errors through the listener include any of the errors except
|
|
* <li>ERROR_UNABLED_TO_START_SERVICE</li>
|
|
* <li>ERROR_MSDC_INVALID_SERVICE_ID</li>
|
|
* <li>ERROR_MSDC_END_OF_SESSION</li>
|
|
*/
|
|
public List<DownloadRequest> listPendingDownloads() {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Attempts to cancel the specified DownloadRequest.
|
|
*
|
|
* May throw a RemoteException.
|
|
*
|
|
* Synchronous responses may include
|
|
* <li>SUCCESS</li>
|
|
* <li>ERROR_MSDC_CONCURRENT_SERVICE_LIMIT_REACHED</li>
|
|
* <li>ERROR_MSDC_UNKNOWN_REQUEST</li>
|
|
*/
|
|
public int cancelDownload(DownloadRequest downloadRequest) {
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Gets information about current and known upcoming downloads.
|
|
*
|
|
* Current is a straightforward count of the files being downloaded "now"
|
|
* for some definition of now (may be racey).
|
|
* Future downloads include counts of files with pending repair operations, counts of
|
|
* files with future downloads and indication of scheduled download times with unknown
|
|
* file details.
|
|
*
|
|
* May throw an IllegalArgumentException or RemoteException.
|
|
*
|
|
* If the DownloadRequest is unknown the results will be null.
|
|
*/
|
|
public DownloadStatus getDownloadStatus(DownloadRequest downloadRequest) {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Resets middleware knowledge regarding this download request.
|
|
*
|
|
* This state consists of knowledge of what files have already been downloaded.
|
|
* Normally the middleware won't download files who's hash matches previously downloaded
|
|
* content, even if that content has since been deleted. If this function is called
|
|
* repeated content will be downloaded again when available. This does not interrupt
|
|
* in-progress downloads.
|
|
*
|
|
* May throw an IllegalArgumentException or RemoteException.
|
|
*
|
|
* <li>SUCCESS</li>
|
|
* <li>ERROR_MSDC_CONCURRENT_SERVICE_LIMIT_REACHED</li>
|
|
* <li>ERROR_MSDC_UNKNOWN_REQUEST</li>
|
|
*/
|
|
public int resetDownloadKnowledge(DownloadRequest downloadRequest) {
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the {@link ComponentName} for the {@link android.content.BroadcastReceiver} that
|
|
* the various intents from the middleware should be targeted towards.
|
|
* @param uid The uid of the frontend app.
|
|
* @return The component name of the receiver that the middleware should send its intents to,
|
|
* or null if the app didn't declare it in the manifest.
|
|
*
|
|
* @hide
|
|
* future systemapi
|
|
*/
|
|
public static ComponentName getAppReceiverFromUid(Context context, int uid) {
|
|
String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
|
|
if (packageNames == null) {
|
|
return null;
|
|
}
|
|
|
|
for (String packageName : packageNames) {
|
|
ComponentName candidate = new ComponentName(packageName,
|
|
MbmsDownloadReceiver.class.getCanonicalName());
|
|
Intent queryIntent = new Intent();
|
|
queryIntent.setComponent(candidate);
|
|
List<ResolveInfo> receivers =
|
|
context.getPackageManager().queryBroadcastReceivers(queryIntent, 0);
|
|
if (receivers != null && receivers.size() > 0) {
|
|
return candidate;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void dispose() {
|
|
try {
|
|
IMbmsDownloadService downloadService = mService.get();
|
|
if (downloadService == null) {
|
|
Log.i(LOG_TAG, "Service already dead");
|
|
return;
|
|
}
|
|
downloadService.dispose(mDownloadAppName, mSubscriptionId);
|
|
mService.set(null);
|
|
} catch (RemoteException e) {
|
|
// Ignore
|
|
Log.i(LOG_TAG, "Remote exception while disposing of service");
|
|
}
|
|
}
|
|
}
|