Embedded GigaGet download manager. First try.

This commit is contained in:
David 2016-04-21 20:28:01 -03:00
parent f08b1224c9
commit 4bae12aa55
58 changed files with 3121 additions and 40 deletions

View File

@ -43,4 +43,6 @@ dependencies {
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
compile 'com.github.nirhart:parallaxscroll:1.0'
compile 'com.google.android.exoplayer:exoplayer:r1.5.5'
compile 'com.google.code.gson:gson:2.3.+'
compile 'com.nononsenseapps:filepicker:2.0.5'
}

View File

@ -125,5 +125,28 @@
android:label="@string/general_error"
android:theme="@android:style/Theme.NoDisplay" />
<activity android:name=".ErrorActivity"/>
<activity
android:name=".download.MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:launchMode="singleTask">
<intent-filter>
<action android:name="us.shandian.giga.intent.DOWNLOAD"/>
<category android:name="android.intent.category.DEFAULT"/>
<data
android:mimeType="application/*"
android:host="*"
android:scheme="http"/>
<data
android:mimeType="application/*"
android:host="*"
android:scheme="https"/>
</intent-filter>
</activity>
<service
android:name="us.shandian.giga.service.DownloadManagerService"/>
</application>
</manifest>

View File

@ -2,9 +2,9 @@ package org.schabi.newpipe.download;
import android.Manifest;
import android.app.Dialog;
import android.app.DownloadManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
@ -14,7 +14,6 @@ import android.support.v4.app.DialogFragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.widget.Toast;
import org.schabi.newpipe.App;
import org.schabi.newpipe.NewPipeSettings;
@ -152,49 +151,23 @@ public class DownloadDialog extends DialogFragment {
private void download(String url, String title,
String fileSuffix, File downloadDir, Context context) {
if(!downloadDir.exists()) {
//attempt to create directory
boolean mkdir = downloadDir.mkdirs();
if(!mkdir && !downloadDir.isDirectory()) {
String message = context.getString(R.string.err_dir_create,downloadDir.toString());
Log.e(TAG, message);
Toast.makeText(context,message , Toast.LENGTH_LONG).show();
return;
}
String message = context.getString(R.string.info_dir_created,downloadDir.toString());
Log.e(TAG, message);
Toast.makeText(context,message , Toast.LENGTH_LONG).show();
}
File saveFilePath = new File(downloadDir,createFileName(title) + fileSuffix);
long id = 0;
if (App.isUsingTor()) {
// if using Tor, do not use DownloadManager because the proxy cannot be set
FileDownloader.downloadFile(getContext(), url, saveFilePath, title);
} else {
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(
Uri.parse(url));
request.setDestinationUri(Uri.fromFile(saveFilePath));
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setTitle(title);
request.setDescription("'" + url +
"' => '" + saveFilePath + "'");
request.allowScanningByMediaScanner();
try {
id = dm.enqueue(request);
} catch (Exception e) {
e.printStackTrace();
}
}
Log.i(TAG,"Started downloading '" + url +
"' => '" + saveFilePath + "' #" + id);
if (App.isUsingTor()) {
//if using Tor, do not use DownloadManager because the proxy cannot be set
//we'll see later
FileDownloader.downloadFile(getContext(), url, saveFilePath, title);
} else {
Intent intent = new Intent(getContext(), MainActivity.class);
intent.setAction(MainActivity.INTENT_DOWNLOAD);
intent.setData(Uri.parse(url));
intent.putExtra("fileName", createFileName(title) + fileSuffix);
startActivity(intent);
}
}
}

View File

@ -0,0 +1,280 @@
package org.schabi.newpipe.download;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.app.FragmentTransaction;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import org.schabi.newpipe.R;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import us.shandian.giga.get.DownloadManager;
import us.shandian.giga.service.DownloadManagerService;
import us.shandian.giga.ui.fragment.AllMissionsFragment;
import us.shandian.giga.ui.fragment.MissionsFragment;
import us.shandian.giga.util.CrashHandler;
import us.shandian.giga.util.Utility;
public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener{
public static final String INTENT_DOWNLOAD = "us.shandian.giga.intent.DOWNLOAD";
public static final String INTENT_LIST = "us.shandian.giga.intent.LIST";
private MissionsFragment mFragment;
private DownloadManager mManager;
private DownloadManagerService.DMBinder mBinder;
private String mPendingUrl;
private SharedPreferences mPrefs;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName p1, IBinder binder) {
mBinder = (DownloadManagerService.DMBinder) binder;
mManager = mBinder.getDownloadManager();
}
@Override
public void onServiceDisconnected(ComponentName p1) {
}
};
@Override
@TargetApi(21)
protected void onCreate(Bundle savedInstanceState) {
CrashHandler.init(this);
CrashHandler.register();
// Service
Intent i = new Intent();
i.setClass(this, DownloadManagerService.class);
startService(i);
bindService(i, mConnection, Context.BIND_AUTO_CREATE);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_downloader);
mPrefs = getSharedPreferences("threads", Context.MODE_WORLD_READABLE);
// Fragment
getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
updateFragments();
getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
// Intent
if (getIntent().getAction().equals(INTENT_DOWNLOAD)) {
mPendingUrl = getIntent().getData().toString();
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent.getAction().equals(INTENT_DOWNLOAD)) {
mPendingUrl = intent.getData().toString();
}
}
@Override
protected void onResume() {
super.onResume();
if (mPendingUrl != null) {
showUrlDialog();
mPendingUrl = null;
}
}
private void updateFragments() {
mFragment = new AllMissionsFragment();
getFragmentManager().beginTransaction()
.replace(R.id.frame, mFragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit();
}
private void showUrlDialog() {
// Create the view
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(R.layout.dialog_url, null);
final EditText text = Utility.findViewById(v, R.id.url);
final EditText name = Utility.findViewById(v, R.id.file_name);
final TextView tCount = Utility.findViewById(v, R.id.threads_count);
final SeekBar threads = Utility.findViewById(v, R.id.threads);
final Toolbar toolbar = Utility.findViewById(v, R.id.toolbar);
final Button fetch = Utility.findViewById(v, R.id.fetch_name);
threads.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) {
tCount.setText(String.valueOf(progress + 1));
}
@Override
public void onStartTrackingTouch(SeekBar p1) {
}
@Override
public void onStopTrackingTouch(SeekBar p1) {
}
});
int def = mPrefs.getInt("threads", 4);
threads.setProgress(def - 1);
tCount.setText(String.valueOf(def));
if (mPendingUrl != null) {
text.setText(mPendingUrl);
}
name.setText(getIntent().getStringExtra("fileName"));
toolbar.setTitle(R.string.add);
toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp);
toolbar.inflateMenu(R.menu.dialog_url);
// Show the dialog
final AlertDialog dialog = new AlertDialog.Builder(this)
.setCancelable(true)
.setView(v)
.create();
dialog.show();
fetch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new NameFetcherTask().execute(text, name);
}
});
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.okay) {
String url = text.getText().toString().trim();
String fName = name.getText().toString().trim();
File f = new File(mManager.getLocation() + "/" + fName);
if (f.exists()) {
Toast.makeText(MainActivity.this, R.string.msg_exists, Toast.LENGTH_SHORT).show();
} else if (!checkURL(url)) {
Toast.makeText(MainActivity.this, R.string.msg_url_malform, Toast.LENGTH_SHORT).show();
} else {
while (mBinder == null);
int res = mManager.startMission(url, fName, threads.getProgress() + 1);
mBinder.onMissionAdded(mManager.getMission(res));
mFragment.notifyChange();
mPrefs.edit().putInt("threads", threads.getProgress() + 1).commit();
mPendingUrl = null;
dialog.dismiss();
}
return true;
} else {
return false;
}
}
});
}
private boolean checkURL(String url) {
try {
URL u = new URL(url);
u.openConnection();
return true;
} catch (MalformedURLException e) {
return false;
} catch (IOException e) {
return false;
}
}
private class NameFetcherTask extends AsyncTask<View, Void, Object[]> {
@Override
protected Object[] doInBackground(View[] params) {
try {
URL url = new URL(((EditText) params[0]).getText().toString());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
String header = conn.getHeaderField("Content-Disposition");
if (header != null && header.indexOf("=") != -1) {
return new Object[]{params[1], header.split("=")[1].replace("\"", "")};
}
} catch (Exception e) {
}
return null;
}
@Override
protected void onPostExecute(Object[] result) {
super.onPostExecute(result);
if (result != null) {
((EditText) result[0]).setText(result[1].toString());
}
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
}
}

