368 lines
14 KiB
Java
368 lines
14 KiB
Java
/*
|
|
* Copyright (C) 2012 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 com.google.android.vending.expansion.downloader;
|
|
|
|
import android.annotation.TargetApi;
|
|
import android.content.Context;
|
|
import android.os.Build;
|
|
import android.os.Environment;
|
|
import android.os.StatFs;
|
|
import android.os.SystemClock;
|
|
import android.util.Log;
|
|
|
|
// -- GODOT start --
|
|
//import com.android.vending.expansion.downloader.R;
|
|
import org.godotengine.godot.R;
|
|
// -- GODOT end --
|
|
|
|
import java.io.File;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.Date;
|
|
import java.util.Locale;
|
|
import java.util.Random;
|
|
import java.util.TimeZone;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
/**
|
|
* Some helper functions for the download manager
|
|
*/
|
|
public class Helpers {
|
|
|
|
public static Random sRandom = new Random(SystemClock.uptimeMillis());
|
|
|
|
/** Regex used to parse content-disposition headers */
|
|
private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern
|
|
.compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\"");
|
|
|
|
private Helpers() {
|
|
}
|
|
|
|
/*
|
|
* Parse the Content-Disposition HTTP Header. The format of the header is defined here:
|
|
* https://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for
|
|
* content that is going to be downloaded to the file system. We only support the attachment
|
|
* type.
|
|
*/
|
|
static String parseContentDisposition(String contentDisposition) {
|
|
try {
|
|
Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
|
|
if (m.find()) {
|
|
return m.group(1);
|
|
}
|
|
} catch (IllegalStateException ex) {
|
|
// This function is defined as returning null when it can't parse
|
|
// the header
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @return the root of the filesystem containing the given path
|
|
*/
|
|
public static File getFilesystemRoot(String path) {
|
|
File cache = Environment.getDownloadCacheDirectory();
|
|
if (path.startsWith(cache.getPath())) {
|
|
return cache;
|
|
}
|
|
File external = Environment.getExternalStorageDirectory();
|
|
if (path.startsWith(external.getPath())) {
|
|
return external;
|
|
}
|
|
throw new IllegalArgumentException(
|
|
"Cannot determine filesystem root for " + path);
|
|
}
|
|
|
|
public static boolean isExternalMediaMounted() {
|
|
if (!Environment.getExternalStorageState().equals(
|
|
Environment.MEDIA_MOUNTED)) {
|
|
// No SD card found.
|
|
if (Constants.LOGVV) {
|
|
Log.d(Constants.TAG, "no external storage");
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @return the number of bytes available on the filesystem rooted at the given File
|
|
*/
|
|
public static long getAvailableBytes(File root) {
|
|
StatFs stat = new StatFs(root.getPath());
|
|
// put a bit of margin (in case creating the file grows the system by a
|
|
// few blocks)
|
|
long availableBlocks = (long) stat.getAvailableBlocks() - 4;
|
|
return stat.getBlockSize() * availableBlocks;
|
|
}
|
|
|
|
/**
|
|
* Checks whether the filename looks legitimate
|
|
*/
|
|
public static boolean isFilenameValid(String filename) {
|
|
filename = filename.replaceFirst("/+", "/"); // normalize leading
|
|
// slashes
|
|
return filename.startsWith(Environment.getDownloadCacheDirectory().toString())
|
|
|| filename.startsWith(Environment.getExternalStorageDirectory().toString());
|
|
}
|
|
|
|
/*
|
|
* Delete the given file from device
|
|
*/
|
|
/* package */static void deleteFile(String path) {
|
|
try {
|
|
File file = new File(path);
|
|
file.delete();
|
|
} catch (Exception e) {
|
|
Log.w(Constants.TAG, "file: '" + path + "' couldn't be deleted", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Showing progress in MB here. It would be nice to choose the unit (KB, MB, GB) based on total
|
|
* file size, but given what we know about the expected ranges of file sizes for APK expansion
|
|
* files, it's probably not necessary.
|
|
*
|
|
* @param overallProgress
|
|
* @param overallTotal
|
|
* @return
|
|
*/
|
|
|
|
static public String getDownloadProgressString(long overallProgress, long overallTotal) {
|
|
if (overallTotal == 0) {
|
|
if (Constants.LOGVV) {
|
|
Log.e(Constants.TAG, "Notification called when total is zero");
|
|
}
|
|
return "";
|
|
}
|
|
// -- GODOT start --
|
|
return String.format(Locale.ENGLISH, "%.2f",
|
|
(float) overallProgress / (1024.0f * 1024.0f))
|
|
+ "MB /" +
|
|
String.format(Locale.ENGLISH, "%.2f", (float) overallTotal /
|
|
(1024.0f * 1024.0f))
|
|
+ "MB";
|
|
// -- GODOT end --
|
|
}
|
|
|
|
/**
|
|
* Adds a percentile to getDownloadProgressString.
|
|
*
|
|
* @param overallProgress
|
|
* @param overallTotal
|
|
* @return
|
|
*/
|
|
static public String getDownloadProgressStringNotification(long overallProgress,
|
|
long overallTotal) {
|
|
if (overallTotal == 0) {
|
|
if (Constants.LOGVV) {
|
|
Log.e(Constants.TAG, "Notification called when total is zero");
|
|
}
|
|
return "";
|
|
}
|
|
return getDownloadProgressString(overallProgress, overallTotal) + " (" +
|
|
getDownloadProgressPercent(overallProgress, overallTotal) + ")";
|
|
}
|
|
|
|
public static String getDownloadProgressPercent(long overallProgress, long overallTotal) {
|
|
if (overallTotal == 0) {
|
|
if (Constants.LOGVV) {
|
|
Log.e(Constants.TAG, "Notification called when total is zero");
|
|
}
|
|
return "";
|
|
}
|
|
return Long.toString(overallProgress * 100 / overallTotal) + "%";
|
|
}
|
|
|
|
public static String getSpeedString(float bytesPerMillisecond) {
|
|
// -- GODOT start --
|
|
return String.format(Locale.ENGLISH, "%.2f", bytesPerMillisecond * 1000 / 1024);
|
|
// -- GODOT end --
|
|
}
|
|
|
|
public static String getTimeRemaining(long durationInMilliseconds) {
|
|
SimpleDateFormat sdf;
|
|
if (durationInMilliseconds > 1000 * 60 * 60) {
|
|
sdf = new SimpleDateFormat("HH:mm", Locale.getDefault());
|
|
} else {
|
|
sdf = new SimpleDateFormat("mm:ss", Locale.getDefault());
|
|
}
|
|
return sdf.format(new Date(durationInMilliseconds - TimeZone.getDefault().getRawOffset()));
|
|
}
|
|
|
|
/**
|
|
* Returns the file name (without full path) for an Expansion APK file from the given context.
|
|
*
|
|
* @param c the context
|
|
* @param mainFile true for main file, false for patch file
|
|
* @param versionCode the version of the file
|
|
* @return String the file name of the expansion file
|
|
*/
|
|
public static String getExpansionAPKFileName(Context c, boolean mainFile, int versionCode) {
|
|
return (mainFile ? "main." : "patch.") + versionCode + "." + c.getPackageName() + ".obb";
|
|
}
|
|
|
|
/**
|
|
* Returns the filename (where the file should be saved) from info about a download
|
|
*/
|
|
static public String generateSaveFileName(Context c, String fileName) {
|
|
String path = getSaveFilePath(c)
|
|
+ File.separator + fileName;
|
|
return path;
|
|
}
|
|
|
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
|
static public String getSaveFilePath(Context c) {
|
|
// This technically existed since Honeycomb, but it is critical
|
|
// on KitKat and greater versions since it will create the
|
|
// directory if needed
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
|
return c.getObbDir().toString();
|
|
} else {
|
|
File root = Environment.getExternalStorageDirectory();
|
|
String path = root.toString() + Constants.EXP_PATH + c.getPackageName();
|
|
return path;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function to ascertain the existence of a file and return true/false appropriately
|
|
*
|
|
* @param c the app/activity/service context
|
|
* @param fileName the name (sans path) of the file to query
|
|
* @param fileSize the size that the file must match
|
|
* @param deleteFileOnMismatch if the file sizes do not match, delete the file
|
|
* @return true if it does exist, false otherwise
|
|
*/
|
|
static public boolean doesFileExist(Context c, String fileName, long fileSize,
|
|
boolean deleteFileOnMismatch) {
|
|
// the file may have been delivered by Play --- let's make sure
|
|
// it's the size we expect
|
|
File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName));
|
|
if (fileForNewFile.exists()) {
|
|
if (fileForNewFile.length() == fileSize) {
|
|
return true;
|
|
}
|
|
if (deleteFileOnMismatch) {
|
|
// delete the file --- we won't be able to resume
|
|
// because we cannot confirm the integrity of the file
|
|
fileForNewFile.delete();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static final int FS_READABLE = 0;
|
|
public static final int FS_DOES_NOT_EXIST = 1;
|
|
public static final int FS_CANNOT_READ = 2;
|
|
|
|
/**
|
|
* Helper function to ascertain whether a file can be read.
|
|
*
|
|
* @param c the app/activity/service context
|
|
* @param fileName the name (sans path) of the file to query
|
|
* @return true if it does exist, false otherwise
|
|
*/
|
|
static public int getFileStatus(Context c, String fileName) {
|
|
// the file may have been delivered by Play --- let's make sure
|
|
// it's the size we expect
|
|
File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName));
|
|
int returnValue;
|
|
if (fileForNewFile.exists()) {
|
|
if (fileForNewFile.canRead()) {
|
|
returnValue = FS_READABLE;
|
|
} else {
|
|
returnValue = FS_CANNOT_READ;
|
|
}
|
|
} else {
|
|
returnValue = FS_DOES_NOT_EXIST;
|
|
}
|
|
return returnValue;
|
|
}
|
|
|
|
/**
|
|
* Helper function to ascertain whether the application has the correct access to the OBB
|
|
* directory to allow an OBB file to be written.
|
|
*
|
|
* @param c the app/activity/service context
|
|
* @return true if the application can write an OBB file, false otherwise
|
|
*/
|
|
static public boolean canWriteOBBFile(Context c) {
|
|
String path = getSaveFilePath(c);
|
|
File fileForNewFile = new File(path);
|
|
boolean canWrite;
|
|
if (fileForNewFile.exists()) {
|
|
canWrite = fileForNewFile.isDirectory() && fileForNewFile.canWrite();
|
|
} else {
|
|
canWrite = fileForNewFile.mkdirs();
|
|
}
|
|
return canWrite;
|
|
}
|
|
|
|
/**
|
|
* Converts download states that are returned by the
|
|
* {@link IDownloaderClient#onDownloadStateChanged} callback into usable strings. This is useful
|
|
* if using the state strings built into the library to display user messages.
|
|
*
|
|
* @param state One of the STATE_* constants from {@link IDownloaderClient}.
|
|
* @return string resource ID for the corresponding string.
|
|
*/
|
|
static public int getDownloaderStringResourceIDFromState(int state) {
|
|
switch (state) {
|
|
case IDownloaderClient.STATE_IDLE:
|
|
return R.string.state_idle;
|
|
case IDownloaderClient.STATE_FETCHING_URL:
|
|
return R.string.state_fetching_url;
|
|
case IDownloaderClient.STATE_CONNECTING:
|
|
return R.string.state_connecting;
|
|
case IDownloaderClient.STATE_DOWNLOADING:
|
|
return R.string.state_downloading;
|
|
case IDownloaderClient.STATE_COMPLETED:
|
|
return R.string.state_completed;
|
|
case IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE:
|
|
return R.string.state_paused_network_unavailable;
|
|
case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
|
|
return R.string.state_paused_by_request;
|
|
case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
|
|
return R.string.state_paused_wifi_disabled;
|
|
case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION:
|
|
return R.string.state_paused_wifi_unavailable;
|
|
case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED:
|
|
return R.string.state_paused_wifi_disabled;
|
|
case IDownloaderClient.STATE_PAUSED_NEED_WIFI:
|
|
return R.string.state_paused_wifi_unavailable;
|
|
case IDownloaderClient.STATE_PAUSED_ROAMING:
|
|
return R.string.state_paused_roaming;
|
|
case IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE:
|
|
return R.string.state_paused_network_setup_failure;
|
|
case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE:
|
|
return R.string.state_paused_sdcard_unavailable;
|
|
case IDownloaderClient.STATE_FAILED_UNLICENSED:
|
|
return R.string.state_failed_unlicensed;
|
|
case IDownloaderClient.STATE_FAILED_FETCHING_URL:
|
|
return R.string.state_failed_fetching_url;
|
|
case IDownloaderClient.STATE_FAILED_SDCARD_FULL:
|
|
return R.string.state_failed_sdcard_full;
|
|
case IDownloaderClient.STATE_FAILED_CANCELED:
|
|
return R.string.state_failed_cancelled;
|
|
default:
|
|
return R.string.state_unknown;
|
|
}
|
|
}
|
|
|
|
}
|