Merge branch 'download-manager' of git://github.com/satiricon/NewPipe into satiricon-download-manager
|
@ -43,4 +43,6 @@ dependencies {
|
||||||
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
||||||
compile 'com.github.nirhart:parallaxscroll:1.0'
|
compile 'com.github.nirhart:parallaxscroll:1.0'
|
||||||
compile 'com.google.android.exoplayer:exoplayer:r1.5.5'
|
compile 'com.google.android.exoplayer:exoplayer:r1.5.5'
|
||||||
|
compile 'com.google.code.gson:gson:2.3.+'
|
||||||
|
compile 'com.nononsenseapps:filepicker:2.0.5'
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,5 +125,28 @@
|
||||||
android:label="@string/general_error"
|
android:label="@string/general_error"
|
||||||
android:theme="@android:style/Theme.NoDisplay" />
|
android:theme="@android:style/Theme.NoDisplay" />
|
||||||
<activity android:name=".ErrorActivity"/>
|
<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>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -28,6 +28,8 @@ import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
import us.shandian.giga.util.Utility;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper for global settings
|
* Helper for global settings
|
||||||
*/
|
*/
|
||||||
|
@ -46,10 +48,34 @@ public class NewPipeSettings {
|
||||||
return getFolder(context, R.string.download_path_key, Environment.DIRECTORY_MOVIES);
|
return getFolder(context, R.string.download_path_key, Environment.DIRECTORY_MOVIES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getVideoDownloadPath(Context context) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
final String key = context.getString(R.string.download_path_key);
|
||||||
|
String downloadPath = prefs.getString(key, Environment.DIRECTORY_MOVIES);
|
||||||
|
|
||||||
|
return downloadPath;
|
||||||
|
}
|
||||||
|
|
||||||
public static File getAudioDownloadFolder(Context context) {
|
public static File getAudioDownloadFolder(Context context) {
|
||||||
return getFolder(context, R.string.download_path_audio_key, Environment.DIRECTORY_MUSIC);
|
return getFolder(context, R.string.download_path_audio_key, Environment.DIRECTORY_MUSIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getAudioDownloadPath(Context context) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
final String key = context.getString(R.string.download_path_audio_key);
|
||||||
|
String downloadPath = prefs.getString(key, Environment.DIRECTORY_MUSIC);
|
||||||
|
|
||||||
|
return downloadPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getDownloadPath(Context context, String fileName)
|
||||||
|
{
|
||||||
|
if(Utility.isVideoFile(fileName)) {
|
||||||
|
return NewPipeSettings.getVideoDownloadPath(context);
|
||||||
|
}
|
||||||
|
return NewPipeSettings.getAudioDownloadPath(context);
|
||||||
|
}
|
||||||
|
|
||||||
private static File getFolder(Context context, int keyID, String defaultDirectoryName) {
|
private static File getFolder(Context context, int keyID, String defaultDirectoryName) {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
final String key = context.getString(keyID);
|
final String key = context.getString(keyID);
|
||||||
|
|
|
@ -579,8 +579,7 @@ public class VideoItemDetailFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
args.putString(DownloadDialog.TITLE, info.title);
|
args.putString(DownloadDialog.TITLE, info.title);
|
||||||
DownloadDialog downloadDialog = new DownloadDialog();
|
DownloadDialog downloadDialog = DownloadDialog.newInstance(args);
|
||||||
downloadDialog.setArguments(args);
|
|
||||||
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
|
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Toast.makeText(VideoItemDetailFragment.this.getActivity(),
|
Toast.makeText(VideoItemDetailFragment.this.getActivity(),
|
||||||
|
|
|
@ -1,20 +1,28 @@
|
||||||
package org.schabi.newpipe.download;
|
package org.schabi.newpipe.download;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.app.Dialog;
|
import android.content.ComponentName;
|
||||||
import android.app.DownloadManager;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.os.IBinder;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.ActivityCompat;
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.support.v4.app.DialogFragment;
|
import android.support.v4.app.DialogFragment;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.SeekBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.schabi.newpipe.App;
|
import org.schabi.newpipe.App;
|
||||||
import org.schabi.newpipe.NewPipeSettings;
|
import org.schabi.newpipe.NewPipeSettings;
|
||||||
|
@ -24,6 +32,10 @@ import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import us.shandian.giga.get.DownloadManager;
|
||||||
|
import us.shandian.giga.service.DownloadManagerService;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Christian Schabesberger on 21.09.15.
|
* Created by Christian Schabesberger on 21.09.15.
|
||||||
*
|
*
|
||||||
|
@ -52,83 +64,126 @@ public class DownloadDialog extends DialogFragment {
|
||||||
public static final String FILE_SUFFIX_VIDEO = "file_suffix_video";
|
public static final String FILE_SUFFIX_VIDEO = "file_suffix_video";
|
||||||
public static final String AUDIO_URL = "audio_url";
|
public static final String AUDIO_URL = "audio_url";
|
||||||
public static final String VIDEO_URL = "video_url";
|
public static final String VIDEO_URL = "video_url";
|
||||||
private Bundle arguments;
|
|
||||||
|
|
||||||
@NonNull
|
private DownloadManager mManager;
|
||||||
|
private DownloadManagerService.DMBinder mBinder;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
public DownloadDialog() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DownloadDialog newInstance(Bundle args)
|
||||||
|
{
|
||||||
|
DownloadDialog dialog = new DownloadDialog();
|
||||||
|
dialog.setArguments(args);
|
||||||
|
dialog.setStyle(DialogFragment.STYLE_NO_TITLE, 0);
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
arguments = getArguments();
|
Bundle savedInstanceState) {
|
||||||
super.onCreateDialog(savedInstanceState);
|
|
||||||
if(ContextCompat.checkSelfPermission(this.getContext(),Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED)
|
if(ContextCompat.checkSelfPermission(this.getContext(),Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED)
|
||||||
ActivityCompat.requestPermissions(getActivity(),new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},0);
|
ActivityCompat.requestPermissions(getActivity(),new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},0);
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
|
||||||
builder.setTitle(R.string.download_dialog_title);
|
|
||||||
|
|
||||||
// If no audio stream available
|
Intent i = new Intent();
|
||||||
|
i.setClass(getContext(), DownloadManagerService.class);
|
||||||
|
getContext().startService(i);
|
||||||
|
getContext().bindService(i, mConnection, Context.BIND_AUTO_CREATE);
|
||||||
|
|
||||||
|
|
||||||
|
return inflater.inflate(R.layout.dialog_url, container);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
Bundle arguments = getArguments();
|
||||||
|
final Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);
|
||||||
|
final EditText name = (EditText) view.findViewById(R.id.file_name);
|
||||||
|
final TextView tCount = (TextView) view.findViewById(R.id.threads_count);
|
||||||
|
final SeekBar threads = (SeekBar) view.findViewById(R.id.threads);
|
||||||
|
|
||||||
|
toolbar.setTitle(R.string.download_dialog_title);
|
||||||
|
toolbar.setNavigationIcon(R.drawable.ic_arrow_back_black_24dp);
|
||||||
|
toolbar.inflateMenu(R.menu.dialog_url);
|
||||||
|
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
getDialog().dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
checkDownloadOptions();
|
||||||
|
|
||||||
|
//int def = mPrefs.getInt("threads", 4);
|
||||||
|
int def = 3;
|
||||||
|
threads.setProgress(def - 1);
|
||||||
|
tCount.setText(String.valueOf(def));
|
||||||
|
|
||||||
|
name.setText(createFileName(arguments.getString(TITLE)));
|
||||||
|
|
||||||
|
|
||||||
|
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onMenuItemClick(MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.okay) {
|
||||||
|
download();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkDownloadOptions(){
|
||||||
|
View view = getView();
|
||||||
|
Bundle arguments = getArguments();
|
||||||
|
CheckBox audio = (CheckBox) view.findViewById(R.id.audio);
|
||||||
|
CheckBox video = (CheckBox) view.findViewById(R.id.video);
|
||||||
|
|
||||||
if(arguments.getString(AUDIO_URL) == null) {
|
if(arguments.getString(AUDIO_URL) == null) {
|
||||||
builder.setItems(R.array.download_options_no_audio, new DialogInterface.OnClickListener() {
|
audio.setVisibility(View.GONE);
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
Context context = getActivity();
|
|
||||||
String title = arguments.getString(TITLE);
|
|
||||||
switch (which) {
|
|
||||||
case 0: // Video
|
|
||||||
download(arguments.getString(VIDEO_URL),
|
|
||||||
title,
|
|
||||||
arguments.getString(FILE_SUFFIX_VIDEO),
|
|
||||||
NewPipeSettings.getVideoDownloadFolder(context),context);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Log.d(TAG, "lolz");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// If no video stream available
|
|
||||||
} else if(arguments.getString(VIDEO_URL) == null) {
|
} else if(arguments.getString(VIDEO_URL) == null) {
|
||||||
builder.setItems(R.array.download_options_no_video, new DialogInterface.OnClickListener() {
|
video.setVisibility(View.GONE);
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
Context context = getActivity();
|
|
||||||
String title = arguments.getString(TITLE);
|
|
||||||
switch (which) {
|
|
||||||
case 0: // Audio
|
|
||||||
download(arguments.getString(AUDIO_URL),
|
|
||||||
title,
|
|
||||||
arguments.getString(FILE_SUFFIX_AUDIO),
|
|
||||||
NewPipeSettings.getAudioDownloadFolder(context),context);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Log.d(TAG, "lolz");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//if both streams ar available
|
|
||||||
} else {
|
|
||||||
builder.setItems(R.array.download_options, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
Context context = getActivity();
|
|
||||||
String title = arguments.getString(TITLE);
|
|
||||||
switch (which) {
|
|
||||||
case 0: // Video
|
|
||||||
download(arguments.getString(VIDEO_URL),
|
|
||||||
title,
|
|
||||||
arguments.getString(FILE_SUFFIX_VIDEO),
|
|
||||||
NewPipeSettings.getVideoDownloadFolder(context), context);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
download(arguments.getString(AUDIO_URL),
|
|
||||||
title,
|
|
||||||
arguments.getString(FILE_SUFFIX_AUDIO),
|
|
||||||
NewPipeSettings.getAudioDownloadFolder(context), context);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Log.d(TAG, "lolz");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return builder.create();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -149,52 +204,60 @@ public class DownloadDialog extends DialogFragment {
|
||||||
return nameToTest;
|
return nameToTest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//download audio, video or both?
|
||||||
|
private void download()
|
||||||
|
{
|
||||||
|
View view = getView();
|
||||||
|
Bundle arguments = getArguments();
|
||||||
|
final EditText name = (EditText) view.findViewById(R.id.file_name);
|
||||||
|
final SeekBar threads = (SeekBar) view.findViewById(R.id.threads);
|
||||||
|
CheckBox audio = (CheckBox) view.findViewById(R.id.audio);
|
||||||
|
CheckBox video = (CheckBox) view.findViewById(R.id.video);
|
||||||
|
|
||||||
|
String fName = name.getText().toString().trim();
|
||||||
|
|
||||||
|
while (mBinder == null);
|
||||||
|
|
||||||
|
if(audio.isChecked()){
|
||||||
|
int res = mManager.startMission(
|
||||||
|
arguments.getString(AUDIO_URL),
|
||||||
|
fName + arguments.getString(FILE_SUFFIX_AUDIO),
|
||||||
|
threads.getProgress() + 1);
|
||||||
|
mBinder.onMissionAdded(mManager.getMission(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(video.isChecked()){
|
||||||
|
int res = mManager.startMission(
|
||||||
|
arguments.getString(VIDEO_URL),
|
||||||
|
fName + arguments.getString(FILE_SUFFIX_VIDEO),
|
||||||
|
threads.getProgress() + 1);
|
||||||
|
mBinder.onMissionAdded(mManager.getMission(res));
|
||||||
|
}
|
||||||
|
getDialog().dismiss();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void download(String url, String title,
|
private void download(String url, String title,
|
||||||
String fileSuffix, File downloadDir, Context context) {
|
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);
|
File saveFilePath = new File(downloadDir,createFileName(title) + fileSuffix);
|
||||||
|
|
||||||
long id = 0;
|
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 +
|
Log.i(TAG,"Started downloading '" + url +
|
||||||
"' => '" + saveFilePath + "' #" + id);
|
"' => '" + 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,282 @@
|
||||||
|
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.v4.app.NavUtils;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
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 android.support.v7.widget.SearchView;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.SettingsActivity;
|
||||||
|
import org.schabi.newpipe.VideoItemDetailActivity;
|
||||||
|
import org.schabi.newpipe.VideoItemListActivity;
|
||||||
|
import org.schabi.newpipe.extractor.ServiceList;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
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 static final String TAG = MainActivity.class.toString();
|
||||||
|
|
||||||
|
private Menu menu = null;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
try {
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
} catch(Exception e) {
|
||||||
|
Log.d(TAG, "Could not get SupportActionBar");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
name.setText(getIntent().getStringExtra("fileName"));
|
||||||
|
|
||||||
|
toolbar.setTitle(R.string.add);
|
||||||
|
toolbar.setNavigationIcon(R.drawable.ic_arrow_back_black_24dp);
|
||||||
|
toolbar.inflateMenu(R.menu.dialog_url);
|
||||||
|
|
||||||
|
// Show the dialog
|
||||||
|
final AlertDialog dialog = new AlertDialog.Builder(this)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setView(v)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
|
||||||
|
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 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 {
|
||||||
|
|
||||||
|
while (mBinder == null);
|
||||||
|
|
||||||
|
int res = mManager.startMission(getIntent().getData().toString(), 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
super.onCreateOptionsMenu(menu);
|
||||||
|
this.menu = menu;
|
||||||
|
MenuInflater inflater = getMenuInflater();
|
||||||
|
|
||||||
|
inflater.inflate(R.menu.download_menu, menu);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
int id = item.getItemId();
|
||||||
|
|
||||||
|
switch (id) {
|
||||||
|
case android.R.id.home: {
|
||||||
|
Intent intent = new Intent(this, VideoItemListActivity.class);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
NavUtils.navigateUpTo(this, intent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case R.id.action_settings: {
|
||||||
|
Intent intent = new Intent(this, SettingsActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case R.id.action_report_error: {
|
||||||
|
ErrorActivity.reportError(MainActivity.this, new Vector<Exception>(),
|
||||||
|
null, null,
|
||||||
|
ErrorActivity.ErrorInfo.make(ErrorActivity.USER_REPORT,
|
||||||
|
null,
|
||||||
|
"user_report", R.string.user_report));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return mFragment.onOptionsItemSelected(item) ||
|
||||||
|
super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -0,0 +1,216 @@
|
||||||
|
package us.shandian.giga.get;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.NewPipeSettings;
|
||||||
|
|
||||||
|
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 = NewPipeSettings.getDownloadPath(mContext, name);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.NewPipeSettings;
|
||||||
|
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 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 = NewPipeSettings.getVideoDownloadPath(this);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,288 @@
|
||||||
|
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.NewPipeSettings;
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import us.shandian.giga.get.DownloadMission;
|
||||||
|
|
||||||
|
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 FileType getFileType(String file) {
|
||||||
|
if (file.endsWith(".apk")) {
|
||||||
|
return FileType.APP;
|
||||||
|
} else if (file.endsWith(".mp3") || file.endsWith(".wav") || file.endsWith(".flac") || file.endsWith(".m4a")) {
|
||||||
|
return FileType.MUSIC;
|
||||||
|
} else if (file.endsWith(".mp4") || file.endsWith(".mpeg") || file.endsWith(".rm") || file.endsWith(".rmvb")
|
||||||
|
|| file.endsWith(".flv") || file.endsWith(".webp") || file.endsWith(".webm")) {
|
||||||
|
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 Boolean isMusicFile(String file)
|
||||||
|
{
|
||||||
|
return Utility.getFileType(file) == FileType.MUSIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Boolean isVideoFile(String file)
|
||||||
|
{
|
||||||
|
return Utility.getFileType(file) == FileType.VIDEO;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 boolean isDirectoryAvailble(String path) {
|
||||||
|
File dir = new File(path);
|
||||||
|
return dir.exists() && dir.isDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDownloadDirectoryAvailble(Context context) {
|
||||||
|
return isDirectoryAvailble(NewPipeSettings.getVideoDownloadPath(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 319 B |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 446 B |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 18 KiB |
|
@ -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>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<LinearLayout 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"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,109 @@
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<android.support.v7.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:theme="@style/NewPipeActionbarTheme" />
|
||||||
|
|
||||||
|
<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">
|
||||||
|
|
||||||
|
</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"/>
|
||||||
|
|
||||||
|
</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="left"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
<CheckBox android:id="@+id/video"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/video"
|
||||||
|
android:checked="true"/>
|
||||||
|
<CheckBox android:id="@+id/audio"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/audio" />
|
||||||
|
</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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
|
||||||
|
<item android:id="@+id/action_settings"
|
||||||
|
app:showAsAction="never"
|
||||||
|
android:title="@string/settings"/>
|
||||||
|
|
||||||
|
<item android:id="@+id/action_report_error"
|
||||||
|
app:showAsAction="never"
|
||||||
|
android:title="@string/report_error" />
|
||||||
|
</menu>
|
|
@ -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>
|
|
@ -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>
|
|
@ -34,4 +34,5 @@
|
||||||
<item name="android:background">@color/video_overlay_color</item>
|
<item name="android:background">@color/video_overlay_color</item>
|
||||||
<item name="background">@color/video_overlay_color</item>
|
<item name="background">@color/video_overlay_color</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
|
@ -17,4 +17,26 @@
|
||||||
<color name="duration_text_color">#EEFFFFFF</color>
|
<color name="duration_text_color">#EEFFFFFF</color>
|
||||||
<color name="video_overlay_color">#66000000</color>
|
<color name="video_overlay_color">#66000000</color>
|
||||||
<color name="background_notification_color">#323232</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>
|
</resources>
|
|
@ -140,4 +140,39 @@
|
||||||
<string name="storage_permission_denied">Permission to access storage was denied</string>
|
<string name="storage_permission_denied">Permission to access storage was denied</string>
|
||||||
<string name="use_exoplayer_title">Use ExoPlayer</string>
|
<string name="use_exoplayer_title">Use ExoPlayer</string>
|
||||||
<string name="use_exoplayer_summary">Experimental</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>
|
</resources>
|
||||||
|
|
|
@ -48,4 +48,5 @@
|
||||||
<item name="background">@color/video_overlay_color</item>
|
<item name="background">@color/video_overlay_color</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|