View File

@ -0,0 +1,14 @@
package us.shandian.giga.get;
public interface DownloadManager
{
public static final int BLOCK_SIZE = 512 * 1024;
public int startMission(String url, String name, int threads);
public void resumeMission(int id);
public void pauseMission(int id);
public void deleteMission(int id);
public DownloadMission getMission(int id);
public int getCount();
public String getLocation();
}

View File

@ -0,0 +1,214 @@
package us.shandian.giga.get;
import android.content.Context;
import android.util.Log;
import com.google.gson.Gson;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import us.shandian.giga.util.Utility;
import static org.schabi.newpipe.BuildConfig.DEBUG;
public class DownloadManagerImpl implements DownloadManager
{
private static final String TAG = DownloadManagerImpl.class.getSimpleName();
private Context mContext;
private String mLocation;
protected ArrayList<DownloadMission> mMissions = new ArrayList<DownloadMission>();
public DownloadManagerImpl(Context context, String location) {
mContext = context;
mLocation = location;
loadMissions();
}
@Override
public int startMission(String url, String name, int threads) {
DownloadMission mission = new DownloadMission();
mission.url = url;
mission.name = name;
mission.location = mLocation;
mission.timestamp = System.currentTimeMillis();
mission.threadCount = threads;
new Initializer(mContext, mission).start();
return insertMission(mission);
}
@Override
public void resumeMission(int i) {
DownloadMission d = getMission(i);
if (!d.running && d.errCode == -1) {
d.start();
}
}
@Override
public void pauseMission(int i) {
DownloadMission d = getMission(i);
if (d.running) {
d.pause();
}
}
@Override
public void deleteMission(int i) {
getMission(i).delete();
mMissions.remove(i);
}
private void loadMissions() {
File f = new File(mLocation);
if (f.exists() && f.isDirectory()) {
File[] subs = f.listFiles();
for (File sub : subs) {
if (sub.isDirectory()) {
continue;
}
if (sub.getName().endsWith(".giga")) {
String str = Utility.readFromFile(sub.getAbsolutePath());
if (str != null && !str.trim().equals("")) {
if (DEBUG) {
Log.d(TAG, "loading mission " + sub.getName());
Log.d(TAG, str);
}
DownloadMission mis = new Gson().fromJson(str, DownloadMission.class);
if (mis.finished) {
sub.delete();
continue;
}
mis.running = false;
mis.recovered = true;
insertMission(mis);
}
} else if (!sub.getName().startsWith(".") && !new File(sub.getPath() + ".giga").exists()) {
// Add a dummy mission for downloaded files
DownloadMission mis = new DownloadMission();
mis.length = sub.length();
mis.done = mis.length;
mis.finished = true;
mis.running = false;
mis.name = sub.getName();
mis.location = mLocation;
mis.timestamp = sub.lastModified();
insertMission(mis);
}
}
}
}
@Override
public DownloadMission getMission(int i) {
return mMissions.get(i);
}
@Override
public int getCount() {
return mMissions.size();
}
private int insertMission(DownloadMission mission) {
int i = -1;
DownloadMission m = null;
if (mMissions.size() > 0) {
do {
m = mMissions.get(++i);
} while (m.timestamp > mission.timestamp && i < mMissions.size() - 1);
//if (i > 0) i--;
} else {
i = 0;
}
mMissions.add(i, mission);
return i;
}
@Override
public String getLocation() {
return mLocation;
}
private class Initializer extends Thread {
private Context context;
private DownloadMission mission;
public Initializer(Context context, DownloadMission mission) {
this.context = context;
this.mission = mission;
}
@Override
public void run() {
try {
URL url = new URL(mission.url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
mission.length = conn.getContentLength();
if (mission.length <= 0) {
mission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED;
//mission.notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
return;
}
// Open again
conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Range", "bytes=" + (mission.length - 10) + "-" + mission.length);
if (conn.getResponseCode() != 206) {
// Fallback to single thread if no partial content support
mission.fallback = true;
if (DEBUG) {
Log.d(TAG, "falling back");
}
}
if (DEBUG) {
Log.d(TAG, "response = " + conn.getResponseCode());
}
mission.blocks = mission.length / BLOCK_SIZE;
if (mission.threadCount > mission.blocks) {
mission.threadCount = (int) mission.blocks;
}
if (mission.threadCount <= 0) {
mission.threadCount = 1;
}
if (mission.blocks * BLOCK_SIZE < mission.length) {
mission.blocks++;
}
new File(mission.location).mkdirs();
new File(mission.location + "/" + mission.name).createNewFile();
RandomAccessFile af = new RandomAccessFile(mission.location + "/" + mission.name, "rw");
af.setLength(mission.length);
af.close();
mission.start();
} catch (Exception e) {
// TODO Notify
throw new RuntimeException(e);
}
}
}
}

View File

@ -0,0 +1,229 @@
package us.shandian.giga.get;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.google.gson.Gson;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.HashMap;
import us.shandian.giga.util.Utility;
import static org.schabi.newpipe.BuildConfig.DEBUG;
public class DownloadMission
{
private static final String TAG = DownloadMission.class.getSimpleName();
public static interface MissionListener {
HashMap<MissionListener, Handler> handlerStore = new HashMap<>();
public void onProgressUpdate(long done, long total);
public void onFinish();
public void onError(int errCode);
}
public static final int ERROR_SERVER_UNSUPPORTED = 206;
public static final int ERROR_UNKNOWN = 233;
public String name = "";
public String url = "";
public String location = "";
public long blocks = 0;
public long length = 0;
public long done = 0;
public int threadCount = 3;
public int finishCount = 0;
public ArrayList<Long> threadPositions = new ArrayList<Long>();
public HashMap<Long, Boolean> blockState = new HashMap<Long, Boolean>();
public boolean running = false;
public boolean finished = false;
public boolean fallback = false;
public int errCode = -1;
public long timestamp = 0;
public transient boolean recovered = false;
private transient ArrayList<WeakReference<MissionListener>> mListeners = new ArrayList<WeakReference<MissionListener>>();
private transient boolean mWritingToFile = false;
public boolean isBlockPreserved(long block) {
return blockState.containsKey(block) ? blockState.get(block) : false;
}
public void preserveBlock(long block) {
synchronized (blockState) {
blockState.put(block, true);
}
}
public void setPosition(int id, long position) {
threadPositions.set(id, position);
}
public long getPosition(int id) {
return threadPositions.get(id);
}
public synchronized void notifyProgress(long deltaLen) {
if (!running) return;
if (recovered) {
recovered = false;
}
done += deltaLen;
if (done > length) {
done = length;
}
if (done != length) {
writeThisToFile();
}
for (WeakReference<MissionListener> ref: mListeners) {
final MissionListener listener = ref.get();
if (listener != null) {
MissionListener.handlerStore.get(listener).post(new Runnable() {
@Override
public void run() {
listener.onProgressUpdate(done, length);
}
});
}
}
}
public synchronized void notifyFinished() {
if (errCode > 0) return;
finishCount++;
if (finishCount == threadCount) {
onFinish();
}
}
private void onFinish() {
if (errCode > 0) return;
if (DEBUG) {
Log.d(TAG, "onFinish");
}
running = false;
finished = true;
deleteThisFromFile();
for (WeakReference<MissionListener> ref : mListeners) {
final MissionListener listener = ref.get();
if (listener != null) {
MissionListener.handlerStore.get(listener).post(new Runnable() {
@Override
public void run() {
listener.onFinish();
}
});
}
}
}
public synchronized void notifyError(int err) {
errCode = err;
writeThisToFile();
for (WeakReference<MissionListener> ref : mListeners) {
final MissionListener listener = ref.get();
MissionListener.handlerStore.get(listener).post(new Runnable() {
@Override
public void run() {
listener.onError(errCode);
}
});
}
}
public synchronized void addListener(MissionListener listener) {
Handler handler = new Handler(Looper.getMainLooper());
MissionListener.handlerStore.put(listener, handler);
mListeners.add(new WeakReference<MissionListener>(listener));
}
public synchronized void removeListener(MissionListener listener) {
for (Iterator<WeakReference<MissionListener>> iterator = mListeners.iterator();
iterator.hasNext(); ) {
WeakReference<MissionListener> weakRef = iterator.next();
if (listener!=null && listener == weakRef.get())
{
iterator.remove();
}
}
}
public void start() {
if (!running && !finished) {
running = true;
if (!fallback) {
for (int i = 0; i < threadCount; i++) {
if (threadPositions.size() <= i && !recovered) {
threadPositions.add((long) i);
}
new Thread(new DownloadRunnable(this, i)).start();
}
} else {
// In fallback mode, resuming is not supported.
threadCount = 1;
done = 0;
blocks = 0;
new Thread(new DownloadRunnableFallback(this)).start();
}
}
}
public void pause() {
if (running) {
running = false;
recovered = true;
// TODO: Notify & Write state to info file
// if (err)
}
}
public void delete() {
deleteThisFromFile();
new File(location + "/" + name).delete();
}
public void writeThisToFile() {
if (!mWritingToFile) {
mWritingToFile = true;
new Thread() {
@Override
public void run() {
doWriteThisToFile();
mWritingToFile = false;
}
}.start();
}
}
private void doWriteThisToFile() {
synchronized (blockState) {
Utility.writeToFile(location + "/" + name + ".giga", new Gson().toJson(this));
}
}
private void deleteThisFromFile() {
new File(location + "/" + name + ".giga").delete();
}
}

View File

@ -0,0 +1,174 @@
package us.shandian.giga.get;
import android.os.Handler;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import static org.schabi.newpipe.BuildConfig.DEBUG;
public class DownloadRunnable implements Runnable
{
private static final String TAG = DownloadRunnable.class.getSimpleName();
private DownloadMission mMission;
private int mId;
public DownloadRunnable(DownloadMission mission, int id) {
mMission = mission;
mId = id;
}
@Override
public void run() {
boolean retry = mMission.recovered;
long position = mMission.getPosition(mId);
if (DEBUG) {
Log.d(TAG, mId + ":default pos " + position);
Log.d(TAG, mId + ":recovered: " + mMission.recovered);
}
while (mMission.errCode == -1 && mMission.running && position < mMission.blocks) {
if (Thread.currentThread().isInterrupted()) {
mMission.pause();
return;
}
if (DEBUG && retry) {
Log.d(TAG, mId + ":retry is true. Resuming at " + position);
}
// Wait for an unblocked position
while (!retry && position < mMission.blocks && mMission.isBlockPreserved(position)) {
if (DEBUG) {
Log.d(TAG, mId + ":position " + position + " preserved, passing");
}
position++;
}
retry = false;
if (position >= mMission.blocks) {
break;
}
if (DEBUG) {
Log.d(TAG, mId + ":preserving position " + position);
}
mMission.preserveBlock(position);
mMission.setPosition(mId, position);
long start = position * DownloadManager.BLOCK_SIZE;
long end = start + DownloadManager.BLOCK_SIZE - 1;
if (end >= mMission.length) {
end = mMission.length - 1;
}
HttpURLConnection conn = null;
int total = 0;
try {
URL url = new URL(mMission.url);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
if (DEBUG) {
Log.d(TAG, mId + ":" + conn.getRequestProperty("Range"));
Log.d(TAG, mId + ":Content-Length=" + conn.getContentLength() + " Code:" + conn.getResponseCode());
}
// A server may be ignoring the range requet
if (conn.getResponseCode() != 206) {
mMission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED;
notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
if (DEBUG) {
Log.e(TAG, mId + ":Unsupported " + conn.getResponseCode());
}
break;
}
RandomAccessFile f = new RandomAccessFile(mMission.location + "/" + mMission.name, "rw");
f.seek(start);
BufferedInputStream ipt = new BufferedInputStream(conn.getInputStream());
byte[] buf = new byte[512];
while (start < end && mMission.running) {
int len = ipt.read(buf, 0, 512);
if (len == -1) {
break;
} else {
start += len;
total += len;
f.write(buf, 0, len);
notifyProgress(len);
}
}
if (DEBUG && mMission.running) {
Log.d(TAG, mId + ":position " + position + " finished, total length " + total);
}
f.close();
ipt.close();
// TODO We should save progress for each thread
} catch (Exception e) {
// TODO Retry count limit & notify error
retry = true;
notifyProgress(-total);
if (DEBUG) {
Log.d(TAG, mId + ":position " + position + " retrying");
}
}
}
if (DEBUG) {
Log.d(TAG, "thread " + mId + " exited main loop");
}
if (mMission.errCode == -1 && mMission.running) {
if (DEBUG) {
Log.d(TAG, "no error has happened, notifying");
}
notifyFinished();
}
if (DEBUG && !mMission.running) {
Log.d(TAG, "The mission has been paused. Passing.");
}
}
private void notifyProgress(final long len) {
synchronized (mMission) {
mMission.notifyProgress(len);
}
}
private void notifyError(final int err) {
synchronized (mMission) {
mMission.notifyError(err);
mMission.pause();
}
}
private void notifyFinished() {
synchronized (mMission) {
mMission.notifyFinished();
}
}
}

View File

@ -0,0 +1,74 @@
package us.shandian.giga.get;
import java.io.BufferedInputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
// Single-threaded fallback mode
public class DownloadRunnableFallback implements Runnable
{
private DownloadMission mMission;
//private int mId;
public DownloadRunnableFallback(DownloadMission mission) {
//mId = id;
mMission = mission;
}
@Override
public void run() {
try {
URL url = new URL(mMission.url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
if (conn.getResponseCode() != 200 && conn.getResponseCode() != 206) {
notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
} else {
RandomAccessFile f = new RandomAccessFile(mMission.location + "/" + mMission.name, "rw");
f.seek(0);
BufferedInputStream ipt = new BufferedInputStream(conn.getInputStream());
byte[] buf = new byte[512];
int len = 0;
while ((len = ipt.read(buf, 0, 512)) != -1 && mMission.running) {
f.write(buf, 0, len);
notifyProgress(len);
if (Thread.currentThread().interrupted()) {
break;
}
}
f.close();
ipt.close();
}
} catch (Exception e) {
notifyError(DownloadMission.ERROR_UNKNOWN);
}
if (mMission.errCode == -1 && mMission.running) {
notifyFinished();
}
}
private void notifyProgress(final long len) {
synchronized (mMission) {
mMission.notifyProgress(len);
}
}
private void notifyError(final int err) {
synchronized (mMission) {
mMission.notifyError(err);
mMission.pause();
}
}
private void notifyFinished() {
synchronized (mMission) {
mMission.notifyFinished();
}
}
}

View File

@ -0,0 +1,88 @@
package us.shandian.giga.get;
import android.content.Context;
import java.util.Map.Entry;
import java.util.HashMap;
public class FilteredDownloadManagerWrapper implements DownloadManager
{
private boolean mDownloaded = false; // T=Filter downloaded files; F=Filter downloading files
private DownloadManager mManager;
private HashMap<Integer, Integer> mElementsMap = new HashMap<Integer, Integer>();
public FilteredDownloadManagerWrapper(DownloadManager manager, boolean filterDownloaded) {
mManager = manager;
mDownloaded = filterDownloaded;
refreshMap();
}
private void refreshMap() {
mElementsMap.clear();
int size = 0;
for (int i = 0; i < mManager.getCount(); i++) {
if (mManager.getMission(i).finished == mDownloaded) {
mElementsMap.put(size++, i);
}
}
}
private int toRealPosition(int pos) {
if (mElementsMap.containsKey(pos)) {
return mElementsMap.get(pos);
} else {
return -1;
}
}
private int toFakePosition(int pos) {
for (Entry<Integer, Integer> entry : mElementsMap.entrySet()) {
if (entry.getValue() == pos) {
return entry.getKey();
}
}
return -1;
}
@Override
public int startMission(String url, String name, int threads) {
int ret = mManager.startMission(url, name, threads);
refreshMap();
return toFakePosition(ret);
}
@Override
public void resumeMission(int id) {
mManager.resumeMission(toRealPosition(id));
}
@Override
public void pauseMission(int id) {
mManager.pauseMission(toRealPosition(id));
}
@Override
public void deleteMission(int id) {
mManager.deleteMission(toRealPosition(id));
}
@Override
public DownloadMission getMission(int id) {
return mManager.getMission(toRealPosition(id));
}
@Override
public int getCount() {
return mElementsMap.size();
}
@Override
public String getLocation() {
return mManager.getLocation();
}
}

View File

@ -0,0 +1,186 @@
package us.shandian.giga.service;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
import android.support.v4.app.NotificationCompat.Builder;
import android.util.Log;
import org.schabi.newpipe.R;
import us.shandian.giga.get.DownloadManager;
import us.shandian.giga.get.DownloadManagerImpl;
import us.shandian.giga.get.DownloadMission;
import org.schabi.newpipe.download.MainActivity;
import us.shandian.giga.util.Settings;
import static org.schabi.newpipe.BuildConfig.DEBUG;
public class DownloadManagerService extends Service implements DownloadMission.MissionListener
{
private static final String TAG = DownloadManagerService.class.getSimpleName();
private DMBinder mBinder;
private DownloadManager mManager;
private Notification mNotification;
private Handler mHandler;
private long mLastTimeStamp = System.currentTimeMillis();
@Override
public void onCreate() {
super.onCreate();
if (DEBUG) {
Log.d(TAG, "onCreate");
}
mBinder = new DMBinder();
if (mManager == null) {
String path = Settings.getInstance(this).getString(Settings.DOWNLOAD_DIRECTORY, Settings.DEFAULT_PATH);
mManager = new DownloadManagerImpl(this, path);
if (DEBUG) {
Log.d(TAG, "mManager == null");
Log.d(TAG, "Download directory: " + path);
}
}
Intent i = new Intent();
i.setAction(Intent.ACTION_MAIN);
i.setClass(this, MainActivity.class);
Drawable icon = this.getResources().getDrawable(R.mipmap.ic_launcher);
Builder builder = new Builder(this)
.setContentIntent(PendingIntent.getActivity(this, 0, i, 0))
.setSmallIcon(android.R.drawable.stat_sys_download)
.setLargeIcon(((BitmapDrawable) icon).getBitmap())
.setContentTitle(getString(R.string.msg_running))
.setContentText(getString(R.string.msg_running_detail));
PendingIntent pendingIntent =
PendingIntent.getActivity(
this,
0,
new Intent(this, MainActivity.class)
.setAction(MainActivity.INTENT_LIST),
PendingIntent.FLAG_UPDATE_CURRENT
);
builder.setContentIntent(pendingIntent);
mNotification = builder.build();
HandlerThread thread = new HandlerThread("ServiceMessenger");
thread.start();
mHandler = new Handler(thread.getLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0) {
int runningCount = 0;
for (int i = 0; i < mManager.getCount(); i++) {
if (mManager.getMission(i).running) {
runningCount++;
}
}
updateState(runningCount);
}
}
};
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (DEBUG) {
Log.d(TAG, "Starting");
}
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
if (DEBUG) {
Log.d(TAG, "Destroying");
}
for (int i = 0; i < mManager.getCount(); i++) {
mManager.pauseMission(i);
}
stopForeground(true);
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void onProgressUpdate(long done, long total) {
long now = System.currentTimeMillis();
long delta = now - mLastTimeStamp;
if (delta > 2000) {
postUpdateMessage();
mLastTimeStamp = now;
}
}
@Override
public void onFinish() {
postUpdateMessage();
}
@Override
public void onError(int errCode) {
postUpdateMessage();
}
private void postUpdateMessage() {
mHandler.sendEmptyMessage(0);
}
private void updateState(int runningCount) {
if (runningCount == 0) {
stopForeground(true);
} else {
startForeground(1000, mNotification);
}
}
// Wrapper of DownloadManager
public class DMBinder extends Binder {
public DownloadManager getDownloadManager() {
return mManager;
}
public void onMissionAdded(DownloadMission mission) {
mission.addListener(DownloadManagerService.this);
postUpdateMessage();
}
public void onMissionRemoved(DownloadMission mission) {
mission.removeListener(DownloadManagerService.this);
postUpdateMessage();
}
}
}

View File

@ -0,0 +1,340 @@
package us.shandian.giga.ui.adapter;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.MimeTypeMap;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.support.v7.widget.RecyclerView;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import org.schabi.newpipe.R;
import us.shandian.giga.get.DownloadManager;
import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.service.DownloadManagerService;
import us.shandian.giga.ui.common.ProgressDrawable;
import us.shandian.giga.util.Utility;
public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHolder>
{
private static final Map<Integer, String> ALGORITHMS = new HashMap<>();
static {
ALGORITHMS.put(R.id.md5, "MD5");
ALGORITHMS.put(R.id.sha1, "SHA1");
}
private Context mContext;
private LayoutInflater mInflater;
private DownloadManager mManager;
private DownloadManagerService.DMBinder mBinder;
private int mLayout;
public MissionAdapter(Context context, DownloadManagerService.DMBinder binder, DownloadManager manager, boolean isLinear) {
mContext = context;
mManager = manager;
mBinder = binder;
mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mLayout = isLinear ? R.layout.mission_item_linear : R.layout.mission_item;
}
@Override
public MissionAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final ViewHolder h = new ViewHolder(mInflater.inflate(mLayout, parent, false));
h.menu.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
buildPopup(h);
}
});
/*h.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showDetail(h);
}
});*/
return h;
}
@Override
public void onViewRecycled(MissionAdapter.ViewHolder h) {
super.onViewRecycled(h);
h.mission.removeListener(h.observer);
h.mission = null;
h.observer = null;
h.progress = null;
h.position = -1;
h.lastTimeStamp = -1;
h.lastDone = -1;
h.colorId = 0;
}
@Override
public void onBindViewHolder(MissionAdapter.ViewHolder h, int pos) {
DownloadMission ms = mManager.getMission(pos);
h.mission = ms;
h.position = pos;
Utility.FileType type = Utility.getFileType(ms.name);
//h.icon.setImageResource(Utility.getIconForFileType(type));
h.name.setText(ms.name);
h.size.setText(Utility.formatBytes(ms.length));
h.progress = new ProgressDrawable(mContext, Utility.getBackgroundForFileType(type), Utility.getForegroundForFileType(type));
h.bkg.setBackgroundDrawable(h.progress);
h.observer = new MissionObserver(this, h);
ms.addListener(h.observer);
updateProgress(h);
}
@Override
public int getItemCount() {
return mManager.getCount();
}
@Override
public long getItemId(int position) {
return position;
}
private void updateProgress(ViewHolder h) {
updateProgress(h, false);
}
private void updateProgress(ViewHolder h, boolean finished) {
if (h.mission == null) return;
long now = System.currentTimeMillis();
if (h.lastTimeStamp == -1) {
h.lastTimeStamp = now;
}
if (h.lastDone == -1) {
h.lastDone = h.mission.done;
}
long deltaTime = now - h.lastTimeStamp;
long deltaDone = h.mission.done - h.lastDone;
if (deltaTime == 0 || deltaTime > 1000 || finished) {
if (h.mission.errCode > 0) {
h.status.setText(R.string.msg_error);
} else {
float progress = (float) h.mission.done / h.mission.length;
h.status.setText(String.format("%.2f%%", progress * 100));
h.progress.setProgress(progress);
}
}
if (deltaTime > 1000 && deltaDone > 0) {
float speed = (float) deltaDone / deltaTime;
String speedStr = Utility.formatSpeed(speed * 1000);
String sizeStr = Utility.formatBytes(h.mission.length);
h.size.setText(sizeStr + " " + speedStr);
h.lastTimeStamp = now;
h.lastDone = h.mission.done;
}
}
private void buildPopup(final ViewHolder h) {
PopupMenu popup = new PopupMenu(mContext, h.menu);
popup.inflate(R.menu.mission);
Menu menu = popup.getMenu();
MenuItem start = menu.findItem(R.id.start);
MenuItem pause = menu.findItem(R.id.pause);
MenuItem view = menu.findItem(R.id.view);
MenuItem delete = menu.findItem(R.id.delete);
MenuItem checksum = menu.findItem(R.id.checksum);
// Set to false first
start.setVisible(false);
pause.setVisible(false);
view.setVisible(false);
delete.setVisible(false);
checksum.setVisible(false);
if (!h.mission.finished) {
if (!h.mission.running) {
if (h.mission.errCode == -1) {
start.setVisible(true);
}
delete.setVisible(true);
} else {
pause.setVisible(true);
}
} else {
view.setVisible(true);
delete.setVisible(true);
checksum.setVisible(true);
}
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.start:
mManager.resumeMission(h.position);
mBinder.onMissionAdded(mManager.getMission(h.position));
return true;
case R.id.pause:
mManager.pauseMission(h.position);
mBinder.onMissionRemoved(mManager.getMission(h.position));
h.lastTimeStamp = -1;
h.lastDone = -1;
return true;
case R.id.view:
Intent i = new Intent();
i.setAction(Intent.ACTION_VIEW);
File f = new File(h.mission.location + "/" + h.mission.name);
String ext = Utility.getFileExt(h.mission.name);
if (ext == null) return false;
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.substring(1));
if (f.exists()) {
i.setDataAndType(Uri.fromFile(f), mime);
try {
mContext.startActivity(i);
} catch (Exception e) {
}
}
return true;
case R.id.delete:
mManager.deleteMission(h.position);
notifyDataSetChanged();
return true;
case R.id.md5:
case R.id.sha1:
DownloadMission mission = mManager.getMission(h.position);
new ChecksumTask().execute(mission.location + "/" + mission.name, ALGORITHMS.get(id));
return true;
default:
return false;
}
}
});
popup.show();
}
private class ChecksumTask extends AsyncTask<String, Void, String> {
ProgressDialog prog;
@Override
protected void onPreExecute() {
super.onPreExecute();
// Create dialog
prog = new ProgressDialog(mContext);
prog.setCancelable(false);
prog.setMessage(mContext.getString(R.string.msg_wait));
prog.show();
}
@Override
protected String doInBackground(String... params) {
return Utility.checksum(params[0], params[1]);
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
prog.dismiss();
Utility.copyToClipboard(mContext, result);
}
}
static class ViewHolder extends RecyclerView.ViewHolder {
public DownloadMission mission;
public int position;
public TextView status;
public ImageView icon;
public TextView name;
public TextView size;
public View bkg;
public ImageView menu;
public ProgressDrawable progress;
public MissionObserver observer;
public long lastTimeStamp = -1;
public long lastDone = -1;
public int colorId = 0;
public ViewHolder(View v) {
super(v);
status = Utility.findViewById(v, R.id.item_status);
icon = Utility.findViewById(v, R.id.item_icon);
name = Utility.findViewById(v, R.id.item_name);
size = Utility.findViewById(v, R.id.item_size);
bkg = Utility.findViewById(v, R.id.item_bkg);
menu = Utility.findViewById(v, R.id.item_more);
}
}
static class MissionObserver implements DownloadMission.MissionListener {
private MissionAdapter mAdapter;
private ViewHolder mHolder;
public MissionObserver(MissionAdapter adapter, ViewHolder holder) {
mAdapter = adapter;
mHolder = holder;
}
@Override
public void onProgressUpdate(long done, long total) {
mAdapter.updateProgress(mHolder);
}
@Override
public void onFinish() {
//mAdapter.mManager.deleteMission(mHolder.position);
// TODO Notification
//mAdapter.notifyDataSetChanged();
if (mHolder.mission != null) {
mHolder.size.setText(Utility.formatBytes(mHolder.mission.length));
mAdapter.updateProgress(mHolder, true);
}
}
@Override
public void onError(int errCode) {
mAdapter.updateProgress(mHolder);
}
}
}

