Merge "Embms download setup"
This commit is contained in:
@@ -16,19 +16,24 @@
|
||||
|
||||
package android.telephony;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
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.MbmsException;
|
||||
import android.telephony.mbms.MbmsUtils;
|
||||
import android.telephony.mbms.vendor.IMbmsDownloadService;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
||||
|
||||
@@ -36,6 +41,8 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
||||
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
|
||||
@@ -76,15 +83,15 @@ public class MbmsDownloadManager {
|
||||
"android.telephony.mbms.action.CLEANUP";
|
||||
|
||||
/**
|
||||
* Integer extra indicating the result code of the download.
|
||||
* TODO: put in link to error list
|
||||
* TODO: future systemapi (here and and all extras)
|
||||
* 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_INFO = "android.telephony.mbms.extra.INFO";
|
||||
|
||||
@@ -143,11 +150,23 @@ public class MbmsDownloadManager {
|
||||
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 static final long BIND_TIMEOUT_MS = 3000;
|
||||
|
||||
private final Context mContext;
|
||||
private int mSubId = INVALID_SUBSCRIPTION_ID;
|
||||
|
||||
@@ -199,12 +218,31 @@ public class MbmsDownloadManager {
|
||||
}
|
||||
|
||||
private void bindAndInitialize() throws MbmsException {
|
||||
// TODO: bind
|
||||
try {
|
||||
mService.initialize(mDownloadAppName, mSubId, mCallback);
|
||||
} catch (RemoteException e) {
|
||||
throw new MbmsException(0); // TODO: proper error code
|
||||
}
|
||||
// TODO: fold binding for download and streaming into a common utils class.
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
ServiceConnection bindListener = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
mService = IMbmsDownloadService.Stub.asInterface(service);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
mService = null;
|
||||
}
|
||||
};
|
||||
|
||||
Intent bindIntent = new Intent();
|
||||
bindIntent.setComponent(MbmsUtils.toComponentName(
|
||||
MbmsUtils.getMiddlewareServiceInfo(mContext, MBMS_DOWNLOAD_SERVICE_ACTION)));
|
||||
|
||||
// Kick off the binding, and synchronously wait until binding is complete
|
||||
mContext.bindService(bindIntent, bindListener, Context.BIND_AUTO_CREATE);
|
||||
|
||||
MbmsUtils.waitOnLatchWithTimeout(latch, BIND_TIMEOUT_MS);
|
||||
|
||||
// TODO: initialize
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,6 +283,11 @@ public class MbmsDownloadManager {
|
||||
*/
|
||||
public DownloadRequest download(DownloadRequest request, IDownloadCallback listener) {
|
||||
request.setAppName(mDownloadAppName);
|
||||
try {
|
||||
mService.download(request, listener);
|
||||
} catch (RemoteException e) {
|
||||
mService = null;
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.telephony.mbms.MbmsException;
|
||||
import android.telephony.mbms.MbmsStreamingManagerCallback;
|
||||
import android.telephony.mbms.MbmsUtils;
|
||||
import android.telephony.mbms.StreamingService;
|
||||
import android.telephony.mbms.StreamingServiceCallback;
|
||||
import android.telephony.mbms.StreamingServiceInfo;
|
||||
@@ -62,7 +63,9 @@ public class MbmsStreamingManager {
|
||||
Log.i(LOG_TAG, String.format("Connected to service %s", name));
|
||||
synchronized (MbmsStreamingManager.this) {
|
||||
mService = IMbmsStreamingService.Stub.asInterface(service);
|
||||
mServiceListeners.forEach(ServiceListener::onServiceConnected);
|
||||
for (ServiceListener l : mServiceListeners) {
|
||||
l.onServiceConnected();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,10 +75,13 @@ public class MbmsStreamingManager {
|
||||
Log.i(LOG_TAG, String.format("Disconnected from service %s", name));
|
||||
synchronized (MbmsStreamingManager.this) {
|
||||
mService = null;
|
||||
mServiceListeners.forEach(ServiceListener::onServiceDisconnected);
|
||||
for (ServiceListener l : mServiceListeners) {
|
||||
l.onServiceDisconnected();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private List<ServiceListener> mServiceListeners = new LinkedList<>();
|
||||
|
||||
private MbmsStreamingManagerCallback mCallbackToApp;
|
||||
@@ -218,22 +224,6 @@ public class MbmsStreamingManager {
|
||||
}
|
||||
|
||||
private void bindAndInitialize() throws MbmsException {
|
||||
// Query for the proper service
|
||||
PackageManager packageManager = mContext.getPackageManager();
|
||||
Intent queryIntent = new Intent();
|
||||
queryIntent.setAction(MBMS_STREAMING_SERVICE_ACTION);
|
||||
List<ResolveInfo> streamingServices = packageManager.queryIntentServices(queryIntent,
|
||||
PackageManager.MATCH_SYSTEM_ONLY);
|
||||
|
||||
if (streamingServices == null || streamingServices.size() == 0) {
|
||||
throw new MbmsException(
|
||||
MbmsException.ERROR_NO_SERVICE_INSTALLED);
|
||||
}
|
||||
if (streamingServices.size() > 1) {
|
||||
throw new MbmsException(
|
||||
MbmsException.ERROR_MULTIPLE_SERVICES_INSTALLED);
|
||||
}
|
||||
|
||||
// Kick off the binding, and synchronously wait until binding is complete
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
ServiceListener bindListener = new ServiceListener() {
|
||||
@@ -252,13 +242,14 @@ public class MbmsStreamingManager {
|
||||
}
|
||||
|
||||
Intent bindIntent = new Intent();
|
||||
bindIntent.setComponent(streamingServices.get(0).getComponentInfo().getComponentName());
|
||||
bindIntent.setComponent(MbmsUtils.toComponentName(
|
||||
MbmsUtils.getMiddlewareServiceInfo(mContext, MBMS_STREAMING_SERVICE_ACTION)));
|
||||
|
||||
mContext.bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
|
||||
|
||||
waitOnLatchWithTimeout(latch, BIND_TIMEOUT_MS);
|
||||
MbmsUtils.waitOnLatchWithTimeout(latch, BIND_TIMEOUT_MS);
|
||||
|
||||
// Remove the listener and call the initialization method through the interface.
|
||||
// Remove the listener and call the initialization method through the interface.
|
||||
synchronized (this) {
|
||||
mServiceListeners.remove(bindListener);
|
||||
|
||||
@@ -279,17 +270,4 @@ public class MbmsStreamingManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static void waitOnLatchWithTimeout(CountDownLatch l, long timeoutMs) {
|
||||
long endTime = System.currentTimeMillis() + timeoutMs;
|
||||
while (System.currentTimeMillis() < endTime) {
|
||||
try {
|
||||
l.await(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
// keep waiting
|
||||
}
|
||||
if (l.getCount() <= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
365
telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
Normal file
365
telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
Normal file
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.telephony.MbmsDownloadManager;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public class MbmsDownloadReceiver extends BroadcastReceiver {
|
||||
private static final String LOG_TAG = "MbmsDownloadReceiver";
|
||||
private static final String TEMP_FILE_SUFFIX = ".embms.temp";
|
||||
private static final int MAX_TEMP_FILE_RETRIES = 5;
|
||||
|
||||
public static final String MBMS_FILE_PROVIDER_META_DATA_KEY = "mbms-file-provider-authority";
|
||||
|
||||
private String mFileProviderAuthorityCache = null;
|
||||
private String mMiddlewarePackageNameCache = null;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (!verifyIntentContents(intent)) {
|
||||
setResultCode(1 /* TODO: define error constants */);
|
||||
return;
|
||||
}
|
||||
|
||||
if (MbmsDownloadManager.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
|
||||
moveDownloadedFile(context, intent);
|
||||
cleanupPostMove(context, intent);
|
||||
} else if (MbmsDownloadManager.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
|
||||
generateTempFiles(context, intent);
|
||||
}
|
||||
// TODO: Add handling for ACTION_CLEANUP
|
||||
}
|
||||
|
||||
private boolean verifyIntentContents(Intent intent) {
|
||||
if (MbmsDownloadManager.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
|
||||
if (!intent.hasExtra(MbmsDownloadManager.EXTRA_RESULT)) {
|
||||
Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
|
||||
return false;
|
||||
}
|
||||
if (!intent.hasExtra(MbmsDownloadManager.EXTRA_REQUEST)) {
|
||||
Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
|
||||
return false;
|
||||
}
|
||||
if (!intent.hasExtra(MbmsDownloadManager.EXTRA_INFO)) {
|
||||
Log.w(LOG_TAG, "Download result did not include the associated file info. " +
|
||||
"Ignoring.");
|
||||
return false;
|
||||
}
|
||||
if (!intent.hasExtra(MbmsDownloadManager.EXTRA_FINAL_URI)) {
|
||||
Log.w(LOG_TAG, "Download result did not include the path to the final " +
|
||||
"temp file. Ignoring.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else if (MbmsDownloadManager.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
|
||||
if (!intent.hasExtra(MbmsDownloadManager.EXTRA_REQUEST)) {
|
||||
Log.w(LOG_TAG, "Temp file request not include the associated request. Ignoring.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Log.w(LOG_TAG, "Received intent with unknown action: " + intent.getAction());
|
||||
return false;
|
||||
}
|
||||
|
||||
private void moveDownloadedFile(Context context, Intent intent) {
|
||||
DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
|
||||
// TODO: check request against token
|
||||
Intent intentForApp = request.getIntentForApp();
|
||||
|
||||
int result = intent.getIntExtra(MbmsDownloadManager.EXTRA_RESULT,
|
||||
MbmsDownloadManager.RESULT_CANCELLED);
|
||||
intentForApp.putExtra(MbmsDownloadManager.EXTRA_RESULT, result);
|
||||
|
||||
if (result != MbmsDownloadManager.RESULT_SUCCESSFUL) {
|
||||
Log.i(LOG_TAG, "Download request indicated a failed download. Aborting.");
|
||||
context.sendBroadcast(intentForApp);
|
||||
return;
|
||||
}
|
||||
|
||||
Uri destinationUri = request.getDestinationUri();
|
||||
Uri finalTempFile = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FINAL_URI);
|
||||
if (!verifyTempFilePath(context, request, finalTempFile)) {
|
||||
Log.w(LOG_TAG, "Download result specified an invalid temp file " + finalTempFile);
|
||||
setResultCode(1);
|
||||
return;
|
||||
}
|
||||
|
||||
String relativePath = calculateDestinationFileRelativePath(request,
|
||||
(FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_INFO));
|
||||
|
||||
if (!moveTempFile(finalTempFile, destinationUri, relativePath)) {
|
||||
Log.w(LOG_TAG, "Failed to move temp file to final destination");
|
||||
setResultCode(1);
|
||||
}
|
||||
|
||||
context.sendBroadcast(intentForApp);
|
||||
setResultCode(0);
|
||||
}
|
||||
|
||||
private void cleanupPostMove(Context context, Intent intent) {
|
||||
// TODO: account for in-use temp files
|
||||
DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
|
||||
if (request == null) {
|
||||
Log.w(LOG_TAG, "Intent does not include a DownloadRequest. Ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Uri> tempFiles = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_TEMP_LIST);
|
||||
if (tempFiles == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Uri tempFileUri : tempFiles) {
|
||||
if (verifyTempFilePath(context, request, tempFileUri)) {
|
||||
File tempFile = new File(tempFileUri.getSchemeSpecificPart());
|
||||
tempFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateTempFiles(Context context, Intent intent) {
|
||||
// TODO: update pursuant to final decision on temp file locations
|
||||
DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
|
||||
if (request == null) {
|
||||
Log.w(LOG_TAG, "Temp file request did not include the associated request. Ignoring.");
|
||||
setResultCode(1 /* TODO: define error constants */);
|
||||
return;
|
||||
}
|
||||
int fdCount = intent.getIntExtra(MbmsDownloadManager.EXTRA_FD_COUNT, 0);
|
||||
List<Uri> pausedList = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_PAUSED_LIST);
|
||||
|
||||
if (fdCount == 0 && (pausedList == null || pausedList.size() == 0)) {
|
||||
Log.i(LOG_TAG, "No temp files actually requested. Ending.");
|
||||
setResultCode(0);
|
||||
setResultExtras(Bundle.EMPTY);
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<UriPathPair> freshTempFiles = generateFreshTempFiles(context, request, fdCount);
|
||||
ArrayList<UriPathPair> pausedFiles =
|
||||
generateUrisForPausedFiles(context, request, pausedList);
|
||||
|
||||
Bundle result = new Bundle();
|
||||
result.putParcelableArrayList(MbmsDownloadManager.EXTRA_FREE_URI_LIST, freshTempFiles);
|
||||
result.putParcelableArrayList(MbmsDownloadManager.EXTRA_PAUSED_URI_LIST, pausedFiles);
|
||||
setResultExtras(result);
|
||||
}
|
||||
|
||||
private ArrayList<UriPathPair> generateFreshTempFiles(Context context, DownloadRequest request,
|
||||
int freshFdCount) {
|
||||
File tempFileDir = getEmbmsTempFileDirForRequest(context, request);
|
||||
if (!tempFileDir.exists()) {
|
||||
tempFileDir.mkdirs();
|
||||
}
|
||||
|
||||
// Name the files with the template "N-UUID", where N is the request ID and UUID is a
|
||||
// random uuid.
|
||||
ArrayList<UriPathPair> result = new ArrayList<>(freshFdCount);
|
||||
for (int i = 0; i < freshFdCount; i++) {
|
||||
File tempFile = generateSingleTempFile(tempFileDir);
|
||||
if (tempFile == null) {
|
||||
setResultCode(2 /* TODO: define error constants */);
|
||||
Log.w(LOG_TAG, "Failed to generate a temp file. Moving on.");
|
||||
continue;
|
||||
}
|
||||
Uri fileUri = Uri.fromParts(ContentResolver.SCHEME_FILE, tempFile.getPath(), null);
|
||||
Uri contentUri = MbmsTempFileProvider.getUriForFile(
|
||||
context, getFileProviderAuthorityCached(context), tempFile);
|
||||
context.grantUriPermission(getMiddlewarePackageCached(context), contentUri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
result.add(new UriPathPair(fileUri, contentUri));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static File generateSingleTempFile(File tempFileDir) {
|
||||
int numTries = 0;
|
||||
while (numTries < MAX_TEMP_FILE_RETRIES) {
|
||||
numTries++;
|
||||
String fileName = UUID.randomUUID() + TEMP_FILE_SUFFIX;
|
||||
File tempFile = new File(tempFileDir, fileName);
|
||||
try {
|
||||
if (tempFile.createNewFile()) {
|
||||
return tempFile.getCanonicalFile();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private ArrayList<UriPathPair> generateUrisForPausedFiles(Context context,
|
||||
DownloadRequest request, List<Uri> pausedFiles) {
|
||||
if (pausedFiles == null) {
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
ArrayList<UriPathPair> result = new ArrayList<>(pausedFiles.size());
|
||||
|
||||
for (Uri fileUri : pausedFiles) {
|
||||
if (!verifyTempFilePath(context, request, fileUri)) {
|
||||
Log.w(LOG_TAG, "Supplied file " + fileUri + " is not a valid temp file to resume");
|
||||
setResultCode(2 /* TODO: define error codes */);
|
||||
continue;
|
||||
}
|
||||
File tempFile = new File(fileUri.getSchemeSpecificPart());
|
||||
if (!tempFile.exists()) {
|
||||
Log.w(LOG_TAG, "Supplied file " + fileUri + " does not exist.");
|
||||
setResultCode(2 /* TODO: define error codes */);
|
||||
continue;
|
||||
}
|
||||
Uri contentUri = MbmsTempFileProvider.getUriForFile(
|
||||
context, getFileProviderAuthorityCached(context), tempFile);
|
||||
context.grantUriPermission(getMiddlewarePackageCached(context), contentUri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
|
||||
result.add(new UriPathPair(fileUri, contentUri));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String calculateDestinationFileRelativePath(DownloadRequest request,
|
||||
FileInfo info) {
|
||||
// TODO: determine whether this is actually the path determination scheme we want to use
|
||||
List<String> filePathComponents = info.uri.getPathSegments();
|
||||
List<String> requestPathComponents = request.getSourceUri().getPathSegments();
|
||||
Iterator<String> filePathIter = filePathComponents.iterator();
|
||||
Iterator<String> requestPathIter = requestPathComponents.iterator();
|
||||
|
||||
LinkedList<String> relativePathComponents = new LinkedList<>();
|
||||
while (filePathIter.hasNext()) {
|
||||
String currFilePathComponent = filePathIter.next();
|
||||
if (requestPathIter.hasNext()) {
|
||||
String requestFilePathComponent = requestPathIter.next();
|
||||
if (requestFilePathComponent.equals(currFilePathComponent)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
relativePathComponents.add(currFilePathComponent);
|
||||
}
|
||||
return String.join("/", relativePathComponents);
|
||||
}
|
||||
|
||||
private static boolean moveTempFile(Uri fromPath, Uri toPath, String relativePath) {
|
||||
if (!ContentResolver.SCHEME_FILE.equals(fromPath.getScheme())) {
|
||||
Log.w(LOG_TAG, "Moving source uri " + fromPath+ " does not have a file scheme");
|
||||
return false;
|
||||
}
|
||||
if (!ContentResolver.SCHEME_FILE.equals(toPath.getScheme())) {
|
||||
Log.w(LOG_TAG, "Moving destination uri " + toPath + " does not have a file scheme");
|
||||
return false;
|
||||
}
|
||||
|
||||
File fromFile = new File(fromPath.getSchemeSpecificPart());
|
||||
File toFile = new File(toPath.getSchemeSpecificPart(), relativePath);
|
||||
toFile.getParentFile().mkdirs();
|
||||
|
||||
// TODO: This may not work if the two files are on different filesystems. Should we
|
||||
// enforce that the temp file storage and the permanent storage are both in the same fs?
|
||||
return fromFile.renameTo(toFile);
|
||||
}
|
||||
|
||||
private static boolean verifyTempFilePath(Context context, DownloadRequest request,
|
||||
Uri filePath) {
|
||||
// TODO: modify pursuant to final decision on temp file path scheme
|
||||
if (!ContentResolver.SCHEME_FILE.equals(filePath.getScheme())) {
|
||||
Log.w(LOG_TAG, "Uri " + filePath + " does not have a file scheme");
|
||||
return false;
|
||||
}
|
||||
|
||||
String path = filePath.getSchemeSpecificPart();
|
||||
File tempFile = new File(path);
|
||||
if (!tempFile.exists()) {
|
||||
Log.w(LOG_TAG, "File at " + path + " does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!MbmsUtils.isContainedIn(getEmbmsTempFileDirForRequest(context, request), tempFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a File linked to the directory used to store temp files for this request
|
||||
*/
|
||||
private static File getEmbmsTempFileDirForRequest(Context context, DownloadRequest request) {
|
||||
File embmsTempFileDir = MbmsTempFileProvider.getEmbmsTempFileDir(
|
||||
context, getFileProviderAuthority(context));
|
||||
|
||||
// TODO: better naming scheme for temp file dirs
|
||||
String tempFileDirName = String.valueOf(request.getFileServiceInfo().getServiceId());
|
||||
return new File(embmsTempFileDir, tempFileDirName);
|
||||
}
|
||||
|
||||
private String getFileProviderAuthorityCached(Context context) {
|
||||
if (mFileProviderAuthorityCache != null) {
|
||||
return mFileProviderAuthorityCache;
|
||||
}
|
||||
|
||||
mFileProviderAuthorityCache = getFileProviderAuthority(context);
|
||||
return mFileProviderAuthorityCache;
|
||||
}
|
||||
|
||||
private static String getFileProviderAuthority(Context context) {
|
||||
ApplicationInfo appInfo;
|
||||
try {
|
||||
appInfo = context.getPackageManager()
|
||||
.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
throw new RuntimeException("Package manager couldn't find " + context.getPackageName());
|
||||
}
|
||||
String authority = appInfo.metaData.getString(MBMS_FILE_PROVIDER_META_DATA_KEY);
|
||||
if (authority == null) {
|
||||
throw new RuntimeException("Must declare the file provider authority as meta data");
|
||||
}
|
||||
return authority;
|
||||
}
|
||||
|
||||
private String getMiddlewarePackageCached(Context context) {
|
||||
if (mMiddlewarePackageNameCache == null) {
|
||||
mMiddlewarePackageNameCache = MbmsUtils.getMiddlewareServiceInfo(context,
|
||||
MbmsDownloadManager.MBMS_DOWNLOAD_SERVICE_ACTION).packageName;
|
||||
}
|
||||
return mMiddlewarePackageNameCache;
|
||||
}
|
||||
}
|
||||
193
telephony/java/android/telephony/mbms/MbmsTempFileProvider.java
Normal file
193
telephony/java/android/telephony/mbms/MbmsTempFileProvider.java
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ProviderInfo;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public class MbmsTempFileProvider extends ContentProvider {
|
||||
public static final String META_DATA_USE_EXTERNAL_STORAGE = "use-external-storage";
|
||||
public static final String META_DATA_TEMP_FILE_DIRECTORY = "temp-file-path";
|
||||
public static final String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot";
|
||||
|
||||
private String mAuthority;
|
||||
private Context mContext;
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(@NonNull Uri uri, @Nullable String[] projection,
|
||||
@Nullable String selection, @Nullable String[] selectionArgs,
|
||||
@Nullable String sortOrder) {
|
||||
throw new UnsupportedOperationException("No querying supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri) {
|
||||
// EMBMS temp files can contain arbitrary content.
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
|
||||
throw new UnsupportedOperationException("No inserting supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(@NonNull Uri uri, @Nullable String selection,
|
||||
@Nullable String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException("No deleting supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String
|
||||
selection, @Nullable String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException("No updating supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||
// ContentProvider has already checked granted permissions
|
||||
final File file = getFileForUri(mContext, mAuthority, uri);
|
||||
final int fileMode = ParcelFileDescriptor.parseMode(mode);
|
||||
return ParcelFileDescriptor.open(file, fileMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachInfo(Context context, ProviderInfo info) {
|
||||
super.attachInfo(context, info);
|
||||
|
||||
// Sanity check our security
|
||||
if (info.exported) {
|
||||
throw new SecurityException("Provider must not be exported");
|
||||
}
|
||||
if (!info.grantUriPermissions) {
|
||||
throw new SecurityException("Provider must grant uri permissions");
|
||||
}
|
||||
|
||||
mAuthority = info.authority;
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public static Uri getUriForFile(Context context, String authority, File file) {
|
||||
// Get the canonical path of the temp file
|
||||
String filePath;
|
||||
try {
|
||||
filePath = file.getCanonicalPath();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Could not get canonical path for file " + file);
|
||||
}
|
||||
|
||||
// Make sure the temp file is contained in the temp file directory as configured in the
|
||||
// manifest
|
||||
File tempFileDir = getEmbmsTempFileDir(context, authority);
|
||||
if (!MbmsUtils.isContainedIn(tempFileDir, file)) {
|
||||
throw new IllegalArgumentException("File " + file + " is not contained in the temp " +
|
||||
"file directory, which is " + tempFileDir);
|
||||
}
|
||||
|
||||
// Get the canonical path of the temp file directory
|
||||
String tempFileDirPath;
|
||||
try {
|
||||
tempFileDirPath = tempFileDir.getCanonicalPath();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(
|
||||
"Could not get canonical path for temp file root dir " + tempFileDir);
|
||||
}
|
||||
|
||||
// Start at first char of path under temp file directory
|
||||
String pathFragment;
|
||||
if (tempFileDirPath.endsWith("/")) {
|
||||
pathFragment = filePath.substring(tempFileDirPath.length());
|
||||
} else {
|
||||
pathFragment = filePath.substring(tempFileDirPath.length() + 1);
|
||||
}
|
||||
|
||||
String encodedPath = Uri.encode(pathFragment);
|
||||
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority(authority).encodedPath(encodedPath).build();
|
||||
}
|
||||
|
||||
public static File getFileForUri(Context context, String authority, Uri uri)
|
||||
throws FileNotFoundException {
|
||||
if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
|
||||
throw new IllegalArgumentException("Uri must have scheme content");
|
||||
}
|
||||
|
||||
String relPath = Uri.decode(uri.getEncodedPath());
|
||||
File file;
|
||||
File tempFileDir;
|
||||
|
||||
try {
|
||||
tempFileDir = getEmbmsTempFileDir(context, authority).getCanonicalFile();
|
||||
file = new File(tempFileDir, relPath).getCanonicalFile();
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException("Could not resolve paths");
|
||||
}
|
||||
|
||||
if (!file.getPath().startsWith(tempFileDir.getPath())) {
|
||||
throw new SecurityException("Resolved path jumped beyond configured root");
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a File for the directory used to store temp files for this app
|
||||
*/
|
||||
public static File getEmbmsTempFileDir(Context context, String authority) {
|
||||
Bundle metadata = getMetadata(context, authority);
|
||||
File parentDirectory;
|
||||
if (metadata.getBoolean(META_DATA_USE_EXTERNAL_STORAGE, false)) {
|
||||
parentDirectory = context.getExternalFilesDir(null);
|
||||
} else {
|
||||
parentDirectory = context.getFilesDir();
|
||||
}
|
||||
|
||||
String tmpFilePath = metadata.getString(META_DATA_TEMP_FILE_DIRECTORY);
|
||||
if (tmpFilePath == null) {
|
||||
tmpFilePath = DEFAULT_TOP_LEVEL_TEMP_DIRECTORY;
|
||||
}
|
||||
return new File(parentDirectory, tmpFilePath);
|
||||
}
|
||||
|
||||
private static Bundle getMetadata(Context context, String authority) {
|
||||
final ProviderInfo info = context.getPackageManager()
|
||||
.resolveContentProvider(authority, PackageManager.GET_META_DATA);
|
||||
return info.metaData;
|
||||
}
|
||||
}
|
||||
86
telephony/java/android/telephony/mbms/MbmsUtils.java
Normal file
86
telephony/java/android/telephony/mbms/MbmsUtils.java
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.*;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.telephony.MbmsDownloadManager;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public class MbmsUtils {
|
||||
private static final String LOG_TAG = "MbmsUtils";
|
||||
|
||||
public static boolean isContainedIn(File parent, File child) {
|
||||
try {
|
||||
String parentPath = parent.getCanonicalPath();
|
||||
String childPath = child.getCanonicalPath();
|
||||
return childPath.startsWith(parentPath);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to resolve canonical paths: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void waitOnLatchWithTimeout(CountDownLatch l, long timeoutMs) {
|
||||
long endTime = System.currentTimeMillis() + timeoutMs;
|
||||
while (System.currentTimeMillis() < endTime) {
|
||||
try {
|
||||
l.await(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
// keep waiting
|
||||
}
|
||||
if (l.getCount() <= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ComponentName toComponentName(ComponentInfo ci) {
|
||||
return new ComponentName(ci.packageName, ci.name);
|
||||
}
|
||||
|
||||
public static ServiceInfo getMiddlewareServiceInfo(Context context, String serviceAction) {
|
||||
// Query for the proper service
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
Intent queryIntent = new Intent();
|
||||
queryIntent.setAction(serviceAction);
|
||||
List<ResolveInfo> downloadServices = packageManager.queryIntentServices(queryIntent,
|
||||
PackageManager.MATCH_SYSTEM_ONLY);
|
||||
|
||||
if (downloadServices == null || downloadServices.size() == 0) {
|
||||
Log.w(LOG_TAG, "No download services found, cannot get service info");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (downloadServices.size() > 1) {
|
||||
Log.w(LOG_TAG, "More than one download service found, cannot get unique service");
|
||||
return null;
|
||||
}
|
||||
return downloadServices.get(0).serviceInfo;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user