513 lines
16 KiB
Java
513 lines
16 KiB
Java
package space.rocketnine.gophast;
|
|
|
|
import android.app.Activity;
|
|
import android.app.Application;
|
|
import android.app.DownloadManager;
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.content.SharedPreferences;
|
|
import android.content.pm.PackageInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.net.Uri;
|
|
import android.os.Build;
|
|
import android.os.Environment;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.os.StrictMode;
|
|
import android.preference.PreferenceManager;
|
|
import android.support.v7.app.AlertDialog;
|
|
import android.support.v7.widget.RecyclerView;
|
|
import android.util.DisplayMetrics;
|
|
import android.util.Log;
|
|
import android.view.View;
|
|
import android.view.animation.Animation;
|
|
import android.view.animation.AnimationUtils;
|
|
import android.widget.Toast;
|
|
|
|
import java.io.File;
|
|
import java.util.ArrayList;
|
|
import java.util.Iterator;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
|
|
import space.rocketnine.gophast.manager.Manager;
|
|
import space.rocketnine.gophast.manager.Metadata;
|
|
import space.rocketnine.gophast.manager.NewDownloadInfo;
|
|
|
|
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
|
|
|
|
public class GoPhast extends Application {
|
|
ArrayList<Download> downloads;
|
|
ExecutorService exec;
|
|
WakeLocker wakeLocker;
|
|
SharedPreferences prefs;
|
|
DownloadAdapter adapter;
|
|
DownloadManager DownloadManager;
|
|
RecyclerView downloadsRecyclerView;
|
|
|
|
@Override
|
|
public void onCreate() {
|
|
super.onCreate();
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
|
|
StrictMode.setVmPolicy(builder.build());
|
|
}
|
|
|
|
exec = Executors.newCachedThreadPool();
|
|
|
|
DownloadManager = (DownloadManager) getSystemService(Activity.DOWNLOAD_SERVICE);
|
|
|
|
wakeLocker = new WakeLocker();
|
|
|
|
try {
|
|
Manager.initialize(2);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
return;
|
|
}
|
|
|
|
String appVersion;
|
|
try {
|
|
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
|
|
appVersion = packageInfo.versionName;
|
|
} catch (PackageManager.NameNotFoundException e) {
|
|
appVersion = "?";
|
|
}
|
|
Manager.setUserAgentExtra("gophast-android/" + appVersion + " (https://gitlab.com/tslocum/gophast-android)");
|
|
|
|
Manager.setForce(true);
|
|
|
|
setDownloadDir("");
|
|
|
|
Long minSplitSize = Long.valueOf(getPrefs().getString("pref_minsplitsize", "10485760"));
|
|
if (minSplitSize >= 0) {
|
|
Manager.setMinSplitSize(minSplitSize);
|
|
}
|
|
|
|
Long maxConnections = Long.valueOf(getPrefs().getString("pref_maxconnections", "3"));
|
|
if (maxConnections >= 0) {
|
|
Manager.setMaxConnections(maxConnections);
|
|
}
|
|
|
|
downloads = new ArrayList<Download>();
|
|
}
|
|
|
|
void addDownload(String url, Activity activity) {
|
|
url = url.trim();
|
|
if (url.equals("") || !url.toLowerCase().startsWith("http")) {
|
|
longToast(getString(R.string.invalidURL));
|
|
return;
|
|
}
|
|
|
|
NewDownloadInfo info = new NewDownloadInfo();
|
|
info.setURL(url);
|
|
info.setPostData(new byte[]{});
|
|
|
|
Metadata md;
|
|
try {
|
|
md = Manager.fetchMetadata(info);
|
|
} catch (Exception e) {
|
|
longToast(e.getMessage());
|
|
return;
|
|
}
|
|
|
|
String downloadName = (new File(url)).getName();
|
|
if (md != null && !md.getName().isEmpty()) {
|
|
downloadName = md.getName();
|
|
}
|
|
|
|
Download download = new Download();
|
|
download.URL = url;
|
|
download.Name = downloadName;
|
|
|
|
File downloadPath = (new File(getDownloadDir(), download.Name));
|
|
File downloadControlPath = (new File(getDownloadDir(), download.Name + ".gophast"));
|
|
if (downloadPath.exists() && !downloadControlPath.exists()) {
|
|
// TODO: Update add dialog with "fetching metadata" and overwrite warning for smoother transition
|
|
// Allow passing known metadata later to reduce transfer time
|
|
|
|
promptExistingFile(download, activity);
|
|
} else {
|
|
finishAddingDownload(download);
|
|
}
|
|
}
|
|
|
|
void promptExistingFile(Download download, Activity activity) {
|
|
AlertDialog.Builder alert = new AlertDialog.Builder(activity, R.style.AppTheme_Dialog)
|
|
.setTitle(String.format(getString(R.string.overwriteFile), download.Name))
|
|
.setIcon(R.drawable.ic_warning_white)
|
|
.setMessage(R.string.promptFileExists)
|
|
.setPositiveButton(R.string.overwrite, new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int whichButton) {
|
|
finishAddingDownload(download);
|
|
}
|
|
})
|
|
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int whichButton) {
|
|
}
|
|
});
|
|
alert.show();
|
|
}
|
|
|
|
void finishAddingDownload(Download download) {
|
|
downloads.add(download);
|
|
updateDownloadIndexes();
|
|
|
|
download.Path = Uri.fromFile(new File(getDownloadDir(), download.Name));
|
|
|
|
adapter.notifyItemInserted(download.Index);
|
|
|
|
DownloadRunnable runnable = new DownloadRunnable();
|
|
runnable.d = download;
|
|
runnable.gophast = GoPhast.this;
|
|
runnable.recyclerView = downloadsRecyclerView;
|
|
Thread thread = new Thread(runnable);
|
|
thread.setPriority(THREAD_PRIORITY_BACKGROUND);
|
|
exec.execute(thread);
|
|
}
|
|
|
|
void updateDownloadIndexes() {
|
|
int index = 0;
|
|
for (Iterator<Download> i = downloads.iterator(); i.hasNext(); ) {
|
|
Download d = i.next();
|
|
d.Index = index;
|
|
|
|
index++;
|
|
}
|
|
}
|
|
|
|
void startDownload(Download d, NewDownloadInfo info) throws Exception {
|
|
if (d.Paused) {
|
|
resumeDownload(d);
|
|
return;
|
|
} else if (d.Complete) {
|
|
return;
|
|
}
|
|
|
|
d.ID = Manager.addDownload(info);
|
|
|
|
acquireWakeLock(d.ID);
|
|
}
|
|
|
|
void pauseDownload(Download d) {
|
|
if (d.ID <= 0 || d.Paused || d.Complete) {
|
|
return;
|
|
}
|
|
d.Paused = true;
|
|
Log.d("gophast", "Pausing #" + d.ID);
|
|
|
|
try {
|
|
Manager.pauseDownload(d.ID);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
|
|
|
|
releaseWakeLock(d.ID);
|
|
}
|
|
|
|
void resumeDownload(Download d) {
|
|
if (d.ID <= 0 || !d.Paused || d.Complete) {
|
|
return;
|
|
}
|
|
d.Paused = false;
|
|
|
|
try {
|
|
Manager.resumeDownload(d.ID);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
return;
|
|
}
|
|
|
|
acquireWakeLock(d.ID);
|
|
}
|
|
|
|
void togglePauseDownload(Download d) {
|
|
if (!d.Paused) {
|
|
exec.execute(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
pauseDownload(d);
|
|
}
|
|
});
|
|
} else {
|
|
exec.execute(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
resumeDownload(d);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void openDownload(Download d, boolean showChooser, Context context) {
|
|
if (!d.Complete) {
|
|
return;
|
|
}
|
|
|
|
Intent intent = new Intent();
|
|
intent.setAction(Intent.ACTION_VIEW);
|
|
intent.setDataAndType(d.Path, d.getMIME());
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
|
|
try {
|
|
if (showChooser) {
|
|
context.startActivity(Intent.createChooser(intent, context.getString(R.string.openWith)));
|
|
} else {
|
|
context.startActivity(intent);
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
longToast("No application able to open this file is installed");
|
|
}
|
|
}
|
|
|
|
void toggleOpenDownloadWhenComplete(Download d) {
|
|
if (d.Complete) {
|
|
return;
|
|
}
|
|
|
|
d.OpenWhenComplete = !d.OpenWhenComplete;
|
|
if (d.OpenWhenComplete) {
|
|
shortToast(String.format(getString(R.string.openingWhenComplete), d.Name));
|
|
} else {
|
|
shortToast(String.format(getString(R.string.notOpeningWhenComplete), d.Name));
|
|
}
|
|
}
|
|
|
|
void promptRemoveDownload(final Download d, Context context) {
|
|
AlertDialog removeDialog = new AlertDialog.Builder(context, R.style.AppTheme_Dialog)
|
|
.setTitle(String.format(getString(R.string.deleteFile), d.Name))
|
|
.setIcon(R.drawable.ic_warning_white)
|
|
.setMessage(R.string.confirmDelete)
|
|
.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
|
|
@Override
|
|
public void onClick(DialogInterface dialogInterface, int i) {
|
|
exec.execute(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
removeDownload(d);
|
|
}
|
|
});
|
|
}
|
|
})
|
|
.setNegativeButton(R.string.cancel, null)
|
|
.create();
|
|
removeDialog.show();
|
|
}
|
|
|
|
void removeDownload(Download delete) {
|
|
if (delete == null) {
|
|
return;
|
|
}
|
|
Log.d("gophast", "Removing #" + delete.ID);
|
|
|
|
long id = delete.ID;
|
|
if (delete.ID > 0) {
|
|
try {
|
|
Manager.removeDownload(delete.ID);
|
|
} catch (Exception e) {
|
|
// Already removed
|
|
}
|
|
}
|
|
|
|
for (Iterator<Download> i = downloads.iterator(); i.hasNext(); ) {
|
|
Download d = i.next();
|
|
if (d.equals(delete)) {
|
|
if (d.DownloadManagerID > 0) {
|
|
try {
|
|
DownloadManager.remove(d.DownloadManagerID);
|
|
} catch (Exception e) {
|
|
// Already removed
|
|
}
|
|
}
|
|
|
|
int index = d.Index;
|
|
String path = d.Path != null ? d.Path.getPath() : "";
|
|
String name = d.Name;
|
|
i.remove();
|
|
updateDownloadIndexes();
|
|
adapter.notifyItemRemoved(index);
|
|
|
|
if (path != null && !path.isEmpty()) {
|
|
boolean deleted = false;
|
|
try {
|
|
File file = new File(path);
|
|
if (file.exists()) {
|
|
if (file.isFile()) {
|
|
deleted = file.delete();
|
|
}
|
|
} else {
|
|
deleted = true;
|
|
}
|
|
} catch (Exception e) {
|
|
// Failed to delete download
|
|
}
|
|
try {
|
|
File controlFile = new File(path + ".gophast");
|
|
if (controlFile.exists() && controlFile.isFile()) {
|
|
controlFile.delete();
|
|
}
|
|
} catch (Exception e) {
|
|
// Failed to delete control file
|
|
}
|
|
if (!deleted) {
|
|
longToast(String.format(getString(R.string.failedToDelete), name));
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (id > 0) {
|
|
releaseWakeLock(id);
|
|
}
|
|
}
|
|
|
|
void shortToast(final String message) {
|
|
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
Toast.makeText(GoPhast.this, message, Toast.LENGTH_SHORT).show();
|
|
}
|
|
});
|
|
}
|
|
|
|
void shortToast(final int resId) {
|
|
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
Toast.makeText(GoPhast.this, resId, Toast.LENGTH_SHORT).show();
|
|
}
|
|
});
|
|
}
|
|
|
|
void longToast(final String message) {
|
|
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
Toast.makeText(GoPhast.this, message, Toast.LENGTH_LONG).show();
|
|
}
|
|
});
|
|
}
|
|
|
|
void longToast(final int resId) {
|
|
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
Toast.makeText(GoPhast.this, resId, Toast.LENGTH_LONG).show();
|
|
}
|
|
});
|
|
}
|
|
|
|
String getDefaultDownloadDir() {
|
|
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
|
|
}
|
|
|
|
String getDownloadDir() {
|
|
String downloadDir = getPrefs().getString("pref_downloaddir", "");
|
|
if (downloadDir == null || downloadDir.isEmpty()) {
|
|
downloadDir = getDefaultDownloadDir();
|
|
}
|
|
|
|
return downloadDir;
|
|
}
|
|
|
|
void setDownloadDir(String newDownloadDir) {
|
|
String downloadDir;
|
|
if (newDownloadDir != null && !newDownloadDir.isEmpty()) {
|
|
downloadDir = newDownloadDir;
|
|
} else {
|
|
downloadDir = getDownloadDir();
|
|
}
|
|
|
|
Log.d("gophast", "Setting download directory to " + downloadDir);
|
|
Manager.setDownloadDir(downloadDir);
|
|
}
|
|
|
|
public void animateShowView(Context ctx, View v, int animation) {
|
|
Animation fadeInAnimation = AnimationUtils.loadAnimation(
|
|
ctx, animation);
|
|
v.startAnimation(fadeInAnimation);
|
|
fadeInAnimation.setAnimationListener(new Animation.AnimationListener() {
|
|
|
|
@Override
|
|
public void onAnimationStart(Animation animation) {
|
|
v.setVisibility(View.VISIBLE);
|
|
}
|
|
|
|
@Override
|
|
public void onAnimationRepeat(Animation animation) {
|
|
}
|
|
|
|
@Override
|
|
public void onAnimationEnd(Animation animation) {
|
|
}
|
|
});
|
|
}
|
|
|
|
public void animateHideView(Context ctx, View v, int animation) {
|
|
Animation fadeInAnimation = AnimationUtils.loadAnimation(
|
|
ctx, animation);
|
|
v.startAnimation(fadeInAnimation);
|
|
fadeInAnimation.setAnimationListener(new Animation.AnimationListener() {
|
|
|
|
@Override
|
|
public void onAnimationStart(Animation animation) {
|
|
}
|
|
|
|
@Override
|
|
public void onAnimationRepeat(Animation animation) {
|
|
}
|
|
|
|
@Override
|
|
public void onAnimationEnd(Animation animation) {
|
|
v.setVisibility(View.INVISIBLE);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* This method converts dp unit to equivalent pixels, depending on device density.
|
|
*
|
|
* @param dp A value in dp (density independent pixels) unit. Which we need to convert into pixels
|
|
* @return A float value to represent px equivalent to dp depending on device density
|
|
*/
|
|
public int convertDpToPixel(int dp) {
|
|
return (int) (dp * ((float) getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT));
|
|
}
|
|
|
|
/**
|
|
* This method converts device specific pixels to density independent pixels.
|
|
*
|
|
* @param px A value in px (pixels) unit. Which we need to convert into db
|
|
* @return A float value to represent dp equivalent to px value
|
|
*/
|
|
public int convertPixelsToDp(int px) {
|
|
return (int) (px / ((float) getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT));
|
|
}
|
|
|
|
public void acquireWakeLock(long id) {
|
|
wakeLocker.acquire(GoPhast.this, id, false);
|
|
}
|
|
|
|
public void releaseWakeLock(long id) {
|
|
wakeLocker.release(id);
|
|
}
|
|
|
|
public SharedPreferences getPrefs() {
|
|
if (prefs == null) {
|
|
prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
|
}
|
|
return prefs;
|
|
}
|
|
|
|
boolean isDownloadIntent(Intent intent) {
|
|
return intent != null && intent.getAction() != null && intent.getAction().equals("android.intent.action.VIEW") && intent.getData() != null;
|
|
}
|
|
}
|