View File

@ -0,0 +1,84 @@
package us.shandian.giga.ui.common;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import org.schabi.newpipe.R;
import us.shandian.giga.get.DownloadMission;
public class BlockGraphView extends View
{
private static int BLOCKS_PER_LINE = 15;
private int mForeground, mBackground;
private int mBlockSize, mLineCount;
private DownloadMission mMission;
public BlockGraphView(Context context) {
this(context, null);
}
public BlockGraphView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BlockGraphView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
try {
TypedArray array = context.obtainStyledAttributes(R.styleable.AppCompatTheme);
mBackground = array.getColor(R.styleable.AppCompatTheme_colorPrimary, 0);
mForeground = array.getColor(R.styleable.AppCompatTheme_colorPrimaryDark, 0);
array.recycle();
} catch (Exception e) {
}
}
public void setMission(DownloadMission mission) {
mMission = mission;
setWillNotDraw(false);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
mBlockSize = width / BLOCKS_PER_LINE - 1;
mLineCount = (int) Math.ceil((double) mMission.blocks / BLOCKS_PER_LINE);
int height = mLineCount * (mBlockSize + 1);
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint p = new Paint();
p.setFlags(Paint.ANTI_ALIAS_FLAG);
for (int i = 0; i < mLineCount; i++) {
for (int j = 0; j < BLOCKS_PER_LINE; j++) {
long pos = i * BLOCKS_PER_LINE + j;
if (pos >= mMission.blocks) {
break;
}
if (mMission.isBlockPreserved(pos)) {
p.setColor(mForeground);
} else {
p.setColor(mBackground);
}
int left = (mBlockSize + 1) * j;
int right = left + mBlockSize;
int top = (mBlockSize + 1) * i;
int bottom = top + mBlockSize;
canvas.drawRect(left, top, right, bottom, p);
}
}
}
}

