1136 lines
50 KiB
Java
1136 lines
50 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 static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
|
|
|
import android.annotation.IntDef;
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.annotation.SdkConstant;
|
|
import android.annotation.SystemApi;
|
|
import android.annotation.TestApi;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.ServiceConnection;
|
|
import android.content.SharedPreferences;
|
|
import android.net.Uri;
|
|
import android.os.Handler;
|
|
import android.os.IBinder;
|
|
import android.os.RemoteException;
|
|
import android.telephony.mbms.DownloadProgressListener;
|
|
import android.telephony.mbms.DownloadRequest;
|
|
import android.telephony.mbms.DownloadStatusListener;
|
|
import android.telephony.mbms.FileInfo;
|
|
import android.telephony.mbms.InternalDownloadProgressListener;
|
|
import android.telephony.mbms.InternalDownloadSessionCallback;
|
|
import android.telephony.mbms.InternalDownloadStatusListener;
|
|
import android.telephony.mbms.MbmsDownloadReceiver;
|
|
import android.telephony.mbms.MbmsDownloadSessionCallback;
|
|
import android.telephony.mbms.MbmsErrors;
|
|
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.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
/**
|
|
* This class provides functionality for file download over MBMS.
|
|
*/
|
|
public class MbmsDownloadSession implements AutoCloseable {
|
|
private static final String LOG_TAG = MbmsDownloadSession.class.getSimpleName();
|
|
|
|
/**
|
|
* Service action which must be handled by the middleware implementing the MBMS file download
|
|
* interface.
|
|
* @hide
|
|
*/
|
|
@SystemApi
|
|
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
|
|
public static final String MBMS_DOWNLOAD_SERVICE_ACTION =
|
|
"android.telephony.action.EmbmsDownload";
|
|
|
|
/**
|
|
* Metadata key that specifies the component name of the service to bind to for file-download.
|
|
* @hide
|
|
*/
|
|
@TestApi
|
|
public static final String MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA =
|
|
"mbms-download-service-override";
|
|
|
|
/**
|
|
* Integer extra that Android will attach to the intent supplied via
|
|
* {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
|
|
* Indicates the result code of the download. One of
|
|
* {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, {@link #RESULT_CANCELLED},
|
|
* {@link #RESULT_IO_ERROR}, {@link #RESULT_DOWNLOAD_FAILURE}, {@link #RESULT_OUT_OF_STORAGE},
|
|
* {@link #RESULT_SERVICE_ID_NOT_DEFINED}, or {@link #RESULT_FILE_ROOT_UNREACHABLE}.
|
|
*
|
|
* This extra may also be used by the middleware when it is sending intents to the app.
|
|
*/
|
|
public static final String EXTRA_MBMS_DOWNLOAD_RESULT =
|
|
"android.telephony.extra.MBMS_DOWNLOAD_RESULT";
|
|
|
|
/**
|
|
* {@link FileInfo} extra that Android will attach to the intent supplied via
|
|
* {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
|
|
* Indicates the file for which the download result is for. Never null.
|
|
*
|
|
* This extra may also be used by the middleware when it is sending intents to the app.
|
|
*/
|
|
public static final String EXTRA_MBMS_FILE_INFO = "android.telephony.extra.MBMS_FILE_INFO";
|
|
|
|
/**
|
|
* {@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 within the directory that the
|
|
* app provided via the builder.
|
|
*
|
|
* 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
|
|
* {@link #setTempFileRootDirectory(File)}, this directory will be created for you under the
|
|
* path returned by {@link Context#getFilesDir()}.
|
|
*/
|
|
public static final String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot";
|
|
|
|
|
|
/** @hide */
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
@IntDef(value = {RESULT_SUCCESSFUL, RESULT_CANCELLED, RESULT_EXPIRED, RESULT_IO_ERROR,
|
|
RESULT_SERVICE_ID_NOT_DEFINED, RESULT_DOWNLOAD_FAILURE, RESULT_OUT_OF_STORAGE,
|
|
RESULT_FILE_ROOT_UNREACHABLE}, prefix = { "RESULT_" })
|
|
public @interface DownloadResultCode{}
|
|
|
|
/**
|
|
* Indicates that the download was successful.
|
|
*/
|
|
public static final int RESULT_SUCCESSFUL = 1;
|
|
|
|
/**
|
|
* Indicates that the download was cancelled via {@link #cancelDownload(DownloadRequest)}.
|
|
*/
|
|
public static final int RESULT_CANCELLED = 2;
|
|
|
|
/**
|
|
* Indicates that the download will not be completed due to the expiration of its download
|
|
* window on the carrier's network.
|
|
*/
|
|
public static final int RESULT_EXPIRED = 3;
|
|
|
|
/**
|
|
* Indicates that the download will not be completed due to an I/O error incurred while
|
|
* writing to temp files.
|
|
*
|
|
* This is likely a transient error and another {@link DownloadRequest} should be sent to try
|
|
* the download again.
|
|
*/
|
|
public static final int RESULT_IO_ERROR = 4;
|
|
|
|
/**
|
|
* Indicates that the Service ID specified in the {@link DownloadRequest} is incorrect due to
|
|
* the Id being incorrect, stale, expired, or similar.
|
|
*/
|
|
public static final int RESULT_SERVICE_ID_NOT_DEFINED = 5;
|
|
|
|
/**
|
|
* Indicates that there was an error while processing downloaded files, such as a file repair or
|
|
* file decoding error and is not due to a file I/O error.
|
|
*
|
|
* This is likely a transient error and another {@link DownloadRequest} should be sent to try
|
|
* the download again.
|
|
*/
|
|
public static final int RESULT_DOWNLOAD_FAILURE = 6;
|
|
|
|
/**
|
|
* Indicates that the file system is full and the {@link DownloadRequest} can not complete.
|
|
* Either space must be made on the current file system or the temp file root location must be
|
|
* changed to a location that is not full to download the temp files.
|
|
*/
|
|
public static final int RESULT_OUT_OF_STORAGE = 7;
|
|
|
|
/**
|
|
* Indicates that the file root that was set is currently unreachable. This can happen if the
|
|
* temp files are set to be stored on external storage and the SD card was removed, for example.
|
|
* The temp file root should be changed before sending another DownloadRequest.
|
|
*/
|
|
public static final int RESULT_FILE_ROOT_UNREACHABLE = 8;
|
|
|
|
/** @hide */
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
@IntDef({STATUS_UNKNOWN, STATUS_ACTIVELY_DOWNLOADING, STATUS_PENDING_DOWNLOAD,
|
|
STATUS_PENDING_REPAIR, STATUS_PENDING_DOWNLOAD_WINDOW})
|
|
public @interface DownloadStatus {}
|
|
|
|
/**
|
|
* Indicates that the middleware has no information on the file.
|
|
*/
|
|
public static final int STATUS_UNKNOWN = 0;
|
|
|
|
/**
|
|
* Indicates that the file is actively being downloaded.
|
|
*/
|
|
public static final int STATUS_ACTIVELY_DOWNLOADING = 1;
|
|
|
|
/**
|
|
* Indicates that the file is awaiting the next download or repair operations. When a more
|
|
* precise status is known, the status will change to either {@link #STATUS_PENDING_REPAIR} or
|
|
* {@link #STATUS_PENDING_DOWNLOAD_WINDOW}.
|
|
*/
|
|
public static final int STATUS_PENDING_DOWNLOAD = 2;
|
|
|
|
/**
|
|
* Indicates that the file is awaiting file repair after the download has ended.
|
|
*/
|
|
public static final int STATUS_PENDING_REPAIR = 3;
|
|
|
|
/**
|
|
* Indicates that the file is waiting to download because its download window has not yet
|
|
* started and is scheduled for a future time.
|
|
*/
|
|
public static final int STATUS_PENDING_DOWNLOAD_WINDOW = 4;
|
|
|
|
private static final String DESTINATION_SANITY_CHECK_FILE_NAME = "destinationSanityCheckFile";
|
|
|
|
private static final int MAX_SERVICE_ANNOUNCEMENT_SIZE = 10 * 1024; // 10KB
|
|
|
|
private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
|
|
|
|
private final Context mContext;
|
|
private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
|
|
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
|
|
@Override
|
|
public void binderDied() {
|
|
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, "Received death notification");
|
|
}
|
|
};
|
|
|
|
private AtomicReference<IMbmsDownloadService> mService = new AtomicReference<>(null);
|
|
private ServiceConnection mServiceConnection;
|
|
private final InternalDownloadSessionCallback mInternalCallback;
|
|
private final Map<DownloadStatusListener, InternalDownloadStatusListener>
|
|
mInternalDownloadStatusListeners = new HashMap<>();
|
|
private final Map<DownloadProgressListener, InternalDownloadProgressListener>
|
|
mInternalDownloadProgressListeners = new HashMap<>();
|
|
|
|
private MbmsDownloadSession(Context context, Executor executor, int subscriptionId,
|
|
MbmsDownloadSessionCallback callback) {
|
|
mContext = context;
|
|
mSubscriptionId = subscriptionId;
|
|
mInternalCallback = new InternalDownloadSessionCallback(callback, executor);
|
|
}
|
|
|
|
/**
|
|
* Create a new {@link MbmsDownloadSession} using the system default data subscription ID.
|
|
* See {@link #create(Context, Executor, int, MbmsDownloadSessionCallback)}
|
|
*/
|
|
public static MbmsDownloadSession create(@NonNull Context context,
|
|
@NonNull Executor executor, @NonNull MbmsDownloadSessionCallback callback) {
|
|
return create(context, executor, SubscriptionManager.getDefaultSubscriptionId(), callback);
|
|
}
|
|
|
|
/**
|
|
* 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 MbmsDownloadSession} that is returned will not be ready for use until
|
|
* {@link MbmsDownloadSessionCallback#onMiddlewareReady()} is called on the provided callback.
|
|
* If you attempt to use the instance before it is ready, an {@link IllegalStateException}
|
|
* will be thrown or an error will be delivered through
|
|
* {@link MbmsDownloadSessionCallback#onError(int, String)}.
|
|
*
|
|
* This also may throw an {@link IllegalArgumentException}.
|
|
*
|
|
* You may only have one instance of {@link MbmsDownloadSession} per UID. If you call this
|
|
* method while there is an active instance of {@link MbmsDownloadSession} in your process
|
|
* (in other words, one that has not had {@link #close()} called on it), this method will
|
|
* throw an {@link IllegalStateException}. If you call this method in a different process
|
|
* running under the same UID, an error will be indicated via
|
|
* {@link MbmsDownloadSessionCallback#onError(int, String)}.
|
|
*
|
|
* Note that initialization may fail asynchronously. If you wish to try again after you
|
|
* receive such an asynchronous error, you must call {@link #close()} on the instance of
|
|
* {@link MbmsDownloadSession} that you received before calling this method again.
|
|
*
|
|
* @param context The instance of {@link Context} to use
|
|
* @param executor The executor on which you wish to execute callbacks.
|
|
* @param subscriptionId The data subscription ID to use
|
|
* @param callback A callback to get asynchronous error messages and file service updates.
|
|
* @return A new instance of {@link MbmsDownloadSession}, or null if an error occurred during
|
|
* setup.
|
|
*/
|
|
public static @Nullable MbmsDownloadSession create(@NonNull Context context,
|
|
@NonNull Executor executor, int subscriptionId,
|
|
final @NonNull MbmsDownloadSessionCallback callback) {
|
|
if (!sIsInitialized.compareAndSet(false, true)) {
|
|
throw new IllegalStateException("Cannot have two active instances");
|
|
}
|
|
MbmsDownloadSession session =
|
|
new MbmsDownloadSession(context, executor, subscriptionId, callback);
|
|
final int result = session.bindAndInitialize();
|
|
if (result != MbmsErrors.SUCCESS) {
|
|
sIsInitialized.set(false);
|
|
executor.execute(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
callback.onError(result, null);
|
|
}
|
|
});
|
|
return null;
|
|
}
|
|
return session;
|
|
}
|
|
|
|
/**
|
|
* Returns the maximum size of the service announcement descriptor that can be provided via
|
|
* {@link #addServiceAnnouncement}
|
|
* @return The maximum length of the byte array passed as an argument to
|
|
* {@link #addServiceAnnouncement}.
|
|
*/
|
|
public static int getMaximumServiceAnnouncementSize() {
|
|
return MAX_SERVICE_ANNOUNCEMENT_SIZE;
|
|
}
|
|
|
|
private int bindAndInitialize() {
|
|
mServiceConnection = new ServiceConnection() {
|
|
@Override
|
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
|
IMbmsDownloadService downloadService =
|
|
IMbmsDownloadService.Stub.asInterface(service);
|
|
int result;
|
|
try {
|
|
result = downloadService.initialize(mSubscriptionId, mInternalCallback);
|
|
} catch (RemoteException e) {
|
|
Log.e(LOG_TAG, "Service died before initialization");
|
|
sIsInitialized.set(false);
|
|
return;
|
|
} catch (RuntimeException e) {
|
|
Log.e(LOG_TAG, "Runtime exception during initialization");
|
|
sendErrorToApp(
|
|
MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
|
|
e.toString());
|
|
sIsInitialized.set(false);
|
|
return;
|
|
}
|
|
if (result == MbmsErrors.UNKNOWN) {
|
|
// Unbind and throw an obvious error
|
|
close();
|
|
throw new IllegalStateException("Middleware must not return an"
|
|
+ " unknown error code");
|
|
}
|
|
if (result != MbmsErrors.SUCCESS) {
|
|
sendErrorToApp(result, "Error returned during initialization");
|
|
sIsInitialized.set(false);
|
|
return;
|
|
}
|
|
try {
|
|
downloadService.asBinder().linkToDeath(mDeathRecipient, 0);
|
|
} catch (RemoteException e) {
|
|
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST,
|
|
"Middleware lost during initialization");
|
|
sIsInitialized.set(false);
|
|
return;
|
|
}
|
|
mService.set(downloadService);
|
|
}
|
|
|
|
@Override
|
|
public void onServiceDisconnected(ComponentName name) {
|
|
Log.w(LOG_TAG, "bindAndInitialize: Remote service disconnected");
|
|
sIsInitialized.set(false);
|
|
mService.set(null);
|
|
}
|
|
|
|
@Override
|
|
public void onNullBinding(ComponentName name) {
|
|
Log.w(LOG_TAG, "bindAndInitialize: Remote service returned null");
|
|
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST,
|
|
"Middleware service binding returned null");
|
|
sIsInitialized.set(false);
|
|
mService.set(null);
|
|
mContext.unbindService(this);
|
|
}
|
|
};
|
|
return MbmsUtils.startBinding(mContext, MBMS_DOWNLOAD_SERVICE_ACTION, mServiceConnection);
|
|
}
|
|
|
|
/**
|
|
* 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 MbmsDownloadSessionCallback#onFileServicesUpdated(List)}
|
|
*
|
|
* Asynchronous error codes via the {@link MbmsDownloadSessionCallback#onError(int, String)}
|
|
* callback may include any of the errors that are not specific to the streaming use-case.
|
|
*
|
|
* May throw an {@link IllegalStateException} or {@link IllegalArgumentException}.
|
|
*
|
|
* @param classList A list of service classes which the app wishes to receive
|
|
* {@link MbmsDownloadSessionCallback#onFileServicesUpdated(List)} callbacks
|
|
* about. Subsequent calls to this method will replace this list of service
|
|
* classes (i.e. the middleware will no longer send updates for services
|
|
* matching classes only in the old list).
|
|
* Values in this list should be negotiated with the wireless carrier prior
|
|
* to using this API.
|
|
*/
|
|
public void requestUpdateFileServices(@NonNull List<String> classList) {
|
|
IMbmsDownloadService downloadService = mService.get();
|
|
if (downloadService == null) {
|
|
throw new IllegalStateException("Middleware not yet bound");
|
|
}
|
|
try {
|
|
int returnCode = downloadService.requestUpdateFileServices(mSubscriptionId, classList);
|
|
if (returnCode == MbmsErrors.UNKNOWN) {
|
|
// Unbind and throw an obvious error
|
|
close();
|
|
throw new IllegalStateException("Middleware must not return an unknown error code");
|
|
}
|
|
if (returnCode != MbmsErrors.SUCCESS) {
|
|
sendErrorToApp(returnCode, null);
|
|
}
|
|
} catch (RemoteException e) {
|
|
Log.w(LOG_TAG, "Remote process died");
|
|
mService.set(null);
|
|
sIsInitialized.set(false);
|
|
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inform the middleware of a service announcement descriptor received from a group
|
|
* communication server.
|
|
*
|
|
* When participating in a group call via the {@link MbmsGroupCallSession} API, applications may
|
|
* receive a service announcement descriptor from the group call server that informs them of
|
|
* files that may be relevant to users communicating on the group call.
|
|
*
|
|
* After supplying the service announcement descriptor received from the server to the
|
|
* middleware via this API, applications will receive information on the available files via
|
|
* {@link MbmsDownloadSessionCallback#onFileServicesUpdated}, and the available files will be
|
|
* downloadable via {@link MbmsDownloadSession#download} like other files published via
|
|
* {@link MbmsDownloadSessionCallback#onFileServicesUpdated}.
|
|
*
|
|
* Asynchronous error codes via the {@link MbmsDownloadSessionCallback#onError(int, String)}
|
|
* callback may include any of the errors that are not specific to the streaming use-case.
|
|
*
|
|
* May throw an {@link IllegalStateException} when the middleware has not yet been bound,
|
|
* or an {@link IllegalArgumentException} if the byte array is too large, or an
|
|
* {@link UnsupportedOperationException} if the middleware has not implemented this method.
|
|
*
|
|
* @param contents The contents of the service announcement descriptor received from the
|
|
* group call server. If the size of this array is greater than the value of
|
|
* {@link #getMaximumServiceAnnouncementSize()}, an
|
|
* {@link IllegalArgumentException} will be thrown.
|
|
*/
|
|
public void addServiceAnnouncement(@NonNull byte[] contents) {
|
|
IMbmsDownloadService downloadService = mService.get();
|
|
if (downloadService == null) {
|
|
throw new IllegalStateException("Middleware not yet bound");
|
|
}
|
|
|
|
if (contents.length > MAX_SERVICE_ANNOUNCEMENT_SIZE) {
|
|
throw new IllegalArgumentException("File too large");
|
|
}
|
|
|
|
try {
|
|
int returnCode = downloadService.addServiceAnnouncement(
|
|
mSubscriptionId, contents);
|
|
if (returnCode == MbmsErrors.UNKNOWN) {
|
|
// Unbind and throw an obvious error
|
|
close();
|
|
throw new IllegalStateException("Middleware must not return an unknown error code");
|
|
}
|
|
if (returnCode != MbmsErrors.SUCCESS) {
|
|
sendErrorToApp(returnCode, null);
|
|
}
|
|
} catch (RemoteException e) {
|
|
Log.w(LOG_TAG, "Remote process died");
|
|
mService.set(null);
|
|
sIsInitialized.set(false);
|
|
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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)}, the framework
|
|
* will default to a directory formed by the concatenation of the app's files directory and
|
|
* {@link MbmsDownloadSession#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY}.
|
|
*
|
|
* 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 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
|
|
* {@link IllegalArgumentException} will be thrown. In addition, as an additional correctness
|
|
* check, an {@link IllegalArgumentException} will be thrown if you attempt to set the temp
|
|
* file root directory to one of your data roots (the value of {@link Context#getDataDir()},
|
|
* {@link Context#getFilesDir()}, or {@link Context#getCacheDir()}).
|
|
* @param tempFileRootDirectory A directory to place temp files in.
|
|
*/
|
|
public void setTempFileRootDirectory(@NonNull File tempFileRootDirectory) {
|
|
IMbmsDownloadService downloadService = mService.get();
|
|
if (downloadService == null) {
|
|
throw new IllegalStateException("Middleware not yet bound");
|
|
}
|
|
try {
|
|
validateTempFileRootSanity(tempFileRootDirectory);
|
|
} catch (IOException e) {
|
|
throw new IllegalStateException("Got IOException checking directory sanity");
|
|
}
|
|
String filePath;
|
|
try {
|
|
filePath = tempFileRootDirectory.getCanonicalPath();
|
|
} catch (IOException e) {
|
|
throw new IllegalArgumentException("Unable to canonicalize the provided path: " + e);
|
|
}
|
|
|
|
try {
|
|
int result = downloadService.setTempFileRootDirectory(mSubscriptionId, filePath);
|
|
if (result == MbmsErrors.UNKNOWN) {
|
|
// Unbind and throw an obvious error
|
|
close();
|
|
throw new IllegalStateException("Middleware must not return an unknown error code");
|
|
}
|
|
if (result != MbmsErrors.SUCCESS) {
|
|
sendErrorToApp(result, null);
|
|
return;
|
|
}
|
|
} catch (RemoteException e) {
|
|
mService.set(null);
|
|
sIsInitialized.set(false);
|
|
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
|
|
return;
|
|
}
|
|
|
|
SharedPreferences prefs = mContext.getSharedPreferences(
|
|
MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
|
|
prefs.edit().putString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, filePath).apply();
|
|
}
|
|
|
|
private void validateTempFileRootSanity(File tempFileRootDirectory) throws IOException {
|
|
if (!tempFileRootDirectory.exists()) {
|
|
throw new IllegalArgumentException("Provided directory does not exist");
|
|
}
|
|
if (!tempFileRootDirectory.isDirectory()) {
|
|
throw new IllegalArgumentException("Provided File is not a directory");
|
|
}
|
|
String canonicalTempFilePath = tempFileRootDirectory.getCanonicalPath();
|
|
if (mContext.getDataDir().getCanonicalPath().equals(canonicalTempFilePath)) {
|
|
throw new IllegalArgumentException("Temp file root cannot be your data dir");
|
|
}
|
|
if (mContext.getCacheDir().getCanonicalPath().equals(canonicalTempFilePath)) {
|
|
throw new IllegalArgumentException("Temp file root cannot be your cache dir");
|
|
}
|
|
if (mContext.getFilesDir().getCanonicalPath().equals(canonicalTempFilePath)) {
|
|
throw new IllegalArgumentException("Temp file root cannot be your files dir");
|
|
}
|
|
}
|
|
/**
|
|
* Retrieves the currently configured temp file root directory. Returns the file that was
|
|
* configured via {@link #setTempFileRootDirectory(File)} or the default directory
|
|
* {@link #download(DownloadRequest)} was called without ever
|
|
* setting the temp file root. If neither method has been called since the last time the app's
|
|
* shared preferences were reset, returns {@code null}.
|
|
*
|
|
* @return A {@link File} pointing to the configured temp file directory, or null if not yet
|
|
* configured.
|
|
*/
|
|
public @Nullable File getTempFileRootDirectory() {
|
|
SharedPreferences prefs = mContext.getSharedPreferences(
|
|
MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
|
|
String path = prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null);
|
|
if (path != null) {
|
|
return new File(path);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Requests the download of a file or set of files that the carrier has indicated to be
|
|
* available.
|
|
*
|
|
* 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 MbmsDownloadSession#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY} and store that as the temp
|
|
* file root directory.
|
|
*
|
|
* If the {@link DownloadRequest} has a destination that is not on the same filesystem as the
|
|
* temp file directory provided via {@link #getTempFileRootDirectory()}, an
|
|
* {@link IllegalArgumentException} will be thrown.
|
|
*
|
|
* Asynchronous errors through the callback may include any error not specific to the
|
|
* streaming use-case.
|
|
*
|
|
* If no error is delivered via the callback after calling this method, that means that the
|
|
* middleware has successfully started the download or scheduled the download, if the download
|
|
* is at a future time.
|
|
* @param request The request that specifies what should be downloaded.
|
|
*/
|
|
public void download(@NonNull DownloadRequest request) {
|
|
IMbmsDownloadService downloadService = mService.get();
|
|
if (downloadService == null) {
|
|
throw new IllegalStateException("Middleware not yet 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(),
|
|
DEFAULT_TOP_LEVEL_TEMP_DIRECTORY);
|
|
tempRootDirectory.mkdirs();
|
|
setTempFileRootDirectory(tempRootDirectory);
|
|
}
|
|
|
|
checkDownloadRequestDestination(request);
|
|
|
|
try {
|
|
int result = downloadService.download(request);
|
|
if (result == MbmsErrors.SUCCESS) {
|
|
writeDownloadRequestToken(request);
|
|
} else {
|
|
if (result == MbmsErrors.UNKNOWN) {
|
|
// Unbind and throw an obvious error
|
|
close();
|
|
throw new IllegalStateException("Middleware must not return an unknown"
|
|
+ " error code");
|
|
}
|
|
sendErrorToApp(result, null);
|
|
}
|
|
} catch (RemoteException e) {
|
|
mService.set(null);
|
|
sIsInitialized.set(false);
|
|
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a list of pending {@link DownloadRequest}s that originated from this application.
|
|
* A pending request is one that was issued via
|
|
* {@link #download(DownloadRequest)} but not cancelled through
|
|
* {@link #cancelDownload(DownloadRequest)}.
|
|
* @return A list, possibly empty, of {@link DownloadRequest}s
|
|
*/
|
|
public @NonNull List<DownloadRequest> listPendingDownloads() {
|
|
IMbmsDownloadService downloadService = mService.get();
|
|
if (downloadService == null) {
|
|
throw new IllegalStateException("Middleware not yet bound");
|
|
}
|
|
|
|
try {
|
|
return downloadService.listPendingDownloads(mSubscriptionId);
|
|
} catch (RemoteException e) {
|
|
mService.set(null);
|
|
sIsInitialized.set(false);
|
|
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
|
|
return Collections.emptyList();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registers a download status listener for a {@link DownloadRequest} previously requested via
|
|
* {@link #download(DownloadRequest)}. This callback will only be called as long as both this
|
|
* app and the middleware are both running -- if either one stops, no further calls on the
|
|
* provided {@link DownloadStatusListener} will be enqueued.
|
|
*
|
|
* If the middleware is not aware of the specified download request,
|
|
* this method will throw an {@link IllegalArgumentException}.
|
|
*
|
|
* If the operation encountered an error, the error code will be delivered via
|
|
* {@link MbmsDownloadSessionCallback#onError}.
|
|
*
|
|
* Repeated calls to this method for the same {@link DownloadRequest} will replace the
|
|
* previously registered listener.
|
|
*
|
|
* @param request The {@link DownloadRequest} that you want updates on.
|
|
* @param executor The {@link Executor} on which calls to {@code listener } should be executed.
|
|
* @param listener The listener that should be called when the middleware has information to
|
|
* share on the status download.
|
|
*/
|
|
public void addStatusListener(@NonNull DownloadRequest request,
|
|
@NonNull Executor executor, @NonNull DownloadStatusListener listener) {
|
|
IMbmsDownloadService downloadService = mService.get();
|
|
if (downloadService == null) {
|
|
throw new IllegalStateException("Middleware not yet bound");
|
|
}
|
|
|
|
InternalDownloadStatusListener internalListener =
|
|
new InternalDownloadStatusListener(listener, executor);
|
|
|
|
try {
|
|
int result = downloadService.addStatusListener(request, internalListener);
|
|
if (result == MbmsErrors.UNKNOWN) {
|
|
// Unbind and throw an obvious error
|
|
close();
|
|
throw new IllegalStateException("Middleware must not return an unknown error code");
|
|
}
|
|
if (result != MbmsErrors.SUCCESS) {
|
|
if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
|
|
throw new IllegalArgumentException("Unknown download request.");
|
|
}
|
|
sendErrorToApp(result, null);
|
|
return;
|
|
}
|
|
} catch (RemoteException e) {
|
|
mService.set(null);
|
|
sIsInitialized.set(false);
|
|
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
|
|
return;
|
|
}
|
|
mInternalDownloadStatusListeners.put(listener, internalListener);
|
|
}
|
|
|
|
/**
|
|
* Un-register a listener previously registered via
|
|
* {@link #addStatusListener(DownloadRequest, Executor, DownloadStatusListener)}. After
|
|
* this method is called, no further calls will be enqueued on the {@link Executor}
|
|
* provided upon registration, even if this method throws an exception.
|
|
*
|
|
* If the middleware is not aware of the specified download request,
|
|
* this method will throw an {@link IllegalArgumentException}.
|
|
*
|
|
* If the operation encountered an error, the error code will be delivered via
|
|
* {@link MbmsDownloadSessionCallback#onError}.
|
|
*
|
|
* @param request The {@link DownloadRequest} provided during registration
|
|
* @param listener The listener provided during registration.
|
|
*/
|
|
public void removeStatusListener(@NonNull DownloadRequest request,
|
|
@NonNull DownloadStatusListener listener) {
|
|
try {
|
|
IMbmsDownloadService downloadService = mService.get();
|
|
if (downloadService == null) {
|
|
throw new IllegalStateException("Middleware not yet bound");
|
|
}
|
|
|
|
InternalDownloadStatusListener internalListener =
|
|
mInternalDownloadStatusListeners.get(listener);
|
|
if (internalListener == null) {
|
|
throw new IllegalArgumentException("Provided listener was never registered");
|
|
}
|
|
|
|
try {
|
|
int result = downloadService.removeStatusListener(request, internalListener);
|
|
if (result == MbmsErrors.UNKNOWN) {
|
|
// Unbind and throw an obvious error
|
|
close();
|
|
throw new IllegalStateException("Middleware must not return an"
|
|
+ " unknown error code");
|
|
}
|
|
if (result != MbmsErrors.SUCCESS) {
|
|
if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
|
|
throw new IllegalArgumentException("Unknown download request.");
|
|
}
|
|
sendErrorToApp(result, null);
|
|
return;
|
|
}
|
|
} catch (RemoteException e) {
|
|
mService.set(null);
|
|
sIsInitialized.set(false);
|
|
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
|
|
return;
|
|
}
|
|
} finally {
|
|
InternalDownloadStatusListener internalCallback =
|
|
mInternalDownloadStatusListeners.remove(listener);
|
|
if (internalCallback != null) {
|
|
internalCallback.stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registers a progress listener for a {@link DownloadRequest} previously requested via
|
|
* {@link #download(DownloadRequest)}. This listener will only be called as long as both this
|
|
* app and the middleware are both running -- if either one stops, no further calls on the
|
|
* provided {@link DownloadProgressListener} will be enqueued.
|
|
*
|
|
* If the middleware is not aware of the specified download request,
|
|
* this method will throw an {@link IllegalArgumentException}.
|
|
*
|
|
* If the operation encountered an error, the error code will be delivered via
|
|
* {@link MbmsDownloadSessionCallback#onError}.
|
|
*
|
|
* Repeated calls to this method for the same {@link DownloadRequest} will replace the
|
|
* previously registered listener.
|
|
*
|
|
* @param request The {@link DownloadRequest} that you want updates on.
|
|
* @param executor The {@link Executor} on which calls to {@code listener} should be executed.
|
|
* @param listener The listener that should be called when the middleware has information to
|
|
* share on the progress of the download.
|
|
*/
|
|
public void addProgressListener(@NonNull DownloadRequest request,
|
|
@NonNull Executor executor, @NonNull DownloadProgressListener listener) {
|
|
IMbmsDownloadService downloadService = mService.get();
|
|
if (downloadService == null) {
|
|
throw new IllegalStateException("Middleware not yet bound");
|
|
}
|
|
|
|
InternalDownloadProgressListener internalListener =
|
|
new InternalDownloadProgressListener(listener, executor);
|
|
|
|
try {
|
|
int result = downloadService.addProgressListener(request, internalListener);
|
|
if (result == MbmsErrors.UNKNOWN) {
|
|
// Unbind and throw an obvious error
|
|
close();
|
|
throw new IllegalStateException("Middleware must not return an unknown error code");
|
|
}
|
|
if (result != MbmsErrors.SUCCESS) {
|
|
if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
|
|
throw new IllegalArgumentException("Unknown download request.");
|
|
}
|
|
sendErrorToApp(result, null);
|
|
return;
|
|
}
|
|
} catch (RemoteException e) {
|
|
mService.set(null);
|
|
sIsInitialized.set(false);
|
|
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
|
|
return;
|
|
}
|
|
mInternalDownloadProgressListeners.put(listener, internalListener);
|
|
}
|
|
|
|
/**
|
|
* Un-register a listener previously registered via
|
|
* {@link #addProgressListener(DownloadRequest, Executor, DownloadProgressListener)}. After
|
|
* this method is called, no further callbacks will be enqueued on the {@link Handler}
|
|
* provided upon registration, even if this method throws an exception.
|
|
*
|
|
* If the middleware is not aware of the specified download request,
|
|
* this method will throw an {@link IllegalArgumentException}.
|
|
*
|
|
* If the operation encountered an error, the error code will be delivered via
|
|
* {@link MbmsDownloadSessionCallback#onError}.
|
|
*
|
|
* @param request The {@link DownloadRequest} provided during registration
|
|
* @param listener The listener provided during registration.
|
|
*/
|
|
public void removeProgressListener(@NonNull DownloadRequest request,
|
|
@NonNull DownloadProgressListener listener) {
|
|
try {
|
|
IMbmsDownloadService downloadService = mService.get();
|
|
if (downloadService == null) {
|
|
throw new IllegalStateException("Middleware not yet bound");
|
|
}
|
|
|
|
InternalDownloadProgressListener internalListener =
|
|
mInternalDownloadProgressListeners.get(listener);
|
|
if (internalListener == null) {
|
|
throw new IllegalArgumentException("Provided listener was never registered");
|
|
}
|
|
|
|
try {
|
|
int result = downloadService.removeProgressListener(request, internalListener);
|
|
if (result == MbmsErrors.UNKNOWN) {
|
|
// Unbind and throw an obvious error
|
|
close();
|
|
throw new IllegalStateException("Middleware must not"
|
|
+ " return an unknown error code");
|
|
}
|
|
if (result != MbmsErrors.SUCCESS) {
|
|
if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
|
|
throw new IllegalArgumentException("Unknown download request.");
|
|
}
|
|
sendErrorToApp(result, null);
|
|
return;
|
|
}
|
|
} catch (RemoteException e) {
|
|
mService.set(null);
|
|
sIsInitialized.set(false);
|
|
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
|
|
return;
|
|
}
|
|
} finally {
|
|
InternalDownloadProgressListener internalCallback =
|
|
mInternalDownloadProgressListeners.remove(listener);
|
|
if (internalCallback != null) {
|
|
internalCallback.stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attempts to cancel the specified {@link DownloadRequest}.
|
|
*
|
|
* If the operation encountered an error, the error code will be delivered via
|
|
* {@link MbmsDownloadSessionCallback#onError}.
|
|
*
|
|
* @param downloadRequest The download request that you wish to cancel.
|
|
*/
|
|
public void cancelDownload(@NonNull DownloadRequest downloadRequest) {
|
|
IMbmsDownloadService downloadService = mService.get();
|
|
if (downloadService == null) {
|
|
throw new IllegalStateException("Middleware not yet bound");
|
|
}
|
|
|
|
try {
|
|
int result = downloadService.cancelDownload(downloadRequest);
|
|
if (result == MbmsErrors.UNKNOWN) {
|
|
// Unbind and throw an obvious error
|
|
close();
|
|
throw new IllegalStateException("Middleware must not return an unknown error code");
|
|
}
|
|
if (result != MbmsErrors.SUCCESS) {
|
|
sendErrorToApp(result, null);
|
|
} else {
|
|
deleteDownloadRequestToken(downloadRequest);
|
|
}
|
|
} catch (RemoteException e) {
|
|
mService.set(null);
|
|
sIsInitialized.set(false);
|
|
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Requests information about the state of a file pending download.
|
|
*
|
|
* The state will be delivered as a callback via
|
|
* {@link DownloadStatusListener#onStatusUpdated(DownloadRequest, FileInfo, int)}. If no such
|
|
* callback has been registered via
|
|
* {@link #addProgressListener(DownloadRequest, Executor, DownloadProgressListener)}, this
|
|
* method will be a no-op.
|
|
*
|
|
* If the middleware has no record of the
|
|
* file indicated by {@code fileInfo} being associated with {@code downloadRequest},
|
|
* an {@link IllegalArgumentException} will be thrown.
|
|
*
|
|
* @param downloadRequest The download request to query.
|
|
* @param fileInfo The particular file within the request to get information on.
|
|
*/
|
|
public void requestDownloadState(DownloadRequest downloadRequest, FileInfo fileInfo) {
|
|
IMbmsDownloadService downloadService = mService.get();
|
|
if (downloadService == null) {
|
|
throw new IllegalStateException("Middleware not yet bound");
|
|
}
|
|
|
|
try {
|
|
int result = downloadService.requestDownloadState(downloadRequest, fileInfo);
|
|
if (result == MbmsErrors.UNKNOWN) {
|
|
// Unbind and throw an obvious error
|
|
close();
|
|
throw new IllegalStateException("Middleware must not return an unknown error code");
|
|
}
|
|
if (result != MbmsErrors.SUCCESS) {
|
|
if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
|
|
throw new IllegalArgumentException("Unknown download request.");
|
|
}
|
|
if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_FILE_INFO) {
|
|
throw new IllegalArgumentException("Unknown file.");
|
|
}
|
|
sendErrorToApp(result, null);
|
|
}
|
|
} catch (RemoteException e) {
|
|
mService.set(null);
|
|
sIsInitialized.set(false);
|
|
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets the middleware's knowledge of previously-downloaded files in this download request.
|
|
*
|
|
* Normally, the middleware keeps track of the hashes of downloaded files and won't re-download
|
|
* files whose server-reported hash matches one of the already-downloaded files. This means
|
|
* that if the file is accidentally deleted by the user or by the app, the middleware will
|
|
* not try to download it again.
|
|
* This method will reset the middleware's cache of hashes for the provided
|
|
* {@link DownloadRequest}, so that previously downloaded content will be downloaded again
|
|
* when available.
|
|
* This will not interrupt in-progress downloads.
|
|
*
|
|
* This is distinct from cancelling and re-issuing the download request -- if you cancel and
|
|
* re-issue, the middleware will not clear its cache of download state information.
|
|
*
|
|
* If the middleware is not aware of the specified download request, an
|
|
* {@link IllegalArgumentException} will be thrown.
|
|
*
|
|
* @param downloadRequest The request to re-download files for.
|
|
*/
|
|
public void resetDownloadKnowledge(DownloadRequest downloadRequest) {
|
|
IMbmsDownloadService downloadService = mService.get();
|
|
if (downloadService == null) {
|
|
throw new IllegalStateException("Middleware not yet bound");
|
|
}
|
|
|
|
try {
|
|
int result = downloadService.resetDownloadKnowledge(downloadRequest);
|
|
if (result == MbmsErrors.UNKNOWN) {
|
|
// Unbind and throw an obvious error
|
|
close();
|
|
throw new IllegalStateException("Middleware must not return an unknown error code");
|
|
}
|
|
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);
|
|
sIsInitialized.set(false);
|
|
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Terminates this instance.
|
|
*
|
|
* After this method returns,
|
|
* no further callbacks originating from the middleware will be enqueued on the provided
|
|
* instance of {@link MbmsDownloadSessionCallback}, but callbacks that have already been
|
|
* enqueued will still be delivered.
|
|
*
|
|
* It is safe to call {@link #create(Context, Executor, int, MbmsDownloadSessionCallback)} to
|
|
* obtain another instance of {@link MbmsDownloadSession} immediately after this method
|
|
* returns.
|
|
*
|
|
* May throw an {@link IllegalStateException}
|
|
*/
|
|
@Override
|
|
public void close() {
|
|
try {
|
|
IMbmsDownloadService downloadService = mService.get();
|
|
if (downloadService == null || mServiceConnection == null) {
|
|
Log.i(LOG_TAG, "Service already dead");
|
|
return;
|
|
}
|
|
downloadService.dispose(mSubscriptionId);
|
|
mContext.unbindService(mServiceConnection);
|
|
} catch (RemoteException e) {
|
|
// Ignore
|
|
Log.i(LOG_TAG, "Remote exception while disposing of service");
|
|
} finally {
|
|
mService.set(null);
|
|
sIsInitialized.set(false);
|
|
mServiceConnection = null;
|
|
mInternalCallback.stop();
|
|
}
|
|
}
|
|
|
|
private void writeDownloadRequestToken(DownloadRequest request) {
|
|
File token = getDownloadRequestTokenPath(request);
|
|
if (!token.getParentFile().exists()) {
|
|
token.getParentFile().mkdirs();
|
|
}
|
|
if (token.exists()) {
|
|
Log.w(LOG_TAG, "Download token " + token.getName() + " already exists");
|
|
return;
|
|
}
|
|
try {
|
|
if (!token.createNewFile()) {
|
|
throw new RuntimeException("Failed to create download token for request "
|
|
+ request + ". Token location is " + token.getPath());
|
|
}
|
|
} catch (IOException e) {
|
|
throw new RuntimeException("Failed to create download token for request " + request
|
|
+ " due to IOException " + e + ". Attempted to write to " + token.getPath());
|
|
}
|
|
}
|
|
|
|
private void deleteDownloadRequestToken(DownloadRequest request) {
|
|
File token = getDownloadRequestTokenPath(request);
|
|
if (!token.isFile()) {
|
|
Log.w(LOG_TAG, "Attempting to delete non-existent download token at " + token);
|
|
return;
|
|
}
|
|
if (!token.delete()) {
|
|
Log.w(LOG_TAG, "Couldn't delete download token at " + token);
|
|
}
|
|
}
|
|
|
|
private void checkDownloadRequestDestination(DownloadRequest request) {
|
|
File downloadRequestDestination = new File(request.getDestinationUri().getPath());
|
|
if (!downloadRequestDestination.isDirectory()) {
|
|
throw new IllegalArgumentException("The destination path must be a directory");
|
|
}
|
|
// Check if the request destination is okay to use by attempting to rename an empty
|
|
// file to there.
|
|
File testFile = new File(MbmsTempFileProvider.getEmbmsTempFileDir(mContext),
|
|
DESTINATION_SANITY_CHECK_FILE_NAME);
|
|
File testFileDestination = new File(downloadRequestDestination,
|
|
DESTINATION_SANITY_CHECK_FILE_NAME);
|
|
|
|
try {
|
|
if (!testFile.exists()) {
|
|
testFile.createNewFile();
|
|
}
|
|
if (!testFile.renameTo(testFileDestination)) {
|
|
throw new IllegalArgumentException("Destination provided in the download request " +
|
|
"is invalid -- files in the temp file directory cannot be directly moved " +
|
|
"there.");
|
|
}
|
|
} catch (IOException e) {
|
|
throw new IllegalStateException("Got IOException while testing out the destination: "
|
|
+ e);
|
|
} finally {
|
|
testFile.delete();
|
|
testFileDestination.delete();
|
|
}
|
|
}
|
|
|
|
private File getDownloadRequestTokenPath(DownloadRequest request) {
|
|
File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForService(mContext,
|
|
request.getFileServiceId());
|
|
String downloadTokenFileName = request.getHash()
|
|
+ MbmsDownloadReceiver.DOWNLOAD_TOKEN_SUFFIX;
|
|
return new File(tempFileLocation, downloadTokenFileName);
|
|
}
|
|
|
|
private void sendErrorToApp(int errorCode, String message) {
|
|
mInternalCallback.onError(errorCode, message);
|
|
}
|
|
}
|