View File

@ -0,0 +1,231 @@
package us.shandian.giga.ui.common;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.FrameLayout;
/*
* From GitHub Gist: https://gist.github.com/Jogan/9def6110edf3247825c9
*/
public class FloatingActionButton extends View implements Animator.AnimatorListener {
final static OvershootInterpolator overshootInterpolator = new OvershootInterpolator();
final static AccelerateInterpolator accelerateInterpolator = new AccelerateInterpolator();
Context context;
Paint mButtonPaint;
Paint mDrawablePaint;
Bitmap mBitmap;
boolean mHidden = false;
public FloatingActionButton(Context context) {
super(context);
this.context = context;
init(Color.WHITE);
}
public void setFloatingActionButtonColor(int FloatingActionButtonColor) {
init(FloatingActionButtonColor);
}
public void setFloatingActionButtonDrawable(Drawable FloatingActionButtonDrawable) {
mBitmap = ((BitmapDrawable) FloatingActionButtonDrawable).getBitmap();
invalidate();
}
public void init(int FloatingActionButtonColor) {
setWillNotDraw(false);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mButtonPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mButtonPaint.setColor(FloatingActionButtonColor);
mButtonPaint.setStyle(Paint.Style.FILL);
mButtonPaint.setShadowLayer(10.0f, 0.0f, 3.5f, Color.argb(100, 0, 0, 0));
mDrawablePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
setClickable(true);
canvas.drawCircle(getPaddingLeft() + getRealWidth() / 2,
getPaddingTop() + getRealHeight() / 2,
(float) getRealWidth() / 2.6f, mButtonPaint);
canvas.drawBitmap(mBitmap, getPaddingLeft() + (getRealWidth() - mBitmap.getWidth()) / 2,
getPaddingTop() + (getRealHeight() - mBitmap.getHeight()) / 2, mDrawablePaint);
}
private int getRealWidth() {
return getWidth() - getPaddingLeft() - getPaddingRight();
}
private int getRealHeight() {
return getHeight() - getPaddingTop() - getPaddingBottom();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
setAlpha(1.0f);
} else if (event.getAction() == MotionEvent.ACTION_DOWN) {
setAlpha(0.6f);
}
return super.onTouchEvent(event);
}
@Override
public void onAnimationCancel(Animator anim) {
}
@Override
public void onAnimationEnd(Animator anim) {
if (mHidden) {
setVisibility(View.GONE);
}
}
@Override
public void onAnimationRepeat(Animator anim) {
}
@Override
public void onAnimationStart(Animator anim) {
}
public void hideFloatingActionButton() {
if (!mHidden) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(this, "scaleX", 1, 0);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(this, "scaleY", 1, 0);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(scaleX, scaleY);
animSetXY.setInterpolator(accelerateInterpolator);
animSetXY.setDuration(100);
animSetXY.start();
animSetXY.addListener(this);
mHidden = true;
}
}
public void showFloatingActionButton() {
if (mHidden) {
setVisibility(View.VISIBLE);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(this, "scaleX", 0, 1);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(this, "scaleY", 0, 1);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(scaleX, scaleY);
animSetXY.setInterpolator(overshootInterpolator);
animSetXY.setDuration(200);
animSetXY.start();
mHidden = false;
}
}
public boolean isHidden() {
return mHidden;
}
static public class Builder {
private FrameLayout.LayoutParams params;
private final Activity activity;
int gravity = Gravity.BOTTOM | Gravity.RIGHT; // default bottom right
Drawable drawable;
int color = Color.WHITE;
int size = 0;
float scale = 0;
int paddingLeft = 0,
paddingTop = 0,
paddingBottom = 0,
paddingRight = 0;
public Builder(Activity context) {
scale = context.getResources().getDisplayMetrics().density;
size = convertToPixels(72, scale); // default size is 72dp by 72dp
params = new FrameLayout.LayoutParams(size, size);
params.gravity = gravity;
this.activity = context;
}
/**
* Sets the gravity for the FAB
*/
public Builder withGravity(int gravity) {
this.gravity = gravity;
return this;
}
/**
* Sets the margins for the FAB in dp
*/
public Builder withPaddings(int left, int top, int right, int bottom) {
paddingLeft = convertToPixels(left, scale);
paddingTop = convertToPixels(top, scale);
paddingRight = convertToPixels(right, scale);
paddingBottom = convertToPixels(bottom, scale);
return this;
}
/**
* Sets the FAB drawable
*/
public Builder withDrawable(final Drawable drawable) {
this.drawable = drawable;
return this;
}
/**
* Sets the FAB color
*/
public Builder withButtonColor(final int color) {
this.color = color;
return this;
}
/**
* Sets the FAB size in dp
*/
public Builder withButtonSize(int size) {
size = convertToPixels(size, scale);
params = new FrameLayout.LayoutParams(size, size);
return this;
}
public FloatingActionButton create() {
final FloatingActionButton button = new FloatingActionButton(activity);
button.setFloatingActionButtonColor(this.color);
button.setFloatingActionButtonDrawable(this.drawable);
button.setPadding(paddingLeft, paddingTop, paddingBottom, paddingRight);
params.gravity = this.gravity;
ViewGroup root = (ViewGroup) activity.findViewById(android.R.id.content);
root.addView(button, params);
return button;
}
// The calculation (value * scale + 0.5f) is a widely used to convert to dps to pixel units
// based on density scale
// see developer.android.com (Supporting Multiple Screen Sizes)
private int convertToPixels(int dp, float scale){
return (int) (dp * scale + 0.5f) ;
}
}
}

View File

@ -0,0 +1,58 @@
package us.shandian.giga.ui.common;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
public class ProgressDrawable extends Drawable
{
private float mProgress = 0.0f;
private int mBackgroundColor, mForegroundColor;
public ProgressDrawable(Context context, int background, int foreground) {
this(context.getResources().getColor(background), context.getResources().getColor(foreground));
}
public ProgressDrawable(int background, int foreground) {
mBackgroundColor = background;
mForegroundColor = foreground;
}
public void setProgress(float progress) {
mProgress = progress;
invalidateSelf();
}
@Override
public void draw(Canvas canvas) {
int width = canvas.getWidth();
int height = canvas.getHeight();
Paint paint = new Paint();
paint.setColor(mBackgroundColor);
canvas.drawRect(0, 0, width, height, paint);
paint.setColor(mForegroundColor);
canvas.drawRect(0, 0, (int) (mProgress * width), height, paint);
}
@Override
public void setAlpha(int alpha) {
// Unsupported
}
@Override
public void setColorFilter(ColorFilter filter) {
// Unsupported
}
@Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
}

View File

@ -0,0 +1,26 @@
package us.shandian.giga.ui.common;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import org.schabi.newpipe.R;
import us.shandian.giga.util.Utility;
public abstract class ToolbarActivity extends ActionBarActivity
{
protected Toolbar mToolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutResource());
mToolbar = Utility.findViewById(this, R.id.toolbar);
setSupportActionBar(mToolbar);
}
protected abstract int getLayoutResource();
}

View File

@ -0,0 +1,13 @@
package us.shandian.giga.ui.fragment;
import us.shandian.giga.get.DownloadManager;
import us.shandian.giga.service.DownloadManagerService;
public class AllMissionsFragment extends MissionsFragment
{
@Override
protected DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder) {
return binder.getDownloadManager();
}
}

View File

@ -0,0 +1,13 @@
package us.shandian.giga.ui.fragment;
import us.shandian.giga.get.DownloadManager;
import us.shandian.giga.get.FilteredDownloadManagerWrapper;
import us.shandian.giga.service.DownloadManagerService;
public class DownloadedMissionsFragment extends MissionsFragment
{
@Override
protected DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder) {
return new FilteredDownloadManagerWrapper(binder.getDownloadManager(), true);
}
}

View File

@ -0,0 +1,13 @@
package us.shandian.giga.ui.fragment;
import us.shandian.giga.get.DownloadManager;
import us.shandian.giga.get.FilteredDownloadManagerWrapper;
import us.shandian.giga.service.DownloadManagerService;
public class DownloadingMissionsFragment extends MissionsFragment
{
@Override
protected DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder) {
return new FilteredDownloadManagerWrapper(binder.getDownloadManager(), false);
}
}

View File

@ -0,0 +1,134 @@
package us.shandian.giga.ui.fragment;
import android.app.Activity;
import android.app.Fragment;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.IBinder;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import org.schabi.newpipe.R;
import us.shandian.giga.get.DownloadManager;
import us.shandian.giga.service.DownloadManagerService;
import us.shandian.giga.ui.adapter.MissionAdapter;
import us.shandian.giga.util.Utility;
public abstract class MissionsFragment extends Fragment
{
private DownloadManager mManager;
private DownloadManagerService.DMBinder mBinder;
private SharedPreferences mPrefs;
private boolean mLinear = false;
private MenuItem mSwitch;
private RecyclerView mList;
private MissionAdapter mAdapter;
private GridLayoutManager mGridManager;
private LinearLayoutManager mLinearManager;
private Activity mActivity;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
mBinder = (DownloadManagerService.DMBinder) binder;
mManager = setupDownloadManager(mBinder);
updateList();
}
@Override
public void onServiceDisconnected(ComponentName name) {
// What to do?
}
};
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.missions, container, false);
mPrefs = getActivity().getSharedPreferences("mode", Context.MODE_WORLD_READABLE);
mLinear = mPrefs.getBoolean("linear", false);
// Bind the service
Intent i = new Intent();
i.setClass(getActivity(), DownloadManagerService.class);
getActivity().bindService(i, mConnection, Context.BIND_AUTO_CREATE);
// Views
mList = Utility.findViewById(v, R.id.mission_recycler);
// Init
mGridManager = new GridLayoutManager(getActivity(), 2);
mLinearManager = new LinearLayoutManager(getActivity());
mList.setLayoutManager(mGridManager);
setHasOptionsMenu(true);
return v;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mActivity = activity;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.frag_mission, menu);
mSwitch = menu.findItem(R.id.switch_mode);
mSwitch.setIcon(mLinear ? R.drawable.grid : R.drawable.list);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.switch_mode:
mLinear = !mLinear;
updateList();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public void notifyChange() {
mAdapter.notifyDataSetChanged();
}
private void updateList() {
mAdapter = new MissionAdapter(mActivity, mBinder, mManager, mLinear);
if (mLinear) {
mList.setLayoutManager(mLinearManager);
} else {
mList.setLayoutManager(mGridManager);
}
mList.setAdapter(mAdapter);
if (mSwitch != null) {
mSwitch.setIcon(mLinear ? R.drawable.grid : R.drawable.list);
}
mPrefs.edit().putBoolean("linear", mLinear).commit();
}
protected abstract DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder);
}

View File

@ -0,0 +1,84 @@
package us.shandian.giga.util;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.os.Build;
import android.os.Environment;
import java.io.File;
import java.io.PrintWriter;
public class CrashHandler implements Thread.UncaughtExceptionHandler
{
public static String CRASH_DIR = Environment.getExternalStorageDirectory().getPath() + "/GigaCrash/";
public static String CRASH_LOG = CRASH_DIR + "last_crash.log";
public static String CRASH_TAG = CRASH_DIR + ".crashed";
private static String ANDROID = Build.VERSION.RELEASE;
private static String MODEL = Build.MODEL;
private static String MANUFACTURER = Build.MANUFACTURER;
public static String VERSION = "Unknown";
private Thread.UncaughtExceptionHandler mPrevious;
public static void init(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
VERSION = info.versionName + info.versionCode;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void register() {
new CrashHandler();
}
private CrashHandler() {
mPrevious = Thread.currentThread().getUncaughtExceptionHandler();
Thread.currentThread().setUncaughtExceptionHandler(this);
}
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
File f = new File(CRASH_LOG);
if (f.exists()) {
f.delete();
} else {
try {
new File(CRASH_DIR).mkdirs();
f.createNewFile();
} catch (Exception e) {
return;
}
}
PrintWriter p;
try {
p = new PrintWriter(f);
} catch (Exception e) {
return;
}
p.write("Android Version: " + ANDROID + "\n");
p.write("Device Model: " + MODEL + "\n");
p.write("Device Manufacturer: " + MANUFACTURER + "\n");
p.write("App Version: " + VERSION + "\n");
p.write("*********************\n");
throwable.printStackTrace(p);
p.close();
try {
new File(CRASH_TAG).createNewFile();
} catch (Exception e) {
return;
}
if (mPrevious != null) {
mPrevious.uncaughtException(thread, throwable);
}
}
}

View File

@ -0,0 +1,60 @@
package us.shandian.giga.util;
import android.content.Context;
import android.content.SharedPreferences;
/*
Settings Provider
*/
public class Settings
{
public static final String XML_NAME = "settings";
public static final String DOWNLOAD_DIRECTORY = "download_directory";
public static final String DEFAULT_PATH = "/storage/sdcard0/GigaGet";
private static Settings sInstance;
private SharedPreferences mPrefs;
public static Settings getInstance(Context context) {
if (sInstance == null) {
sInstance = new Settings(context);
}
return sInstance;
}
private Settings(Context context) {
mPrefs = context.getSharedPreferences(XML_NAME, Context.MODE_PRIVATE);
}
public Settings putBoolean(String key, boolean value) {
mPrefs.edit().putBoolean(key, value).commit();
return this;
}
public boolean getBoolean(String key, boolean def) {
return mPrefs.getBoolean(key, def);
}
public Settings putInt(String key, int value) {
mPrefs.edit().putInt(key, value).commit();
return this;
}
public int getInt(String key, int defValue) {
return mPrefs.getInt(key, defValue);
}
public Settings putString(String key, String value) {
mPrefs.edit().putString(key, value).commit();
return this;
}
public String getString(String key, String defValue) {
return mPrefs.getString(key, defValue);
}
}

View File

@ -0,0 +1,334 @@
package us.shandian.giga.util;
import android.app.Activity;
import android.content.ClipboardManager;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.view.View;
import android.widget.Toast;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.schabi.newpipe.R;
import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.util.Settings;
import com.nononsenseapps.filepicker.FilePickerActivity;
import com.nononsenseapps.filepicker.AbstractFilePickerFragment;
public class Utility
{
public static enum FileType {
APP,
VIDEO,
EXCEL,
WORD,
POWERPOINT,
MUSIC,
ARCHIVE,
UNKNOWN
}
public static String formatBytes(long bytes) {
if (bytes < 1024) {
return String.format("%d B", bytes);
} else if (bytes < 1024 * 1024) {
return String.format("%.2f kB", (float) bytes / 1024);
} else if (bytes < 1024 * 1024 * 1024) {
return String.format("%.2f MB", (float) bytes / 1024 / 1024);
} else {
return String.format("%.2f GB", (float) bytes / 1024 / 1024 / 1024);
}
}
public static String formatSpeed(float speed) {
if (speed < 1024) {
return String.format("%.2f B/s", speed);
} else if (speed < 1024 * 1024) {
return String.format("%.2f kB/s", speed / 1024);
} else if (speed < 1024 * 1024 * 1024) {
return String.format("%.2f MB/s", speed / 1024 / 1024);
} else {
return String.format("%.2f GB/s", speed / 1024 / 1024 / 1024);
}
}
public static void writeToFile(String fileName, String content) {
try {
writeToFile(fileName, content.getBytes("UTF-8"));
} catch (Exception e) {
}
}
public static void writeToFile(String fileName, byte[] content) {
File f = new File(fileName);
if (!f.exists()) {
try {
f.createNewFile();
} catch (Exception e) {
}
}
try {
FileOutputStream opt = new FileOutputStream(f, false);
opt.write(content, 0, content.length);
opt.close();
} catch (Exception e) {
}
}
public static String readFromFile(String file) {
try {
File f = new File(file);
if (!f.exists() || !f.canRead()) {
return null;
}
BufferedInputStream ipt = new BufferedInputStream(new FileInputStream(f));
byte[] buf = new byte[512];
StringBuilder sb = new StringBuilder();
while (ipt.available() > 0) {
int len = ipt.read(buf, 0, 512);
sb.append(new String(buf, 0, len, "UTF-8"));
}
ipt.close();
return sb.toString();
} catch (Exception e) {
return null;
}
}
public static <T> T findViewById(View v, int id) {
return (T) v.findViewById(id);
}
public static <T> T findViewById(Activity activity, int id) {
return (T) activity.findViewById(id);
}
public static String getFileExt(String url) {
if (url.indexOf("?")>-1) {
url = url.substring(0,url.indexOf("?"));
}
if (url.lastIndexOf(".") == -1) {
return null;
} else {
String ext = url.substring(url.lastIndexOf(".") );
if (ext.indexOf("%")>-1) {
ext = ext.substring(0,ext.indexOf("%"));
}
if (ext.indexOf("/")>-1) {
ext = ext.substring(0,ext.indexOf("/"));
}
return ext.toLowerCase();
}
}
public static String getErrorString(Context context, int code) {
switch (code) {
case DownloadMission.ERROR_SERVER_UNSUPPORTED:
return context.getString(R.string.msg_server_unsupported);
default:
return "";
}
}
public static FileType getFileType(String file) {
if (file.endsWith(".apk")) {
return FileType.APP;
} else if (file.endsWith(".mp3") || file.endsWith(".wav") || file.endsWith(".flac")) {
return FileType.MUSIC;
} else if (file.endsWith(".mp4") || file.endsWith(".mpeg") || file.endsWith(".rm") || file.endsWith(".rmvb")
|| file.endsWith(".flv") || file.endsWith(".webp")) {
return FileType.VIDEO;
} else if (file.endsWith(".doc") || file.endsWith(".docx")) {
return FileType.WORD;
} else if (file.endsWith(".xls") || file.endsWith(".xlsx")) {
return FileType.EXCEL;
} else if (file.endsWith(".ppt") || file.endsWith(".pptx")) {
return FileType.POWERPOINT;
} else if (file.endsWith(".zip") || file.endsWith(".rar") || file.endsWith(".7z") || file.endsWith(".gz")
|| file.endsWith("tar") || file.endsWith(".bz")) {
return FileType.ARCHIVE;
} else {
return FileType.UNKNOWN;
}
}
public static int getBackgroundForFileType(FileType type) {
switch (type) {
case APP:
return R.color.orange;
case MUSIC:
return R.color.cyan;
case ARCHIVE:
return R.color.blue;
case VIDEO:
return R.color.green;
case WORD:
case EXCEL:
case POWERPOINT:
return R.color.brown;
case UNKNOWN:
default:
return R.color.bluegray;
}
}
public static int getForegroundForFileType(FileType type) {
switch (type) {
case APP:
return R.color.orange_dark;
case MUSIC:
return R.color.cyan_dark;
case ARCHIVE:
return R.color.blue_dark;
case VIDEO:
return R.color.green_dark;
case WORD:
case EXCEL:
case POWERPOINT:
return R.color.brown_dark;
case UNKNOWN:
default:
return R.color.bluegray_dark;
}
}
public static int getThemeForFileType(FileType type) {
/*switch (type) {
case APP:
return R.style.Theme_App_Orange;
case MUSIC:
return R.style.Theme_App_Cyan;
case ARCHIVE:
return R.style.Theme_App_Blue;
case VIDEO:
return R.style.Theme_App_Green;
case WORD:
case EXCEL:
case POWERPOINT:
return R.style.Theme_App_Brown;
case UNKNOWN:
default:
return R.style.Theme_App_BlueGray;
}*/
return 0;
}
public static int getIconForFileType(FileType type) {
switch (type) {
case APP:
return R.drawable.apps;
case MUSIC:
return R.drawable.music;
case ARCHIVE:
return R.drawable.archive;
case VIDEO:
return R.drawable.video;
case WORD:
return R.drawable.word;
case EXCEL:
return R.drawable.excel;
case POWERPOINT:
return R.drawable.powerpoint;
case UNKNOWN:
default:
return R.drawable.unknown;
}
}
public static boolean isDirectoryAvailble(String path) {
File dir = new File(path);
return dir.exists() && dir.isDirectory();
}
public static boolean isDownloadDirectoryAvailble(Context context) {
return isDirectoryAvailble(Settings.getInstance(context).getString(Settings.DOWNLOAD_DIRECTORY, Settings.DEFAULT_PATH));
}
public static void changeDownloadDirectory(Context context, String path) {
Settings.getInstance(context).putString(Settings.DOWNLOAD_DIRECTORY, path);
}
public static void showDirectoryChooser(Activity activity) {
Intent i = new Intent(activity, FilePickerActivity.class);
i.setAction(Intent.ACTION_GET_CONTENT);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true);
i.putExtra(FilePickerActivity.EXTRA_MODE, AbstractFilePickerFragment.MODE_DIR);
activity.startActivityForResult(i, 233);
}
public static void checkAndReshow(Activity activity){
if (!isDownloadDirectoryAvailble(activity)){
Toast.makeText(activity.getApplicationContext(),
R.string.no_available_dir, Toast.LENGTH_LONG).show();
showDirectoryChooser(activity);
}
}
public static void copyToClipboard(Context context, String str) {
ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
cm.setPrimaryClip(ClipData.newPlainText("text", str));
Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show();
}
public static String checksum(String path, String algorithm) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
FileInputStream i = null;
try {
i = new FileInputStream(path);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
byte[] buf = new byte[1024];
int len = 0;
try {
while ((len = i.read(buf)) != -1) {
md.update(buf, 0, len);
}
} catch (IOException e) {
}
byte[] digest = md.digest();
// HEX
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#44000000"
android:endColor="#00000000"
android:angle="270"
android:type="linear"/>
</shape>

View File

@ -0,0 +1,18 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/frame"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<FrameLayout
android:id="@+id/nav"
android:layout_gravity="left"
android:layout_width="240dp"
android:layout_height="match_parent">
</FrameLayout>
</RelativeLayout>

View File

@ -0,0 +1,111 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/actionBarItemBackground"/>
<View
android:layout_width="match_parent"
android:layout_height="4dp"
android:background="@drawable/action_shadow"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:layout_marginTop="9dp"
android:layout_marginBottom="24dp"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/msg_url"/>
<EditText
android:id="@+id/url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ems="10"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:layout_marginBottom="24dp"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/msg_name"/>
<EditText
android:id="@+id/file_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"/>
<Button
android:id="@+id/fetch_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/msg_fetch_filename"
android:textColor="?attr/colorPrimary"
android:background="?android:selectableItemBackground"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:layout_marginBottom="24dp"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/msg_threads"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/threads_count"
android:layout_width="20dp"
android:layout_height="wrap_content"
android:text="0"/>
<SeekBar
android:id="@+id/threads"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="31"
android:progress="3"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,81 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="match_parent">
<RelativeLayout
android:id="@+id/item_bkg"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_margin="2dp"
android:background="@color/bluegray">
<RelativeLayout
android:id="@+id/item_title_line"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginTop="2dp">
<TextView
android:id="@+id/item_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="6dp"
android:singleLine="true"
android:text="0%"
android:textSize="20sp"
android:textColor="@color/white"/>
<ImageView
style="?attr/buttonBarButtonStyle"
android:id="@+id/item_more"
android:layout_width="49dp"
android:layout_height="49dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="1dp"
android:src="@drawable/ic_menu_more"
android:scaleType="centerInside"/>
</RelativeLayout>
<ImageView
android:id="@+id/item_icon"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_below="@id/item_title_line"
android:layout_centerHorizontal="true"
android:scaleType="fitXY"
android:gravity="center"
android:padding="10dp"/>
<TextView
android:id="@+id/item_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/item_icon"
android:padding="6dp"
android:singleLine="true"
android:ellipsize="end"
android:text="XXX.xx"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@color/white"/>
<TextView
android:id="@+id/item_size"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/item_name"
android:padding="6dp"
android:singleLine="true"
android:text="100.00MB"
android:textSize="12sp"
android:textColor="@color/white"/>
</RelativeLayout>
</RelativeLayout>

View File

@ -0,0 +1,83 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="match_parent">
<RelativeLayout
android:id="@+id/item_bkg"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_margin="2dp"
android:background="@color/bluegray">
<ImageView
android:id="@+id/item_icon"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_centerVertical="true"
android:scaleType="fitXY"
android:gravity="center"
android:padding="15dp"/>
<LinearLayout
android:id="@+id/item_name_and_size"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/item_icon"
android:layout_toLeftOf="@+id/item_status"
android:layout_centerVertical="true"
android:orientation="vertical">
<TextView
android:id="@+id/item_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="6dp"
android:singleLine="true"
android:ellipsize="end"
android:text="XXX.xx"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@color/white"/>
<TextView
android:id="@+id/item_size"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/item_name"
android:padding="6dp"
android:singleLine="true"
android:text="100.00MB"
android:textSize="12sp"
android:textColor="@color/white"/>
</LinearLayout>
<TextView
android:id="@+id/item_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/item_more"
android:layout_centerVertical="true"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:singleLine="true"
android:text="0%"
android:textSize="20sp"
android:textColor="@color/white"/>
<ImageView
style="?attr/buttonBarButtonStyle"
android:id="@+id/item_more"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="4dp"
android:src="@drawable/ic_menu_more"
android:scaleType="centerInside"/>
</RelativeLayout>
</RelativeLayout>

View File

@ -0,0 +1,12 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/mission_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>

View File

@ -0,0 +1,10 @@
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/okay"
android:title="@string/finish"
app:showAsAction="always"/>
</menu>

View File

@ -0,0 +1,11 @@
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/switch_mode"
android:title="@string/switch_mode"
android:icon="@drawable/list"
app:showAsAction="always"/>
</menu>

View File

@ -0,0 +1,37 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/start"
android:title="@string/start"/>
<item
android:id="@+id/pause"
android:title="@string/pause"/>
<item
android:id="@+id/view"
android:title="@string/view"/>
<item
android:id="@+id/delete"
android:title="@string/delete"/>
<item
android:id="@+id/checksum"
android:title="@string/checksum">
<menu>
<item
android:id="@+id/md5"
android:title="@string/md5"/>
<item
android:id="@+id/sha1"
android:title="@string/sha1"/>
</menu>
</item>
</menu>

View File

@ -34,4 +34,5 @@
<item name="android:background">@color/video_overlay_color</item>
<item name="background">@color/video_overlay_color</item>
</style>
</resources>

View File

@ -17,4 +17,26 @@
<color name="duration_text_color">#EEFFFFFF</color>
<color name="video_overlay_color">#66000000</color>
<color name="background_notification_color">#323232</color>
<!-- GigaGet Theme colors -->
<color name="blue">#2979FF</color>
<color name="blue_dark">#1565C0</color>
<color name="bluegray">#607D8B</color>
<color name="bluegray_dark">#546E7A</color>
<color name="cyan">#00BCD4</color>
<color name="cyan_dark">#00ACC1</color>
<color name="orange">#FF9800</color>
<color name="orange_dark">#EF6C00</color>
<color name="green">#4CAF50</color>
<color name="green_dark">#388E3C</color>
<color name="brown">#795548</color>
<color name="brown_dark">#5D4037</color>
<!-- GigaGet Component colors -->
<color name="white">#FFFFFF</color>
<color name="light_gray">#EFEFEF</color>
<color name="middle_gray">#E0E0E0</color>
<color name="gray">#616161</color>
</resources>

View File

@ -140,4 +140,39 @@
<string name="storage_permission_denied">Permission to access storage was denied</string>
<string name="use_exoplayer_title">Use ExoPlayer</string>
<string name="use_exoplayer_summary">Experimental</string>
<!-- Missions -->
<string name="start">Start</string>
<string name="pause">Pause</string>
<string name="view">View</string>
<string name="delete">Delete</string>
<string name="checksum">Checksum</string>
<!-- Fragment -->
<string name="add">New mission</string>
<string name="finish">Okay</string>
<string name="switch_mode">Switch between list and grid</string>
<!-- Msg -->
<string name="msg_url">Download URL</string>
<string name="msg_name">File name</string>
<string name="msg_threads">Threads</string>
<string name="msg_fetch_filename">Fetch file name</string>
<string name="msg_error">Error</string>
<string name="msg_server_unsupported">Server unsupported</string>
<string name="msg_exists">File already exists</string>
<string name="msg_url_malform">Malformed URL or Internet not available</string>
<string name="msg_running">NewPipe Downloading</string>
<string name="msg_running_detail">Click for details</string>
<string name="msg_wait">Please wait...</string>
<string name="msg_copied">Copied to clipboard.</string>
<string name="no_available_dir">Please select an available download directory.</string>
<!-- Checksum types -->
<string name="md5" translatable="false">MD5</string>
<string name="sha1" translatable="false">SHA1</string>
<!-- End of GigaGet's Strings -->
</resources>

View File

@ -48,4 +48,5 @@
<item name="background">@color/video_overlay_color</item>
</style>
</resources>