Merge remote-tracking branch 'origin/master'
@ -8,8 +8,8 @@ android {
|
||||
applicationId "org.schabi.newpipe"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 25
|
||||
versionCode 21
|
||||
versionName "0.8.7"
|
||||
versionCode 22
|
||||
versionName "0.8.8"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
@ -46,5 +46,6 @@ dependencies {
|
||||
compile 'com.google.code.gson:gson:2.4'
|
||||
compile 'com.nononsenseapps:filepicker:3.0.0'
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||
compile 'ch.acra:acra:4.9.0'
|
||||
}
|
||||
|
@ -161,6 +161,16 @@
|
||||
<activity
|
||||
android:name=".ReCaptchaActivity"
|
||||
android:label="@string/reCaptchaActivity" />
|
||||
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths"/>
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -48,6 +48,7 @@ public class App extends Application {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// init crashreport
|
||||
try {
|
||||
final ACRAConfiguration acraConfig = new ConfigurationBuilder(this)
|
||||
|
@ -4,6 +4,7 @@ import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.design.widget.CollapsingToolbarLayout;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
@ -31,6 +32,7 @@ import org.schabi.newpipe.info_list.InfoListAdapter;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
@ -71,6 +73,10 @@ public class ChannelActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
if (Objects.equals(PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getString("theme", getResources().getString(R.string.light_theme_title)), getResources().getString(R.string.dark_theme_title))) {
|
||||
setTheme(R.style.DarkTheme_NoActionBar);
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_channel);
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
@ -128,7 +134,7 @@ public class ChannelActivity extends AppCompatActivity {
|
||||
CollapsingToolbarLayout ctl = (CollapsingToolbarLayout) findViewById(R.id.channel_toolbar_layout);
|
||||
ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
|
||||
ImageView channelBanner = (ImageView) findViewById(R.id.channel_banner_image);
|
||||
FloatingActionButton feedButton = (FloatingActionButton) findViewById(R.id.channel_rss_fab);
|
||||
final FloatingActionButton feedButton = (FloatingActionButton) findViewById(R.id.channel_rss_fab);
|
||||
ImageView avatarView = (ImageView) findViewById(R.id.channel_avatar_view);
|
||||
ImageView haloView = (ImageView) findViewById(R.id.channel_avatar_halo);
|
||||
|
||||
|
@ -4,13 +4,13 @@ import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 02.08.16.
|
||||
@ -32,7 +32,7 @@ import org.schabi.newpipe.settings.SettingsActivity;
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
public class MainActivity extends ThemableActivity {
|
||||
|
||||
private Fragment mainFragment = null;
|
||||
|
||||
@ -40,7 +40,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
mainFragment = getSupportFragmentManager()
|
||||
.findFragmentById(R.id.search_fragment);
|
||||
@ -52,8 +51,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
|
||||
inflater.inflate(R.menu.main_menu, menu);
|
||||
|
||||
mainFragment.onCreateOptionsMenu(menu, inflater);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -74,13 +71,15 @@ public class MainActivity extends AppCompatActivity {
|
||||
return true;
|
||||
}
|
||||
case R.id.action_show_downloads: {
|
||||
if(!PermissionHelper.checkStoragePermissions(this)) {
|
||||
return false;
|
||||
}
|
||||
Intent intent = new Intent(this, org.schabi.newpipe.download.DownloadActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return mainFragment.onOptionsItemSelected(item) ||
|
||||
super.onOptionsItemSelected(item);
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
21
app/src/main/java/org/schabi/newpipe/ThemableActivity.java
Normal file
@ -0,0 +1,21 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.schabi.newpipe.R.attr.theme;
|
||||
|
||||
public class ThemableActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (Objects.equals(PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getString("theme", getResources().getString(R.string.light_theme_title)), getResources().getString(R.string.dark_theme_title))) {
|
||||
setTheme(R.style.DarkTheme);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@ import android.media.AudioManager;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@ -14,6 +13,7 @@ import android.widget.Toast;
|
||||
import org.schabi.newpipe.App;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.ThemableActivity;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
|
||||
@ -39,7 +39,7 @@ import java.util.HashSet;
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class VideoItemDetailActivity extends AppCompatActivity {
|
||||
public class VideoItemDetailActivity extends ThemableActivity {
|
||||
|
||||
/**
|
||||
* Removes invisible separators (\p{Z}) and punctuation characters including
|
||||
@ -164,19 +164,10 @@ public class VideoItemDetailActivity extends AppCompatActivity {
|
||||
NavUtils.navigateUpTo(this, intent);
|
||||
return true;
|
||||
} else {
|
||||
return fragment.onOptionsItemSelected(item) ||
|
||||
super.onOptionsItemSelected(item);
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
fragment.onCreateOptionsMenu(menu, getMenuInflater());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves all Strings which look remotely like URLs from a text.
|
||||
* Used if NewPipe was called through share menu.
|
||||
|
@ -21,6 +21,7 @@ import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -30,7 +31,6 @@ import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
@ -39,26 +39,26 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
import org.schabi.newpipe.ActivityCommunicator;
|
||||
import org.schabi.newpipe.ChannelActivity;
|
||||
import org.schabi.newpipe.ReCaptchaActivity;
|
||||
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfo;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.ImageErrorLoadingListener;
|
||||
import org.schabi.newpipe.Localization;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.ReCaptchaActivity;
|
||||
import org.schabi.newpipe.download.DownloadDialog;
|
||||
import org.schabi.newpipe.extractor.stream_info.AudioStream;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.stream_info.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfo;
|
||||
import org.schabi.newpipe.extractor.stream_info.VideoStream;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.player.BackgroundPlayer;
|
||||
import org.schabi.newpipe.player.PlayVideoActivity;
|
||||
import org.schabi.newpipe.player.ExoPlayerActivity;
|
||||
import org.schabi.newpipe.player.PlayVideoActivity;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import java.util.Vector;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
import static org.schabi.newpipe.ReCaptchaActivity.RECAPTCHA_REQUEST;
|
||||
@ -393,6 +393,10 @@ public class VideoItemDetailFragment extends Fragment {
|
||||
actionBarHandler.setOnDownloadListener(new ActionBarHandler.OnActionListener() {
|
||||
@Override
|
||||
public void onActionSelected(int selectedStreamId) {
|
||||
if(!PermissionHelper.checkStoragePermissions(getActivity())) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Bundle args = new Bundle();
|
||||
|
||||
@ -629,6 +633,7 @@ public class VideoItemDetailFragment extends Fragment {
|
||||
onNotSpecifiedContentError();
|
||||
}
|
||||
});
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -650,7 +655,6 @@ public class VideoItemDetailFragment extends Fragment {
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
Activity a = getActivity();
|
||||
|
||||
infoItemBuilder = new InfoItemBuilder(a, a.findViewById(android.R.id.content));
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT < 18) {
|
||||
|
@ -13,8 +13,8 @@ import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v7.app.ActionBar;
|
||||
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;
|
||||
@ -28,8 +28,10 @@ import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.schabi.newpipe.ThemableActivity;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
|
||||
import java.io.File;
|
||||
@ -42,7 +44,7 @@ import us.shandian.giga.ui.fragment.MissionsFragment;
|
||||
import us.shandian.giga.util.CrashHandler;
|
||||
import us.shandian.giga.util.Utility;
|
||||
|
||||
public class DownloadActivity extends AppCompatActivity implements AdapterView.OnItemClickListener{
|
||||
public class DownloadActivity extends ThemableActivity implements AdapterView.OnItemClickListener{
|
||||
|
||||
public static final String INTENT_DOWNLOAD = "us.shandian.giga.intent.DOWNLOAD";
|
||||
|
||||
@ -53,26 +55,11 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O
|
||||
|
||||
|
||||
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) {
|
||||
@ -83,7 +70,6 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O
|
||||
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);
|
||||
@ -91,7 +77,7 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O
|
||||
|
||||
//noinspection ConstantConditions
|
||||
|
||||
// its ok if this failes, we will catch that error later, and send it as report
|
||||
// its ok if this fails, we will catch that error later, and send it as report
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setTitle(R.string.downloads_title);
|
||||
@ -202,22 +188,24 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
if (item.getItemId() == R.id.okay) {
|
||||
|
||||
String location;
|
||||
if(audioButton.isChecked()) {
|
||||
location = NewPipeSettings.getAudioDownloadPath(DownloadActivity.this);
|
||||
} else {
|
||||
location = NewPipeSettings.getVideoDownloadPath(DownloadActivity.this);
|
||||
}
|
||||
|
||||
String fName = name.getText().toString().trim();
|
||||
|
||||
File f = new File(mManager.getLocation() + "/" + fName);
|
||||
|
||||
File f = new File(location, fName);
|
||||
if (f.exists()) {
|
||||
Toast.makeText(DownloadActivity.this, R.string.msg_exists, Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
|
||||
while (mBinder == null);
|
||||
|
||||
int res = mManager.startMission(
|
||||
getIntent().getData().toString(),
|
||||
fName,
|
||||
audioButton.isChecked(),
|
||||
threads.getProgress() + 1);
|
||||
mBinder.onMissionAdded(mManager.getMission(res));
|
||||
DownloadManagerService.startMission(
|
||||
DownloadActivity.this,
|
||||
getIntent().getData().toString(), location, fName,
|
||||
audioButton.isChecked(), threads.getProgress() + 1);
|
||||
mFragment.notifyChange();
|
||||
|
||||
mPrefs.edit().putInt(THREADS, threads.getProgress() + 1).commit();
|
||||
@ -273,8 +261,8 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return mFragment.onOptionsItemSelected(item) ||
|
||||
super.onOptionsItemSelected(item);
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.App;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
@ -65,24 +67,6 @@ public class DownloadDialog extends DialogFragment {
|
||||
public static final String AUDIO_URL = "audio_url";
|
||||
public static final String VIDEO_URL = "video_url";
|
||||
|
||||
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() {
|
||||
|
||||
}
|
||||
@ -102,12 +86,6 @@ public class DownloadDialog extends DialogFragment {
|
||||
if(ContextCompat.checkSelfPermission(this.getContext(),Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED)
|
||||
ActivityCompat.requestPermissions(getActivity(),new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},0);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -219,36 +197,22 @@ public class DownloadDialog extends DialogFragment {
|
||||
|
||||
String fName = name.getText().toString().trim();
|
||||
|
||||
// todo: add timeout? would be bad if the thread gets locked dueto this.
|
||||
while (mBinder == null);
|
||||
|
||||
if(audioButton.isChecked()){
|
||||
int res = mManager.startMission(
|
||||
arguments.getString(AUDIO_URL),
|
||||
fName + arguments.getString(FILE_SUFFIX_AUDIO),
|
||||
audioButton.isChecked(),
|
||||
threads.getProgress() + 1);
|
||||
DownloadMission mission = mManager.getMission(res);
|
||||
mBinder.onMissionAdded(mission);
|
||||
// add download listener to allow media scan notification
|
||||
DownloadListener listener = new DownloadListener(getContext(), mission);
|
||||
mission.addListener(listener);
|
||||
boolean isAudio = audioButton.isChecked();
|
||||
String url, location, filename;
|
||||
if(isAudio) {
|
||||
url = arguments.getString(AUDIO_URL);
|
||||
location = NewPipeSettings.getAudioDownloadPath(getContext());
|
||||
filename = fName + arguments.getString(FILE_SUFFIX_AUDIO);
|
||||
} else {
|
||||
url = arguments.getString(VIDEO_URL);
|
||||
location = NewPipeSettings.getVideoDownloadPath(getContext());
|
||||
filename = fName + arguments.getString(FILE_SUFFIX_VIDEO);
|
||||
}
|
||||
|
||||
if(videoButton.isChecked()){
|
||||
int res = mManager.startMission(
|
||||
arguments.getString(VIDEO_URL),
|
||||
fName + arguments.getString(FILE_SUFFIX_VIDEO),
|
||||
audioButton.isChecked(),
|
||||
threads.getProgress() + 1);
|
||||
DownloadMission mission = mManager.getMission(res);
|
||||
mBinder.onMissionAdded(mission);
|
||||
// add download listener to allow media scan notification
|
||||
DownloadListener listener = new DownloadListener(getContext(), mission);
|
||||
mission.addListener(listener);
|
||||
}
|
||||
DownloadManagerService.startMission(getContext(), url, location, filename, isAudio,
|
||||
threads.getProgress() + 1);
|
||||
|
||||
getDialog().dismiss();
|
||||
|
||||
}
|
||||
|
||||
private void download(String url, String title,
|
||||
|
@ -1,62 +0,0 @@
|
||||
package org.schabi.newpipe.download;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import us.shandian.giga.get.DownloadMission;
|
||||
import us.shandian.giga.get.DownloadMission.MissionListener;
|
||||
|
||||
/**
|
||||
* Created by erwin on 06.11.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* DownloadListener.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class DownloadListener implements MissionListener
|
||||
{
|
||||
DownloadMission mMission;
|
||||
Context mContext;
|
||||
|
||||
public DownloadListener(Context context, DownloadMission mission)
|
||||
{
|
||||
super();
|
||||
mMission = mission;
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressUpdate(long done, long total)
|
||||
{
|
||||
// do nothing special ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish()
|
||||
{
|
||||
// notify media scanner on downloaded media file ...
|
||||
mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
|
||||
Uri.parse( "file://" + mMission.location
|
||||
+ "/" + mMission.name)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errCode)
|
||||
{
|
||||
// do nothing special ...
|
||||
}
|
||||
}
|
@ -42,7 +42,7 @@ import info.guardianproject.netcipher.NetCipher;
|
||||
*/
|
||||
|
||||
|
||||
// TODO: FOR HEVEN SAKE !!! DO NOT SIMPLY USE ASYNCTASK. MAKE THIS A PROPER SERVICE !!!
|
||||
// TODO: FOR HEAVEN SAKE !!! DO NOT SIMPLY USE ASYNCTASK. MAKE THIS A PROPER SERVICE !!!
|
||||
public class FileDownloader extends AsyncTask<Void, Integer, Void> {
|
||||
public static final String TAG = "FileDownloader";
|
||||
|
||||
|
@ -4,7 +4,6 @@ import org.schabi.newpipe.extractor.UrlIdHandler;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 10.08.15.
|
||||
@ -26,7 +25,6 @@ import java.util.List;
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
@SuppressWarnings("ALL")
|
||||
public abstract class SearchEngine {
|
||||
public static class NothingFoundException extends ExtractionException {
|
||||
public NothingFoundException(String message) {
|
||||
|
@ -85,18 +85,18 @@ public class YoutubeSearchEngine extends SearchEngine {
|
||||
Element el;
|
||||
|
||||
// both types of spell correction item
|
||||
if (!((el = item.select("div[class*=\"spell-correction\"]").first()) == null)) {
|
||||
if ((el = item.select("div[class*=\"spell-correction\"]").first()) != null) {
|
||||
collector.setSuggestion(el.select("a").first().text());
|
||||
if(list.children().size() == 1) {
|
||||
throw new NothingFoundException("Did you mean: " + el.select("a").first().text());
|
||||
}
|
||||
// search message item
|
||||
} else if (!((el = item.select("div[class*=\"search-message\"]").first()) == null)) {
|
||||
} else if ((el = item.select("div[class*=\"search-message\"]").first()) != null) {
|
||||
//result.errorMessage = el.text();
|
||||
throw new NothingFoundException(el.text());
|
||||
|
||||
// video item type
|
||||
} else if (!((el = item.select("div[class*=\"yt-lockup-video\"").first()) == null)) {
|
||||
} else if ((el = item.select("div[class*=\"yt-lockup-video\"").first()) != null) {
|
||||
collector.commit(extractPreviewInfo(el));
|
||||
} else {
|
||||
//noinspection ConstantConditions
|
||||
|
@ -46,7 +46,7 @@ public class YoutubeService extends StreamingService {
|
||||
@Override
|
||||
public StreamExtractor getExtractorInstance(String url)
|
||||
throws ExtractionException, IOException {
|
||||
UrlIdHandler urlIdHandler = new YoutubeStreamUrlIdHandler();
|
||||
UrlIdHandler urlIdHandler = YoutubeStreamUrlIdHandler.getInstance();
|
||||
if(urlIdHandler.acceptUrl(url)) {
|
||||
return new YoutubeStreamExtractor(urlIdHandler, url, getServiceId());
|
||||
}
|
||||
@ -61,7 +61,7 @@ public class YoutubeService extends StreamingService {
|
||||
|
||||
@Override
|
||||
public UrlIdHandler getUrlIdHandlerInstance() {
|
||||
return new YoutubeStreamUrlIdHandler();
|
||||
return YoutubeStreamUrlIdHandler.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -88,7 +88,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
||||
// $$el_type$$ will be replaced by the actual el_type (se the declarations below)
|
||||
private static final String GET_VIDEO_INFO_URL =
|
||||
"https://www.youtube.com/get_video_info?video_id=%%video_id%%$$el_type$$&ps=default&eurl=&gl=US&hl=en";
|
||||
// eltype is nececeary for the url aboth
|
||||
// eltype is necessary for the url above
|
||||
private static final String EL_INFO = "el=info";
|
||||
|
||||
public enum ItagType {
|
||||
@ -184,7 +184,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
||||
// cached values
|
||||
private static volatile String decryptionCode = "";
|
||||
|
||||
UrlIdHandler urlidhandler = new YoutubeStreamUrlIdHandler();
|
||||
UrlIdHandler urlidhandler = YoutubeStreamUrlIdHandler.getInstance();
|
||||
String pageUrl = "";
|
||||
|
||||
public YoutubeStreamExtractor(UrlIdHandler urlIdHandler, String pageUrl, int serviceId)
|
||||
|
@ -1,12 +1,21 @@
|
||||
package org.schabi.newpipe.extractor.services.youtube;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.schabi.newpipe.extractor.Downloader;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.Parser;
|
||||
import org.schabi.newpipe.extractor.UrlIdHandler;
|
||||
import org.schabi.newpipe.extractor.exceptions.FoundAdException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 02.02.16.
|
||||
@ -29,45 +38,55 @@ import java.net.URLDecoder;
|
||||
*/
|
||||
|
||||
public class YoutubeStreamUrlIdHandler implements UrlIdHandler {
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
|
||||
private static final YoutubeStreamUrlIdHandler instance = new YoutubeStreamUrlIdHandler();
|
||||
private static final String ID_PATTERN = "([\\-a-zA-Z0-9_]{11})";
|
||||
|
||||
private YoutubeStreamUrlIdHandler() {}
|
||||
|
||||
public static YoutubeStreamUrlIdHandler getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl(String videoId) {
|
||||
return "https://www.youtube.com/watch?v=" + videoId;
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@Override
|
||||
public String getId(String url) throws ParsingException, IllegalArgumentException {
|
||||
if(url.isEmpty())
|
||||
{
|
||||
if(url.isEmpty()) {
|
||||
throw new IllegalArgumentException("The url parameter should not be empty");
|
||||
}
|
||||
String id;
|
||||
|
||||
if(url.contains("youtube")) {
|
||||
if(url.contains("attribution_link")) {
|
||||
String id;
|
||||
String lowercaseUrl = url.toLowerCase();
|
||||
if(lowercaseUrl.contains("youtube")) {
|
||||
if (url.contains("attribution_link")) {
|
||||
try {
|
||||
String escapedQuery = Parser.matchGroup1("u=(.[^&|$]*)", url);
|
||||
String query = URLDecoder.decode(escapedQuery, "UTF-8");
|
||||
id = Parser.matchGroup1("v=([\\-a-zA-Z0-9_]{11})", query);
|
||||
} catch(UnsupportedEncodingException uee) {
|
||||
id = Parser.matchGroup1("v=" + ID_PATTERN, query);
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new ParsingException("Could not parse attribution_link", uee);
|
||||
}
|
||||
}
|
||||
else if(url.contains("vnd.youtube"))
|
||||
{
|
||||
id = Parser.matchGroup1("([\\-a-zA-Z0-9_]{11}).*", url);
|
||||
} else if(lowercaseUrl.contains("youtube.com/shared?ci=")) {
|
||||
return getRealIdFromSharedLink(url);
|
||||
} else if (url.contains("vnd.youtube")) {
|
||||
id = Parser.matchGroup1(ID_PATTERN, url);
|
||||
} else if (url.contains("embed")) {
|
||||
id = Parser.matchGroup1("embed/" + ID_PATTERN, url);
|
||||
} else if(url.contains("googleads")) {
|
||||
throw new FoundAdException("Error found add: " + url);
|
||||
} else {
|
||||
id = Parser.matchGroup1("[?&]v=([\\-a-zA-Z0-9_]{11})", url);
|
||||
id = Parser.matchGroup1("[?&]v=" + ID_PATTERN, url);
|
||||
}
|
||||
}
|
||||
else if(url.contains("youtu.be")) {
|
||||
else if(lowercaseUrl.contains("youtu.be")) {
|
||||
if(url.contains("v=")) {
|
||||
id = Parser.matchGroup1("v=([\\-a-zA-Z0-9_]{11})", url);
|
||||
id = Parser.matchGroup1("v=" + ID_PATTERN, url);
|
||||
} else {
|
||||
id = Parser.matchGroup1("youtu\\.be/([a-zA-Z0-9_-]{11})", url);
|
||||
id = Parser.matchGroup1("[Yy][Oo][Uu][Tt][Uu]\\.[Bb][Ee]/" + ID_PATTERN, url);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -82,12 +101,55 @@ public class YoutubeStreamUrlIdHandler implements UrlIdHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the real url from a shared uri.
|
||||
*
|
||||
* Shared URI's look like this:
|
||||
* <pre>
|
||||
* * https://www.youtube.com/shared?ci=PJICrTByb3E
|
||||
* * vnd.youtube://www.youtube.com/shared?ci=PJICrTByb3E&feature=twitter-deep-link
|
||||
* </pre>
|
||||
* @param url The shared url
|
||||
* @return the id of the stream
|
||||
* @throws ParsingException
|
||||
*/
|
||||
private @NonNull String getRealIdFromSharedLink(String url) throws ParsingException {
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(url);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new ParsingException("Invalid shared link", e);
|
||||
}
|
||||
String sharedId = getSharedId(uri);
|
||||
Downloader downloader = NewPipe.getDownloader();
|
||||
String content;
|
||||
try {
|
||||
content = downloader.download("https://www.youtube.com/shared?ci=" + sharedId);
|
||||
} catch (IOException | ReCaptchaException e) {
|
||||
throw new ParsingException("Unable to resolve shared link", e);
|
||||
}
|
||||
// is this bad? is this fragile?:
|
||||
String realId = Parser.matchGroup1("rel=\"shortlink\" href=\"https://youtu.be/" + ID_PATTERN, content);
|
||||
if(sharedId.equals(realId)) {
|
||||
throw new ParsingException("Got same id for as shared id: " + sharedId);
|
||||
}
|
||||
return realId;
|
||||
}
|
||||
|
||||
private @NonNull String getSharedId(URI uri) throws ParsingException {
|
||||
if (!"/shared".equals(uri.getPath())) {
|
||||
throw new ParsingException("Not a shared link: " + uri.toString() + " (path != " + uri.getPath() + ")");
|
||||
}
|
||||
return Parser.matchGroup1("ci=" + ID_PATTERN, uri.getQuery());
|
||||
}
|
||||
|
||||
public String cleanUrl(String complexUrl) throws ParsingException {
|
||||
return getUrl(getId(complexUrl));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptUrl(String videoUrl) {
|
||||
videoUrl = videoUrl.toLowerCase();
|
||||
return videoUrl.contains("youtube") ||
|
||||
videoUrl.contains("youtu.be");
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.schabi.newpipe.extractor.stream_info;
|
||||
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.UrlIdHandler;
|
||||
import org.schabi.newpipe.extractor.exceptions.FoundAdException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
@ -60,7 +61,7 @@ public class StreamPreviewInfoCollector {
|
||||
if (urlIdHandler == null) {
|
||||
throw new ParsingException("Error: UrlIdHandler not set");
|
||||
} else if(!resultItem.webpage_url.isEmpty()) {
|
||||
resultItem.id = (new YoutubeStreamUrlIdHandler()).getId(resultItem.webpage_url);
|
||||
resultItem.id = NewPipe.getService(serviceId).getUrlIdHandlerInstance().getId(resultItem.webpage_url);
|
||||
}
|
||||
resultItem.title = extractor.getTitle();
|
||||
resultItem.stream_type = extractor.getStreamType();
|
||||
|
@ -30,13 +30,13 @@ import org.schabi.newpipe.R;
|
||||
|
||||
public class InfoItemHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public ImageView itemThumbnailView;
|
||||
public TextView itemVideoTitleView,
|
||||
public final ImageView itemThumbnailView;
|
||||
public final TextView itemVideoTitleView,
|
||||
itemUploaderView,
|
||||
itemDurationView,
|
||||
itemUploadDateView,
|
||||
itemViewCountView;
|
||||
public Button itemButton;
|
||||
public final Button itemButton;
|
||||
|
||||
public InfoItemHolder(View v) {
|
||||
super(v);
|
||||
@ -48,4 +48,5 @@ public class InfoItemHolder extends RecyclerView.ViewHolder {
|
||||
itemViewCountView = (TextView) v.findViewById(R.id.itemViewCountView);
|
||||
itemButton = (Button) v.findViewById(R.id.item_button);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -34,11 +34,12 @@ import java.util.Vector;
|
||||
|
||||
public class InfoListAdapter extends RecyclerView.Adapter<InfoItemHolder> {
|
||||
|
||||
InfoItemBuilder infoItemBuilder = null;
|
||||
List<StreamPreviewInfo> streamList = new Vector<>();
|
||||
private final InfoItemBuilder infoItemBuilder;
|
||||
private final List<StreamPreviewInfo> streamList;
|
||||
|
||||
public InfoListAdapter(Activity a, View rootView) {
|
||||
infoItemBuilder = new InfoItemBuilder(a, rootView);
|
||||
streamList = new Vector<>();
|
||||
}
|
||||
|
||||
public void setOnItemSelectedListener
|
||||
@ -54,7 +55,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<InfoItemHolder> {
|
||||
}
|
||||
|
||||
public void clearSteamItemList() {
|
||||
streamList = new Vector<>();
|
||||
streamList.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,6 @@ import android.preference.PreferenceManager;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
@ -35,6 +34,7 @@ import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.Downloader;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.ThemableActivity;
|
||||
import org.schabi.newpipe.extractor.Parser;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
@ -65,7 +65,7 @@ import java.util.Vector;
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class ErrorActivity extends AppCompatActivity {
|
||||
public class ErrorActivity extends ThemableActivity {
|
||||
public static class ErrorInfo implements Parcelable {
|
||||
public int userAction;
|
||||
public String request;
|
||||
|
@ -56,13 +56,15 @@ public class SearchInfoItemFragment extends Fragment {
|
||||
|
||||
private static final String TAG = SearchInfoItemFragment.class.toString();
|
||||
|
||||
/**
|
||||
* Listener for search queries
|
||||
*/
|
||||
public class SearchQueryListener implements SearchView.OnQueryTextListener {
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
Activity a = getActivity();
|
||||
try {
|
||||
searchQuery = query;
|
||||
search(query);
|
||||
|
||||
// hide virtual keyboard
|
||||
@ -89,8 +91,6 @@ public class SearchInfoItemFragment extends Fragment {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
View bg = a.findViewById(R.id.mainBG);
|
||||
bg.setVisibility(View.GONE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -108,12 +108,10 @@ public class SearchInfoItemFragment extends Fragment {
|
||||
private boolean isLoading = false;
|
||||
|
||||
private ProgressBar loadingIndicator = null;
|
||||
private SearchView searchView = null;
|
||||
private int pageNumber = 0;
|
||||
private SuggestionListAdapter suggestionListAdapter = null;
|
||||
private InfoListAdapter infoListAdapter = null;
|
||||
private LinearLayoutManager streamInfoListLayoutManager = null;
|
||||
private RecyclerView recyclerView = null;
|
||||
|
||||
// savedInstanceBundle arguments
|
||||
private static final String QUERY = "query";
|
||||
@ -126,23 +124,32 @@ public class SearchInfoItemFragment extends Fragment {
|
||||
public SearchInfoItemFragment() {
|
||||
}
|
||||
|
||||
// TODO: Customize parameter initialization
|
||||
@SuppressWarnings("unused")
|
||||
public static SearchInfoItemFragment newInstance(int columnCount) {
|
||||
public static SearchInfoItemFragment newInstance(int streamingServiceId, String searchQuery) {
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(STREAMING_SERVICE, streamingServiceId);
|
||||
args.putString(QUERY, searchQuery);
|
||||
SearchInfoItemFragment fragment = new SearchInfoItemFragment();
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
searchQuery = "";
|
||||
if (savedInstanceState != null) {
|
||||
searchQuery = savedInstanceState.getString(QUERY);
|
||||
streamingServiceId = savedInstanceState.getInt(STREAMING_SERVICE);
|
||||
} else {
|
||||
try {
|
||||
streamingServiceId = NewPipe.getIdOfService("Youtube");
|
||||
Bundle args = getArguments();
|
||||
if(args != null) {
|
||||
searchQuery = args.getString(QUERY);
|
||||
streamingServiceId = args.getInt(STREAMING_SERVICE);
|
||||
} else {
|
||||
streamingServiceId = NewPipe.getIdOfService("Youtube");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
ErrorActivity.reportError(getActivity(), e, null,
|
||||
@ -153,13 +160,14 @@ public class SearchInfoItemFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
SearchWorker sw = SearchWorker.getInstance();
|
||||
sw.setSearchWorkerResultListener(new SearchWorker.SearchWorkerResultListener() {
|
||||
@Override
|
||||
public void onResult(SearchResult result) {
|
||||
infoListAdapter.addStreamItemList(result.resultList);
|
||||
isLoading = false;
|
||||
loadingIndicator.setVisibility(View.GONE);
|
||||
setDoneLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -167,8 +175,7 @@ public class SearchInfoItemFragment extends Fragment {
|
||||
//setListShown(true);
|
||||
Toast.makeText(getActivity(), getString(stringResource),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
isLoading = false;
|
||||
loadingIndicator.setVisibility(View.GONE);
|
||||
setDoneLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -176,8 +183,7 @@ public class SearchInfoItemFragment extends Fragment {
|
||||
//setListShown(true);
|
||||
Toast.makeText(getActivity(), message,
|
||||
Toast.LENGTH_LONG).show();
|
||||
isLoading = false;
|
||||
loadingIndicator.setVisibility(View.GONE);
|
||||
setDoneLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -191,6 +197,7 @@ public class SearchInfoItemFragment extends Fragment {
|
||||
RECAPTCHA_REQUEST);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -200,7 +207,7 @@ public class SearchInfoItemFragment extends Fragment {
|
||||
|
||||
Context context = view.getContext();
|
||||
loadingIndicator = (ProgressBar) view.findViewById(R.id.progressBar);
|
||||
recyclerView = (RecyclerView) view.findViewById(R.id.list);
|
||||
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list);
|
||||
streamInfoListLayoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(streamInfoListLayoutManager);
|
||||
|
||||
@ -209,15 +216,12 @@ public class SearchInfoItemFragment extends Fragment {
|
||||
infoListAdapter.setOnItemSelectedListener(new InfoItemBuilder.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void selected(String url) {
|
||||
Intent i = new Intent(getActivity(), VideoItemDetailActivity.class);
|
||||
i.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
|
||||
i.putExtra(VideoItemDetailFragment.VIDEO_URL, url);
|
||||
getActivity().startActivity(i);
|
||||
startDetailActivity(url);
|
||||
}
|
||||
});
|
||||
recyclerView.setAdapter(infoListAdapter);
|
||||
|
||||
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
recyclerView.clearOnScrollListeners();
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||
int pastVisiblesItems, visibleItemCount, totalItemCount;
|
||||
@ -239,14 +243,26 @@ public class SearchInfoItemFragment extends Fragment {
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
private void startDetailActivity(String url) {
|
||||
Intent i = new Intent(getActivity(), VideoItemDetailActivity.class);
|
||||
i.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
|
||||
i.putExtra(VideoItemDetailFragment.VIDEO_URL, url);
|
||||
getActivity().startActivity(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
if(!searchQuery.isEmpty()) {
|
||||
search(searchQuery);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString(QUERY, searchQuery);
|
||||
outState.putInt(STREAMING_SERVICE, streamingServiceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -255,15 +271,10 @@ public class SearchInfoItemFragment extends Fragment {
|
||||
inflater.inflate(R.menu.search_menu, menu);
|
||||
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search);
|
||||
searchView = (SearchView) searchItem.getActionView();
|
||||
SearchView searchView = (SearchView) searchItem.getActionView();
|
||||
setupSearchView(searchView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void setupSearchView(SearchView searchView) {
|
||||
suggestionListAdapter = new SuggestionListAdapter(getActivity());
|
||||
searchView.setSuggestionsAdapter(suggestionListAdapter);
|
||||
@ -278,7 +289,9 @@ public class SearchInfoItemFragment extends Fragment {
|
||||
private void search(String query) {
|
||||
infoListAdapter.clearSteamItemList();
|
||||
pageNumber = 0;
|
||||
searchQuery = query;
|
||||
search(query, pageNumber);
|
||||
hideBackground();
|
||||
loadingIndicator.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@ -288,6 +301,20 @@ public class SearchInfoItemFragment extends Fragment {
|
||||
sw.search(streamingServiceId, query, page, getActivity());
|
||||
}
|
||||
|
||||
private void setDoneLoading() {
|
||||
this.isLoading = false;
|
||||
loadingIndicator.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the "dummy" background when no results are shown
|
||||
*/
|
||||
private void hideBackground() {
|
||||
View view = getView();
|
||||
if(view == null) return;
|
||||
view.findViewById(R.id.mainBG).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void searchSuggestions(String query) {
|
||||
SuggestionSearchRunnable suggestionSearchRunnable =
|
||||
new SuggestionSearchRunnable(streamingServiceId, query, getActivity(), suggestionListAdapter);
|
||||
|
@ -25,8 +25,8 @@ import android.support.v7.widget.SearchView;
|
||||
|
||||
public class SearchSuggestionListener implements SearchView.OnSuggestionListener{
|
||||
|
||||
private SearchView searchView;
|
||||
private SuggestionListAdapter adapter;
|
||||
private final SearchView searchView;
|
||||
private final SuggestionListAdapter adapter;
|
||||
|
||||
public SearchSuggestionListener(SearchView searchView, SuggestionListAdapter adapter) {
|
||||
this.searchView = searchView;
|
||||
|
@ -82,6 +82,7 @@ public class SearchWorker {
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
final String serviceName = NewPipe.getNameOfService(serviceId);
|
||||
SearchResult result = null;
|
||||
SearchEngine engine = null;
|
||||
|
||||
@ -119,7 +120,7 @@ public class SearchWorker {
|
||||
View rootView = a.findViewById(android.R.id.content);
|
||||
ErrorActivity.reportError(h, a, result.errors, null, rootView,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
|
||||
/* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.light_parsing_error));
|
||||
serviceName, query, R.string.light_parsing_error));
|
||||
|
||||
}
|
||||
// hard errors:
|
||||
@ -148,8 +149,7 @@ public class SearchWorker {
|
||||
} catch(ExtractionException e) {
|
||||
ErrorActivity.reportError(h, a, e, null, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
|
||||
/* todo: this shoudl not be assigned static */
|
||||
YOUTUBE, query, R.string.parsing_error));
|
||||
serviceName, query, R.string.parsing_error));
|
||||
//postNewErrorToast(h, R.string.parsing_error);
|
||||
e.printStackTrace();
|
||||
|
||||
|
@ -3,10 +3,8 @@ package org.schabi.newpipe.search_fragment;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.support.v4.widget.ResourceCursorAdapter;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
@ -31,52 +29,56 @@ import java.util.List;
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class SuggestionListAdapter extends CursorAdapter {
|
||||
/**
|
||||
* {@link ResourceCursorAdapter} to display suggestions.
|
||||
*/
|
||||
public class SuggestionListAdapter extends ResourceCursorAdapter {
|
||||
|
||||
private static final String[] columns = new String[]{"_id", "title"};
|
||||
private static final int INDEX_ID = 0;
|
||||
private static final int INDEX_TITLE = 1;
|
||||
|
||||
private String[] columns = new String[]{"_id", "title"};
|
||||
|
||||
public SuggestionListAdapter(Context context) {
|
||||
super(context, null, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
ViewHolder viewHolder;
|
||||
|
||||
View view = LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_1, parent, false);
|
||||
viewHolder = new ViewHolder();
|
||||
viewHolder.suggestionTitle = (TextView) view.findViewById(android.R.id.text1);
|
||||
view.setTag(viewHolder);
|
||||
|
||||
|
||||
return view;
|
||||
super(context, android.R.layout.simple_list_item_1, null, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
ViewHolder viewHolder = (ViewHolder) view.getTag();
|
||||
viewHolder.suggestionTitle.setText(cursor.getString(1));
|
||||
ViewHolder viewHolder = new ViewHolder(view);
|
||||
viewHolder.suggestionTitle.setText(cursor.getString(INDEX_TITLE));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the suggestion list
|
||||
* @param suggestions the list of suggestions
|
||||
*/
|
||||
public void updateAdapter(List<String> suggestions) {
|
||||
MatrixCursor cursor = new MatrixCursor(columns);
|
||||
MatrixCursor cursor = new MatrixCursor(columns, suggestions.size());
|
||||
int i = 0;
|
||||
for (String s : suggestions) {
|
||||
String[] temp = new String[2];
|
||||
temp[0] = Integer.toString(i);
|
||||
temp[1] = s;
|
||||
for (String suggestion : suggestions) {
|
||||
String[] columnValues = new String[columns.length];
|
||||
columnValues[INDEX_TITLE] = suggestion;
|
||||
columnValues[INDEX_ID] = Integer.toString(i);
|
||||
cursor.addRow(columnValues);
|
||||
i++;
|
||||
cursor.addRow(temp);
|
||||
}
|
||||
changeCursor(cursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the suggestion for a position
|
||||
* @param position the position of the suggestion
|
||||
* @return the suggestion
|
||||
*/
|
||||
public String getSuggestion(int position) {
|
||||
return ((Cursor) getItem(position)).getString(1);
|
||||
return ((Cursor) getItem(position)).getString(INDEX_TITLE);
|
||||
}
|
||||
|
||||
private class ViewHolder {
|
||||
public TextView suggestionTitle;
|
||||
private final TextView suggestionTitle;
|
||||
private ViewHolder(View view) {
|
||||
this.suggestionTitle = (TextView) view.findViewById(android.R.id.text1);
|
||||
}
|
||||
}
|
||||
}
|
@ -37,14 +37,15 @@ import java.util.List;
|
||||
|
||||
public class SuggestionSearchRunnable implements Runnable{
|
||||
|
||||
/**
|
||||
* Runnable to update a {@link SuggestionListAdapter}
|
||||
*/
|
||||
private class SuggestionResultRunnable implements Runnable{
|
||||
|
||||
private List<String> suggestions;
|
||||
private SuggestionListAdapter adapter;
|
||||
private final List<String> suggestions;
|
||||
|
||||
private SuggestionResultRunnable(List<String> suggestions, SuggestionListAdapter adapter) {
|
||||
private SuggestionResultRunnable(List<String> suggestions) {
|
||||
this.suggestions = suggestions;
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -55,9 +56,9 @@ public class SuggestionSearchRunnable implements Runnable{
|
||||
|
||||
private final int serviceId;
|
||||
private final String query;
|
||||
final Handler h = new Handler();
|
||||
private Activity a = null;
|
||||
private SuggestionListAdapter adapter;
|
||||
private final Handler h = new Handler();
|
||||
private final Activity a;
|
||||
private final SuggestionListAdapter adapter;
|
||||
public SuggestionSearchRunnable(int serviceId, String query,
|
||||
Activity activity, SuggestionListAdapter adapter) {
|
||||
this.serviceId = serviceId;
|
||||
@ -76,7 +77,7 @@ public class SuggestionSearchRunnable implements Runnable{
|
||||
String searchLanguage = sp.getString(searchLanguageKey,
|
||||
a.getString(R.string.default_language_value));
|
||||
List<String> suggestions = se.suggestionList(query, searchLanguage);
|
||||
h.post(new SuggestionResultRunnable(suggestions, adapter));
|
||||
h.post(new SuggestionResultRunnable(suggestions));
|
||||
} catch (ExtractionException e) {
|
||||
ErrorActivity.reportError(h, a, e, null, a.findViewById(android.R.id.content),
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
|
||||
|
@ -5,6 +5,7 @@ import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.ActionBar;
|
||||
@ -16,6 +17,8 @@ import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 31.08.15.
|
||||
@ -43,6 +46,10 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceBundle) {
|
||||
if (Objects.equals(PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getString("theme", getResources().getString(R.string.light_theme_title)), getResources().getString(R.string.dark_theme_title))) {
|
||||
setTheme(R.style.DarkTheme);
|
||||
}
|
||||
getDelegate().installViewFactory();
|
||||
getDelegate().onCreate(savedInstanceBundle);
|
||||
super.onCreate(savedInstanceBundle);
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.schabi.newpipe.settings;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ListActivity;
|
||||
import android.content.ClipData;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
@ -20,6 +21,7 @@ import org.schabi.newpipe.App;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
|
||||
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
||||
|
||||
@ -55,6 +57,7 @@ public class SettingsFragment extends PreferenceFragment
|
||||
String DOWNLOAD_PATH_PREFERENCE;
|
||||
String DOWNLOAD_PATH_AUDIO_PREFERENCE;
|
||||
String USE_TOR_KEY;
|
||||
String THEME;
|
||||
|
||||
public static final int REQUEST_INSTALL_ORBOT = 0x1234;
|
||||
|
||||
@ -63,11 +66,11 @@ public class SettingsFragment extends PreferenceFragment
|
||||
private ListPreference searchLanguagePreference;
|
||||
private Preference downloadPathPreference;
|
||||
private Preference downloadPathAudioPreference;
|
||||
private Preference themePreference;
|
||||
private SharedPreferences defaultPreferences;
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.settings);
|
||||
|
||||
@ -81,6 +84,7 @@ public class SettingsFragment extends PreferenceFragment
|
||||
SEARCH_LANGUAGE_PREFERENCE = getString(R.string.search_language_key);
|
||||
DOWNLOAD_PATH_PREFERENCE = getString(R.string.download_path_key);
|
||||
DOWNLOAD_PATH_AUDIO_PREFERENCE = getString(R.string.download_path_audio_key);
|
||||
THEME = getString(R.string.theme_key);
|
||||
USE_TOR_KEY = getString(R.string.use_tor_key);
|
||||
|
||||
// get pref objects
|
||||
@ -92,6 +96,7 @@ public class SettingsFragment extends PreferenceFragment
|
||||
(ListPreference) findPreference(SEARCH_LANGUAGE_PREFERENCE);
|
||||
downloadPathPreference = findPreference(DOWNLOAD_PATH_PREFERENCE);
|
||||
downloadPathAudioPreference = findPreference(DOWNLOAD_PATH_AUDIO_PREFERENCE);
|
||||
themePreference = findPreference(THEME);
|
||||
|
||||
prefListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
@ -132,6 +137,11 @@ public class SettingsFragment extends PreferenceFragment
|
||||
downloadPathAudioPreference
|
||||
.setSummary(downloadPath);
|
||||
}
|
||||
else if (key == THEME)
|
||||
{
|
||||
String theme = sharedPreferences.getString(THEME, "Light");
|
||||
themePreference.setSummary(theme);
|
||||
}
|
||||
updateSummary();
|
||||
}
|
||||
};
|
||||
@ -161,7 +171,6 @@ public class SettingsFragment extends PreferenceFragment
|
||||
activity.startActivityForResult(i, R.string.download_path_audio_key);
|
||||
}
|
||||
}
|
||||
|
||||
return super.onPreferenceTreeClick(preferenceScreen, preference);
|
||||
}
|
||||
|
||||
@ -216,8 +225,8 @@ public class SettingsFragment extends PreferenceFragment
|
||||
// installing the app does not necessarily return RESULT_OK
|
||||
App.configureTor(requestCode == REQUEST_INSTALL_ORBOT
|
||||
&& OrbotHelper.requestStartTor(a));
|
||||
|
||||
}
|
||||
|
||||
updateSummary();
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
@ -239,6 +248,9 @@ public class SettingsFragment extends PreferenceFragment
|
||||
downloadPathAudioPreference.setSummary(
|
||||
defaultPreferences.getString(DOWNLOAD_PATH_AUDIO_PREFERENCE,
|
||||
getString(R.string.download_path_audio_summary)));
|
||||
themePreference.setSummary(
|
||||
defaultPreferences.getString(THEME,
|
||||
getString(R.string.light_theme_title)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,68 @@
|
||||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
||||
public class PermissionHelper {
|
||||
public static final int PERMISSION_WRITE_STORAGE = 778;
|
||||
public static final int PERMISSION_READ_STORAGE = 777;
|
||||
|
||||
|
||||
|
||||
public static boolean checkStoragePermissions(Activity activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
if(!checkReadStoragePermissions(activity)) return false;
|
||||
}
|
||||
return checkWriteStoragePermissions(activity);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
|
||||
public static boolean checkReadStoragePermissions(Activity activity) {
|
||||
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(activity,
|
||||
new String[]{
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
PERMISSION_READ_STORAGE);
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static boolean checkWriteStoragePermissions(Activity activity) {
|
||||
// Here, thisActivity is the current activity
|
||||
if (ContextCompat.checkSelfPermission(activity,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
|
||||
// Should we show an explanation?
|
||||
/*if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||
|
||||
// Show an explanation to the user *asynchronously* -- don't block
|
||||
// this thread waiting for the user's response! After the user
|
||||
// sees the explanation, try again to request the permission.
|
||||
} else {*/
|
||||
|
||||
// No explanation needed, we can request the permission.
|
||||
ActivityCompat.requestPermissions(activity,
|
||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
PERMISSION_WRITE_STORAGE);
|
||||
|
||||
// PERMISSION_WRITE_STORAGE is an
|
||||
// app-defined int constant. The callback method gets the
|
||||
// result of the request.
|
||||
/*}*/
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package us.shandian.giga.get;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides access to the storage of {@link DownloadMission}s
|
||||
*/
|
||||
public interface DownloadDataSource {
|
||||
|
||||
/**
|
||||
* Load all missions
|
||||
* @return a list of download missions
|
||||
*/
|
||||
List<DownloadMission> loadMissions();
|
||||
|
||||
/**
|
||||
* Add a downlaod mission to the storage
|
||||
* @param downloadMission the download mission to add
|
||||
* @return the identifier of the mission
|
||||
*/
|
||||
void addMission(DownloadMission downloadMission);
|
||||
|
||||
/**
|
||||
* Update a download mission which exists in the storage
|
||||
* @param downloadMission the download mission to update
|
||||
* @throws IllegalArgumentException if the mission was not added to storage
|
||||
*/
|
||||
void updateMission(DownloadMission downloadMission);
|
||||
|
||||
|
||||
/**
|
||||
* Delete a download mission
|
||||
* @param downloadMission the mission to delete
|
||||
*/
|
||||
void deleteMission(DownloadMission downloadMission);
|
||||
}
|
@ -3,12 +3,46 @@ package us.shandian.giga.get;
|
||||
public interface DownloadManager
|
||||
{
|
||||
int BLOCK_SIZE = 512 * 1024;
|
||||
|
||||
int startMission(String url, String name, boolean isAudio, int threads);
|
||||
|
||||
/**
|
||||
* Start a new download mission
|
||||
* @param url the url to download
|
||||
* @param location the location
|
||||
* @param name the name of the file to create
|
||||
* @param isAudio true if the download is an audio file
|
||||
* @param threads the number of threads maximal used to download chunks of the file. @return the identifier of the mission.
|
||||
*/
|
||||
int startMission(String url, String location, String name, boolean isAudio, int threads);
|
||||
|
||||
/**
|
||||
* Resume the execution of a download mission.
|
||||
* @param id the identifier of the mission to resume.
|
||||
*/
|
||||
void resumeMission(int id);
|
||||
|
||||
/**
|
||||
* Pause the execution of a download mission.
|
||||
* @param id the identifier of the mission to pause.
|
||||
*/
|
||||
void pauseMission(int id);
|
||||
|
||||
/**
|
||||
* Deletes the mission from the downloaded list but keeps the downloaded file.
|
||||
* @param id The mission identifier
|
||||
*/
|
||||
void deleteMission(int id);
|
||||
|
||||
/**
|
||||
* Get the download mission by its identifier
|
||||
* @param id the identifier of the download mission
|
||||
* @return the download mission or null if the mission doesn't exist
|
||||
*/
|
||||
DownloadMission getMission(int id);
|
||||
|
||||
/**
|
||||
* Get the number of download missions.
|
||||
* @return the number of download missions.
|
||||
*/
|
||||
int getCount();
|
||||
String getLocation();
|
||||
|
||||
}
|
||||
|
@ -1,17 +1,21 @@
|
||||
package us.shandian.giga.get;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import us.shandian.giga.util.Utility;
|
||||
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
||||
@ -19,33 +23,48 @@ 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();
|
||||
private final DownloadDataSource mDownloadDataSource;
|
||||
|
||||
private final ArrayList<DownloadMission> mMissions = new ArrayList<DownloadMission>();
|
||||
|
||||
/**
|
||||
* Create a new instance
|
||||
* @param searchLocations the directories to search for unfinished downloads
|
||||
* @param downloadDataSource the data source for finished downloads
|
||||
*/
|
||||
public DownloadManagerImpl(Collection<String> searchLocations, DownloadDataSource downloadDataSource) {
|
||||
mDownloadDataSource = downloadDataSource;
|
||||
loadMissions(searchLocations);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int startMission(String url, String name, boolean isAudio, int threads) {
|
||||
DownloadMission mission = new DownloadMission();
|
||||
mission.url = url;
|
||||
mission.name = name;
|
||||
if(isAudio) {
|
||||
mission.location = NewPipeSettings.getAudioDownloadPath(mContext);
|
||||
} else {
|
||||
mission.location = NewPipeSettings.getVideoDownloadPath(mContext);
|
||||
public int startMission(String url, String location, String name, boolean isAudio, int threads) {
|
||||
DownloadMission existingMission = getMissionByLocation(location, name);
|
||||
if(existingMission != null) {
|
||||
// Already downloaded or downloading
|
||||
if(existingMission.finished) {
|
||||
// Overwrite mission
|
||||
deleteMission(mMissions.indexOf(existingMission));
|
||||
} else {
|
||||
// Rename file (?)
|
||||
try {
|
||||
name = generateUniqueName(location, name);
|
||||
}catch (Exception e) {
|
||||
Log.e(TAG, "Unable to generate unique name", e);
|
||||
name = System.currentTimeMillis() + name ;
|
||||
Log.i(TAG, "Using " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DownloadMission mission = new DownloadMission(name, url, location);
|
||||
mission.timestamp = System.currentTimeMillis();
|
||||
mission.threadCount = threads;
|
||||
new Initializer(mContext, mission).start();
|
||||
mission.addListener(new MissionListener(mission));
|
||||
new Initializer(mission).start();
|
||||
return insertMission(mission);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void resumeMission(int i) {
|
||||
DownloadMission d = getMission(i);
|
||||
@ -53,7 +72,7 @@ public class DownloadManagerImpl implements DownloadManager
|
||||
d.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void pauseMission(int i) {
|
||||
DownloadMission d = getMission(i);
|
||||
@ -61,55 +80,94 @@ public class DownloadManagerImpl implements DownloadManager
|
||||
d.pause();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void deleteMission(int i) {
|
||||
getMission(i).delete();
|
||||
DownloadMission mission = getMission(i);
|
||||
if(mission.finished) {
|
||||
mDownloadDataSource.deleteMission(mission);
|
||||
}
|
||||
mission.delete();
|
||||
mMissions.remove(i);
|
||||
}
|
||||
|
||||
private void loadMissions() {
|
||||
File f = new File(mLocation);
|
||||
|
||||
private void loadMissions(Iterable<String> searchLocations) {
|
||||
mMissions.clear();
|
||||
loadFinishedMissions();
|
||||
for(String location: searchLocations) {
|
||||
loadMissions(location);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads finished missions from the data source
|
||||
*/
|
||||
private void loadFinishedMissions() {
|
||||
List<DownloadMission> finishedMissions = mDownloadDataSource.loadMissions();
|
||||
if(finishedMissions == null) {
|
||||
finishedMissions = new ArrayList<>();
|
||||
}
|
||||
// Ensure its sorted
|
||||
Collections.sort(finishedMissions, new Comparator<DownloadMission>() {
|
||||
@Override
|
||||
public int compare(DownloadMission o1, DownloadMission o2) {
|
||||
return (int) (o1.timestamp - o2.timestamp);
|
||||
}
|
||||
});
|
||||
mMissions.ensureCapacity(mMissions.size() + finishedMissions.size());
|
||||
for(DownloadMission mission: finishedMissions) {
|
||||
File downloadedFile = mission.getDownloadedFile();
|
||||
if(!downloadedFile.isFile()) {
|
||||
if(DEBUG) {
|
||||
Log.d(TAG, "downloaded file removed: " + downloadedFile.getAbsolutePath());
|
||||
}
|
||||
mDownloadDataSource.deleteMission(mission);
|
||||
} else {
|
||||
mission.length = downloadedFile.length();
|
||||
mission.finished = true;
|
||||
mission.running = false;
|
||||
mMissions.add(mission);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadMissions(String location) {
|
||||
|
||||
File f = new File(location);
|
||||
|
||||
if (f.exists() && f.isDirectory()) {
|
||||
File[] subs = f.listFiles();
|
||||
|
||||
|
||||
if(subs == null) {
|
||||
Log.e(TAG, "listFiles() returned null");
|
||||
return;
|
||||
}
|
||||
|
||||
for (File sub : subs) {
|
||||
if (sub.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sub.getName().endsWith(".giga")) {
|
||||
if (sub.isFile() && 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();
|
||||
if(!sub.delete()) {
|
||||
Log.w(TAG, "Unable to delete .giga file: " + sub.getPath());
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -144,18 +202,81 @@ public class DownloadManagerImpl implements DownloadManager
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocation() {
|
||||
return mLocation;
|
||||
|
||||
/**
|
||||
* Get a mission by its location and name
|
||||
* @param location the location
|
||||
* @param name the name
|
||||
* @return the mission or null if no such mission exists
|
||||
*/
|
||||
private @Nullable DownloadMission getMissionByLocation(String location, String name) {
|
||||
for(DownloadMission mission: mMissions) {
|
||||
if(location.equals(mission.location) && name.equals(mission.name)) {
|
||||
return mission;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Splits the filename into name and extension
|
||||
*
|
||||
* Dots are ignored if they appear: not at all, at the beginning of the file,
|
||||
* at the end of the file
|
||||
*
|
||||
* @param name the name to split
|
||||
* @return a string array with a length of 2 containing the name and the extension
|
||||
*/
|
||||
private static String[] splitName(String name) {
|
||||
int dotIndex = name.lastIndexOf('.');
|
||||
if(dotIndex <= 0 || (dotIndex == name.length() - 1)) {
|
||||
return new String[]{name, ""};
|
||||
} else {
|
||||
return new String[]{name.substring(0, dotIndex), name.substring(dotIndex + 1)};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique file name.
|
||||
*
|
||||
* e.g. "myname (1).txt" if the name "myname.txt" exists.
|
||||
* @param location the location (to check for existing files)
|
||||
* @param name the name of the file
|
||||
* @return the unique file name
|
||||
* @throws IllegalArgumentException if the location is not a directory
|
||||
* @throws SecurityException if the location is not readable
|
||||
*/
|
||||
private static String generateUniqueName(String location, String name) {
|
||||
if(location == null) throw new NullPointerException("location is null");
|
||||
if(name == null) throw new NullPointerException("name is null");
|
||||
File destination = new File(location);
|
||||
if(!destination.isDirectory()) {
|
||||
throw new IllegalArgumentException("location is not a directory: " + location);
|
||||
}
|
||||
final String[] nameParts = splitName(name);
|
||||
String[] existingName = destination.list(new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
return name.startsWith(nameParts[0]);
|
||||
}
|
||||
});
|
||||
Arrays.sort(existingName);
|
||||
String newName;
|
||||
int downloadIndex = 0;
|
||||
do {
|
||||
newName = nameParts[0] + " (" + downloadIndex + ")." + nameParts[1];
|
||||
++downloadIndex;
|
||||
if(downloadIndex == 1000) { // Probably an error on our side
|
||||
throw new RuntimeException("Too many existing files");
|
||||
}
|
||||
} while (Arrays.binarySearch(existingName, newName) >= 0);
|
||||
return newName;
|
||||
}
|
||||
|
||||
private class Initializer extends Thread {
|
||||
private Context context;
|
||||
private DownloadMission mission;
|
||||
|
||||
public Initializer(Context context, DownloadMission mission) {
|
||||
this.context = context;
|
||||
public Initializer(DownloadMission mission) {
|
||||
this.mission = mission;
|
||||
}
|
||||
|
||||
@ -217,4 +338,30 @@ public class DownloadManagerImpl implements DownloadManager
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for mission to finish to add it to the {@link #mDownloadDataSource}
|
||||
*/
|
||||
private class MissionListener implements DownloadMission.MissionListener {
|
||||
private final DownloadMission mMission;
|
||||
|
||||
private MissionListener(DownloadMission mission) {
|
||||
if(mission == null) throw new NullPointerException("mission is null");
|
||||
// Could the mission be passed in onFinish()?
|
||||
mMission = mission;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish(DownloadMission downloadMission) {
|
||||
mDownloadDataSource.addMission(mMission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(DownloadMission downloadMission, int errCode) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package us.shandian.giga.get;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
@ -10,39 +9,63 @@ 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 java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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 interface MissionListener {
|
||||
HashMap<MissionListener, Handler> handlerStore = new HashMap<>();
|
||||
|
||||
void onProgressUpdate(long done, long total);
|
||||
void onFinish();
|
||||
void onError(int errCode);
|
||||
void onProgressUpdate(DownloadMission downloadMission, long done, long total);
|
||||
void onFinish(DownloadMission downloadMission);
|
||||
void onError(DownloadMission downloadMission, 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 = "";
|
||||
|
||||
/**
|
||||
* The filename
|
||||
*/
|
||||
public String name;
|
||||
|
||||
/**
|
||||
* The url of the file to download
|
||||
*/
|
||||
public String url;
|
||||
|
||||
/**
|
||||
* The directory to store the download
|
||||
*/
|
||||
public String location;
|
||||
|
||||
/**
|
||||
* Number of blocks the size of {@link DownloadManager#BLOCK_SIZE}
|
||||
*/
|
||||
public long blocks;
|
||||
|
||||
/**
|
||||
* Number of bytes
|
||||
*/
|
||||
public long length;
|
||||
|
||||
/**
|
||||
* Number of bytes downloaded
|
||||
*/
|
||||
public long done;
|
||||
public int threadCount = 3;
|
||||
public int finishCount;
|
||||
public List<Long> threadPositions = new ArrayList<Long>();
|
||||
public Map<Long, Boolean> blockState = new HashMap<Long, Boolean>();
|
||||
private List<Long> threadPositions = new ArrayList<Long>();
|
||||
public final Map<Long, Boolean> blockState = new HashMap<Long, Boolean>();
|
||||
public boolean running;
|
||||
public boolean finished;
|
||||
public boolean fallback;
|
||||
@ -53,23 +76,65 @@ public class DownloadMission
|
||||
|
||||
private transient ArrayList<WeakReference<MissionListener>> mListeners = new ArrayList<WeakReference<MissionListener>>();
|
||||
private transient boolean mWritingToFile;
|
||||
|
||||
|
||||
private static final int NO_IDENTIFIER = -1;
|
||||
private long db_identifier = NO_IDENTIFIER;
|
||||
|
||||
public DownloadMission() {
|
||||
}
|
||||
|
||||
public DownloadMission(String name, String url, String location) {
|
||||
if(name == null) throw new NullPointerException("name is null");
|
||||
if(name.isEmpty()) throw new IllegalArgumentException("name is empty");
|
||||
if(url == null) throw new NullPointerException("url is null");
|
||||
if(url.isEmpty()) throw new IllegalArgumentException("url is empty");
|
||||
if(location == null) throw new NullPointerException("location is null");
|
||||
if(location.isEmpty()) throw new IllegalArgumentException("location is empty");
|
||||
this.url = url;
|
||||
this.name = name;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
|
||||
private void checkBlock(long block) {
|
||||
if(block < 0 || block >= blocks) {
|
||||
throw new IllegalArgumentException("illegal block identifier");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a block is reserved
|
||||
* @param block the block identifier
|
||||
* @return true if the block is reserved and false if otherwise
|
||||
*/
|
||||
public boolean isBlockPreserved(long block) {
|
||||
checkBlock(block);
|
||||
return blockState.containsKey(block) ? blockState.get(block) : false;
|
||||
}
|
||||
|
||||
public void preserveBlock(long block) {
|
||||
checkBlock(block);
|
||||
synchronized (blockState) {
|
||||
blockState.put(block, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPosition(int id, long position) {
|
||||
threadPositions.set(id, position);
|
||||
|
||||
/**
|
||||
* Set the download position of the file
|
||||
* @param threadId the identifier of the thread
|
||||
* @param position the download position of the thread
|
||||
*/
|
||||
public void setPosition(int threadId, long position) {
|
||||
threadPositions.set(threadId, position);
|
||||
}
|
||||
|
||||
public long getPosition(int id) {
|
||||
return threadPositions.get(id);
|
||||
|
||||
/**
|
||||
* Get the position of a thread
|
||||
* @param threadId the identifier of the thread
|
||||
* @return the position for the thread
|
||||
*/
|
||||
public long getPosition(int threadId) {
|
||||
return threadPositions.get(threadId);
|
||||
}
|
||||
|
||||
public synchronized void notifyProgress(long deltaLen) {
|
||||
@ -95,13 +160,16 @@ public class DownloadMission
|
||||
MissionListener.handlerStore.get(listener).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onProgressUpdate(done, length);
|
||||
listener.onProgressUpdate(DownloadMission.this, done, length);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called by a download thread when it finished.
|
||||
*/
|
||||
public synchronized void notifyFinished() {
|
||||
if (errCode > 0) return;
|
||||
|
||||
@ -111,7 +179,10 @@ public class DownloadMission
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when all parts are downloaded
|
||||
*/
|
||||
private void onFinish() {
|
||||
if (errCode > 0) return;
|
||||
|
||||
@ -130,7 +201,7 @@ public class DownloadMission
|
||||
MissionListener.handlerStore.get(listener).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onFinish();
|
||||
listener.onFinish(DownloadMission.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -147,7 +218,7 @@ public class DownloadMission
|
||||
MissionListener.handlerStore.get(listener).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onError(errCode);
|
||||
listener.onError(DownloadMission.this, errCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -169,7 +240,10 @@ public class DownloadMission
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start downloading with multiple threads.
|
||||
*/
|
||||
public void start() {
|
||||
if (!running && !finished) {
|
||||
running = true;
|
||||
@ -200,12 +274,19 @@ public class DownloadMission
|
||||
// if (err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the file and the meta file
|
||||
*/
|
||||
public void delete() {
|
||||
deleteThisFromFile();
|
||||
new File(location + "/" + name).delete();
|
||||
new File(location, name).delete();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write this {@link DownloadMission} to the meta file asynchronously
|
||||
* if no thread is already running.
|
||||
*/
|
||||
public void writeThisToFile() {
|
||||
if (!mWritingToFile) {
|
||||
mWritingToFile = true;
|
||||
@ -218,14 +299,30 @@ public class DownloadMission
|
||||
}.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write this {@link DownloadMission} to the meta file.
|
||||
*/
|
||||
private void doWriteThisToFile() {
|
||||
synchronized (blockState) {
|
||||
Utility.writeToFile(location + "/" + name + ".giga", new Gson().toJson(this));
|
||||
Utility.writeToFile(getMetaFilename(), new Gson().toJson(this));
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteThisFromFile() {
|
||||
new File(location + "/" + name + ".giga").delete();
|
||||
new File(getMetaFilename()).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path of the meta file
|
||||
* @return the path to the meta file
|
||||
*/
|
||||
private String getMetaFilename() {
|
||||
return location + "/" + name + ".giga";
|
||||
}
|
||||
|
||||
public File getDownloadedFile() {
|
||||
return new File(location, name);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,14 +9,19 @@ import java.net.URL;
|
||||
|
||||
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
||||
|
||||
/**
|
||||
* Runnable to download blocks of a file until the file is completely downloaded,
|
||||
* an error occurs or the process is stopped.
|
||||
*/
|
||||
public class DownloadRunnable implements Runnable
|
||||
{
|
||||
private static final String TAG = DownloadRunnable.class.getSimpleName();
|
||||
|
||||
private DownloadMission mMission;
|
||||
private int mId;
|
||||
private final DownloadMission mMission;
|
||||
private final int mId;
|
||||
|
||||
public DownloadRunnable(DownloadMission mission, int id) {
|
||||
if(mission == null) throw new NullPointerException("mission is null");
|
||||
mMission = mission;
|
||||
mId = id;
|
||||
}
|
||||
@ -86,7 +91,7 @@ public class DownloadRunnable implements Runnable
|
||||
Log.d(TAG, mId + ":Content-Length=" + conn.getContentLength() + " Code:" + conn.getResponseCode());
|
||||
}
|
||||
|
||||
// A server may be ignoring the range requet
|
||||
// A server may be ignoring the range request
|
||||
if (conn.getResponseCode() != 206) {
|
||||
mMission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED;
|
||||
notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
|
||||
@ -131,7 +136,7 @@ public class DownloadRunnable implements Runnable
|
||||
notifyProgress(-total);
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, mId + ":position " + position + " retrying");
|
||||
Log.d(TAG, mId + ":position " + position + " retrying", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,11 @@ import java.net.URL;
|
||||
// Single-threaded fallback mode
|
||||
public class DownloadRunnableFallback implements Runnable
|
||||
{
|
||||
private DownloadMission mMission;
|
||||
private final DownloadMission mMission;
|
||||
//private int mId;
|
||||
|
||||
public DownloadRunnableFallback(DownloadMission mission) {
|
||||
if(mission == null) throw new NullPointerException("mission is null");
|
||||
//mId = id;
|
||||
mMission = mission;
|
||||
}
|
||||
@ -35,7 +36,7 @@ public class DownloadRunnableFallback implements Runnable
|
||||
f.write(buf, 0, len);
|
||||
notifyProgress(len);
|
||||
|
||||
if (Thread.currentThread().interrupted()) {
|
||||
if (Thread.interrupted()) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,102 @@
|
||||
package us.shandian.giga.get.sqlite;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import us.shandian.giga.get.DownloadMission;
|
||||
|
||||
/**
|
||||
* SqliteHelper to store {@link us.shandian.giga.get.DownloadMission}
|
||||
*/
|
||||
public class DownloadMissionSQLiteHelper extends SQLiteOpenHelper {
|
||||
|
||||
|
||||
private final String TAG = "DownloadMissionHelper";
|
||||
|
||||
// TODO: use NewPipeSQLiteHelper ('s constants) when playlist branch is merged (?)
|
||||
private static final String DATABASE_NAME = "downloads.db";
|
||||
|
||||
private static final int DATABASE_VERSION = 2;
|
||||
/**
|
||||
* The table name of download missions
|
||||
*/
|
||||
static final String MISSIONS_TABLE_NAME = "download_missions";
|
||||
|
||||
/**
|
||||
* The key to the directory location of the mission
|
||||
*/
|
||||
static final String KEY_LOCATION = "location";
|
||||
/**
|
||||
* The key to the url of a mission
|
||||
*/
|
||||
static final String KEY_URL = "url";
|
||||
/**
|
||||
* The key to the name of a mission
|
||||
*/
|
||||
static final String KEY_NAME = "name";
|
||||
|
||||
/**
|
||||
* The key to the done.
|
||||
*/
|
||||
static final String KEY_DONE = "bytes_downloaded";
|
||||
|
||||
static final String KEY_TIMESTAMP = "timestamp";
|
||||
|
||||
/**
|
||||
* The statement to create the table
|
||||
*/
|
||||
private static final String MISSIONS_CREATE_TABLE =
|
||||
"CREATE TABLE " + MISSIONS_TABLE_NAME + " (" +
|
||||
KEY_LOCATION + " TEXT NOT NULL, " +
|
||||
KEY_NAME + " TEXT NOT NULL, " +
|
||||
KEY_URL + " TEXT NOT NULL, " +
|
||||
KEY_DONE + " INTEGER NOT NULL, " +
|
||||
KEY_TIMESTAMP + " INTEGER NOT NULL, " +
|
||||
" UNIQUE(" + KEY_LOCATION + ", " + KEY_NAME + "));";
|
||||
|
||||
|
||||
DownloadMissionSQLiteHelper(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all values of the download mission as ContentValues.
|
||||
* @param downloadMission the download mission
|
||||
* @return the content values
|
||||
*/
|
||||
public static ContentValues getValuesOfMission(DownloadMission downloadMission) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(KEY_URL, downloadMission.url);
|
||||
values.put(KEY_LOCATION, downloadMission.location);
|
||||
values.put(KEY_NAME, downloadMission.name);
|
||||
values.put(KEY_DONE, downloadMission.done);
|
||||
values.put(KEY_TIMESTAMP, downloadMission.timestamp);
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL(MISSIONS_CREATE_TABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
// Currently nothing to do
|
||||
}
|
||||
|
||||
public static DownloadMission getMissionFromCursor(Cursor cursor) {
|
||||
if(cursor == null) throw new NullPointerException("cursor is null");
|
||||
int pos;
|
||||
String name = cursor.getString(cursor.getColumnIndexOrThrow(KEY_NAME));
|
||||
String location = cursor.getString(cursor.getColumnIndexOrThrow(KEY_LOCATION));
|
||||
String url = cursor.getString(cursor.getColumnIndexOrThrow(KEY_URL));
|
||||
DownloadMission mission = new DownloadMission(name, url, location);
|
||||
mission.done = cursor.getLong(cursor.getColumnIndexOrThrow(KEY_DONE));
|
||||
mission.timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(KEY_TIMESTAMP));
|
||||
mission.finished = true;
|
||||
return mission;
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package us.shandian.giga.get.sqlite;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import us.shandian.giga.get.DownloadDataSource;
|
||||
import us.shandian.giga.get.DownloadMission;
|
||||
|
||||
import static us.shandian.giga.get.sqlite.DownloadMissionSQLiteHelper.KEY_LOCATION;
|
||||
import static us.shandian.giga.get.sqlite.DownloadMissionSQLiteHelper.KEY_NAME;
|
||||
import static us.shandian.giga.get.sqlite.DownloadMissionSQLiteHelper.MISSIONS_TABLE_NAME;
|
||||
|
||||
|
||||
/**
|
||||
* Non-thread-safe implementation of {@link DownloadDataSource}
|
||||
*/
|
||||
public class SQLiteDownloadDataSource implements DownloadDataSource {
|
||||
|
||||
private static final String TAG = "DownloadDataSourceImpl";
|
||||
private final DownloadMissionSQLiteHelper downloadMissionSQLiteHelper;
|
||||
|
||||
public SQLiteDownloadDataSource(Context context) {
|
||||
downloadMissionSQLiteHelper = new DownloadMissionSQLiteHelper(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DownloadMission> loadMissions() {
|
||||
ArrayList<DownloadMission> result;
|
||||
SQLiteDatabase database = downloadMissionSQLiteHelper.getReadableDatabase();
|
||||
Cursor cursor = database.query(MISSIONS_TABLE_NAME, null, null,
|
||||
null, null, null, DownloadMissionSQLiteHelper.KEY_TIMESTAMP);
|
||||
|
||||
int count = cursor.getCount();
|
||||
if(count == 0) return new ArrayList<>();
|
||||
result = new ArrayList<>(count);
|
||||
while (cursor.moveToNext()) {
|
||||
result.add(DownloadMissionSQLiteHelper.getMissionFromCursor(cursor));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMission(DownloadMission downloadMission) {
|
||||
if(downloadMission == null) throw new NullPointerException("downloadMission is null");
|
||||
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
|
||||
ContentValues values = DownloadMissionSQLiteHelper.getValuesOfMission(downloadMission);
|
||||
database.insert(MISSIONS_TABLE_NAME, null, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMission(DownloadMission downloadMission) {
|
||||
if(downloadMission == null) throw new NullPointerException("downloadMission is null");
|
||||
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
|
||||
ContentValues values = DownloadMissionSQLiteHelper.getValuesOfMission(downloadMission);
|
||||
String whereClause = KEY_LOCATION+ " = ? AND " +
|
||||
KEY_NAME + " = ?";
|
||||
int rowsAffected = database.update(MISSIONS_TABLE_NAME, values,
|
||||
whereClause, new String[]{downloadMission.location, downloadMission.name});
|
||||
if(rowsAffected != 1) {
|
||||
Log.e(TAG, "Expected 1 row to be affected by update but got " + rowsAffected);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteMission(DownloadMission downloadMission) {
|
||||
if(downloadMission == null) throw new NullPointerException("downloadMission is null");
|
||||
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
|
||||
database.delete(MISSIONS_TABLE_NAME,
|
||||
KEY_LOCATION + " = ? AND " +
|
||||
KEY_NAME + " = ?",
|
||||
new String[]{downloadMission.location, downloadMission.name});
|
||||
}
|
||||
}
|
@ -1,56 +1,96 @@
|
||||
package us.shandian.giga.service;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
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.support.v4.content.PermissionChecker;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.download.DownloadActivity;
|
||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import us.shandian.giga.get.DownloadDataSource;
|
||||
import us.shandian.giga.get.DownloadManager;
|
||||
import us.shandian.giga.get.DownloadManagerImpl;
|
||||
import us.shandian.giga.get.DownloadMission;
|
||||
import us.shandian.giga.get.sqlite.SQLiteDownloadDataSource;
|
||||
|
||||
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
||||
|
||||
public class DownloadManagerService extends Service implements DownloadMission.MissionListener
|
||||
public class DownloadManagerService extends Service
|
||||
{
|
||||
|
||||
|
||||
private static final String TAG = DownloadManagerService.class.getSimpleName();
|
||||
|
||||
|
||||
/**
|
||||
* Message code of update messages stored as {@link Message#what}.
|
||||
*/
|
||||
private static final int UPDATE_MESSAGE = 0;
|
||||
private static final int NOTIFICATION_ID = 1000;
|
||||
private static final String EXTRA_NAME = "DownloadManagerService.extra.name";
|
||||
private static final String EXTRA_LOCATION = "DownloadManagerService.extra.location";
|
||||
private static final String EXTRA_IS_AUDIO = "DownloadManagerService.extra.is_audio";
|
||||
private static final String EXTRA_THREADS = "DownloadManagerService.extra.threads";
|
||||
|
||||
|
||||
private DMBinder mBinder;
|
||||
private DownloadManager mManager;
|
||||
private Notification mNotification;
|
||||
private Handler mHandler;
|
||||
private long mLastTimeStamp = System.currentTimeMillis();
|
||||
private DownloadDataSource mDataSource;
|
||||
|
||||
|
||||
|
||||
private MissionListener missionListener = new MissionListener();
|
||||
|
||||
|
||||
private void notifyMediaScanner(DownloadMission mission) {
|
||||
Uri uri = Uri.parse("file://" + mission.location + "/" + mission.name);
|
||||
// notify media scanner on downloaded media file ...
|
||||
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onCreate");
|
||||
}
|
||||
|
||||
|
||||
mBinder = new DMBinder();
|
||||
if(mDataSource == null) {
|
||||
mDataSource = new SQLiteDownloadDataSource(this);
|
||||
}
|
||||
if (mManager == null) {
|
||||
String path = NewPipeSettings.getVideoDownloadPath(this);
|
||||
mManager = new DownloadManagerImpl(this, path);
|
||||
ArrayList<String> paths = new ArrayList<>(2);
|
||||
paths.add(NewPipeSettings.getVideoDownloadPath(this));
|
||||
paths.add(NewPipeSettings.getAudioDownloadPath(this));
|
||||
mManager = new DownloadManagerImpl(paths, mDataSource);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "mManager == null");
|
||||
Log.d(TAG, "Download directory: " + path);
|
||||
Log.d(TAG, "Download directory: " + paths);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Intent i = new Intent();
|
||||
i.setAction(Intent.ACTION_MAIN);
|
||||
i.setClass(this, DownloadActivity.class);
|
||||
@ -83,28 +123,50 @@ public class DownloadManagerService extends Service implements DownloadMission.M
|
||||
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++;
|
||||
switch (msg.what) {
|
||||
case UPDATE_MESSAGE: {
|
||||
int runningCount = 0;
|
||||
|
||||
for (int i = 0; i < mManager.getCount(); i++) {
|
||||
if (mManager.getMission(i).running) {
|
||||
runningCount++;
|
||||
}
|
||||
}
|
||||
updateState(runningCount);
|
||||
break;
|
||||
}
|
||||
|
||||
updateState(runningCount);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
private void startMissionAsync(final String url, final String location, final String name,
|
||||
final boolean isAudio, final int threads) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int missionId = mManager.startMission(url, location, name, isAudio, threads);
|
||||
mBinder.onMissionAdded(mManager.getMission(missionId));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Starting");
|
||||
}
|
||||
|
||||
Log.i(TAG, "Got intent: " + intent);
|
||||
String action = intent.getAction();
|
||||
if(action != null && action.equals(Intent.ACTION_RUN)) {
|
||||
String name = intent.getStringExtra(EXTRA_NAME);
|
||||
String location = intent.getStringExtra(EXTRA_LOCATION);
|
||||
int threads = intent.getIntExtra(EXTRA_THREADS, 1);
|
||||
boolean isAudio = intent.getBooleanExtra(EXTRA_IS_AUDIO, false);
|
||||
String url = intent.getDataString();
|
||||
startMissionAsync(url, location, name, isAudio, threads);
|
||||
}
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@ -119,52 +181,76 @@ public class DownloadManagerService extends Service implements DownloadMission.M
|
||||
for (int i = 0; i < mManager.getCount(); i++) {
|
||||
mManager.pauseMission(i);
|
||||
}
|
||||
|
||||
|
||||
stopForeground(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
int permissionCheck;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
|
||||
permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||
if(permissionCheck == PermissionChecker.PERMISSION_DENIED) {
|
||||
Toast.makeText(this, "Permission denied (read)", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
if(permissionCheck == PermissionChecker.PERMISSION_DENIED) {
|
||||
Toast.makeText(this, "Permission denied (write)", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
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);
|
||||
mHandler.sendEmptyMessage(UPDATE_MESSAGE);
|
||||
}
|
||||
|
||||
private void updateState(int runningCount) {
|
||||
if (runningCount == 0) {
|
||||
stopForeground(true);
|
||||
} else {
|
||||
startForeground(1000, mNotification);
|
||||
startForeground(NOTIFICATION_ID, mNotification);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void startMission(Context context, String url, String location, String name, boolean isAudio, int threads) {
|
||||
Intent intent = new Intent(context, DownloadManagerService.class);
|
||||
intent.setAction(Intent.ACTION_RUN);
|
||||
intent.setData(Uri.parse(url));
|
||||
intent.putExtra(EXTRA_NAME, name);
|
||||
intent.putExtra(EXTRA_LOCATION, location);
|
||||
intent.putExtra(EXTRA_IS_AUDIO, isAudio);
|
||||
intent.putExtra(EXTRA_THREADS, threads);
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
|
||||
class MissionListener implements DownloadMission.MissionListener {
|
||||
@Override
|
||||
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
|
||||
long now = System.currentTimeMillis();
|
||||
long delta = now - mLastTimeStamp;
|
||||
if (delta > 2000) {
|
||||
postUpdateMessage();
|
||||
mLastTimeStamp = now;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish(DownloadMission downloadMission) {
|
||||
postUpdateMessage();
|
||||
notifyMediaScanner(downloadMission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(DownloadMission downloadMission, int errCode) {
|
||||
postUpdateMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Wrapper of DownloadManager
|
||||
public class DMBinder extends Binder {
|
||||
public DownloadManager getDownloadManager() {
|
||||
@ -172,15 +258,13 @@ public class DownloadManagerService extends Service implements DownloadMission.M
|
||||
}
|
||||
|
||||
public void onMissionAdded(DownloadMission mission) {
|
||||
mission.addListener(DownloadManagerService.this);
|
||||
mission.addListener(missionListener);
|
||||
postUpdateMessage();
|
||||
}
|
||||
|
||||
public void onMissionRemoved(DownloadMission mission) {
|
||||
mission.removeListener(DownloadManagerService.this);
|
||||
mission.removeListener(missionListener);
|
||||
postUpdateMessage();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,6 +5,9 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.support.v4.content.FileProvider;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@ -19,6 +22,7 @@ import android.support.v7.widget.RecyclerView;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
@ -28,10 +32,14 @@ import us.shandian.giga.service.DownloadManagerService;
|
||||
import us.shandian.giga.ui.common.ProgressDrawable;
|
||||
import us.shandian.giga.util.Utility;
|
||||
|
||||
import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
|
||||
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
||||
|
||||
public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHolder>
|
||||
{
|
||||
private static final Map<Integer, String> ALGORITHMS = new HashMap<>();
|
||||
|
||||
private static final String TAG = "MissionAdapter";
|
||||
|
||||
static {
|
||||
ALGORITHMS.put(R.id.md5, "MD5");
|
||||
ALGORITHMS.put(R.id.sha1, "SHA1");
|
||||
@ -143,9 +151,8 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
||||
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.status.setText(String.format(Locale.US, "%.2f%%", progress * 100));
|
||||
h.progress.setProgress(progress);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,23 +219,23 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
||||
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);
|
||||
File f = new File(h.mission.location, h.mission.name);
|
||||
String ext = Utility.getFileExt(h.mission.name);
|
||||
|
||||
if (ext == null) return false;
|
||||
|
||||
Log.d(TAG, "Viewing file: " + f.getAbsolutePath() + " ext: " + ext);
|
||||
|
||||
if (ext == null) {
|
||||
Log.w(TAG, "Can't view file because it has no extension: " +
|
||||
h.mission.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.substring(1));
|
||||
|
||||
Log.v(TAG, "Mime: " + mime + " package: " + mContext.getApplicationContext().getPackageName() + ".provider");
|
||||
if (f.exists()) {
|
||||
i.setDataAndType(Uri.fromFile(f), mime);
|
||||
|
||||
try {
|
||||
mContext.startActivity(i);
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
viewFileWithFileProvider(f, mime);
|
||||
} else {
|
||||
Log.w(TAG, "File doesn't exist");
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -249,6 +256,34 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
||||
|
||||
popup.show();
|
||||
}
|
||||
|
||||
private void viewFile(File file, String mimetype) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(Uri.fromFile(file), mimetype);
|
||||
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
intent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION);
|
||||
}
|
||||
//mContext.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
Log.v(TAG, "Starting intent: " + intent);
|
||||
mContext.startActivity(intent);
|
||||
}
|
||||
|
||||
private void viewFileWithFileProvider(File file, String mimetype) {
|
||||
String ourPackage = mContext.getApplicationContext().getPackageName();
|
||||
Uri uri = FileProvider.getUriForFile(mContext, ourPackage + ".provider", file);
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(uri, mimetype);
|
||||
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
intent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION);
|
||||
}
|
||||
//mContext.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
Log.v(TAG, "Starting intent: " + intent);
|
||||
mContext.startActivity(intent);
|
||||
}
|
||||
|
||||
private class ChecksumTask extends AsyncTask<String, Void, String> {
|
||||
ProgressDialog prog;
|
||||
@ -280,7 +315,7 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public DownloadMission mission;
|
||||
public int position;
|
||||
|
||||
|
||||
public TextView status;
|
||||
public ImageView icon;
|
||||
public TextView name;
|
||||
@ -289,7 +324,7 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
||||
public ImageView menu;
|
||||
public ProgressDrawable progress;
|
||||
public MissionObserver observer;
|
||||
|
||||
|
||||
public long lastTimeStamp = -1;
|
||||
public long lastDone = -1;
|
||||
public int colorId;
|
||||
@ -316,12 +351,12 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressUpdate(long done, long total) {
|
||||
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
|
||||
mAdapter.updateProgress(mHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
public void onFinish(DownloadMission downloadMission) {
|
||||
//mAdapter.mManager.deleteMission(mHolder.position);
|
||||
// TODO Notification
|
||||
//mAdapter.notifyDataSetChanged();
|
||||
@ -332,7 +367,7 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errCode) {
|
||||
public void onError(DownloadMission downloadMission, int errCode) {
|
||||
mAdapter.updateProgress(mHolder);
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ public abstract class MissionsFragment extends Fragment
|
||||
private MissionAdapter mAdapter;
|
||||
private GridLayoutManager mGridManager;
|
||||
private LinearLayoutManager mLinearManager;
|
||||
private Activity mActivity;
|
||||
private Context mActivity;
|
||||
|
||||
private ServiceConnection mConnection = new ServiceConnection() {
|
||||
|
||||
@ -65,7 +65,7 @@ public abstract class MissionsFragment extends Fragment
|
||||
|
||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
mLinear = mPrefs.getBoolean("linear", false);
|
||||
|
||||
|
||||
// Bind the service
|
||||
Intent i = new Intent();
|
||||
i.setClass(getActivity(), DownloadManagerService.class);
|
||||
@ -85,14 +85,15 @@ public abstract class MissionsFragment extends Fragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
public void onAttach(Context activity) {
|
||||
super.onAttach(activity);
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
getActivity().unbindService(mConnection);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
BIN
app/src/main/res/drawable-hdpi/ic_cast_black_24dp.png
Normal file
After Width: | Height: | Size: 384 B |
BIN
app/src/main/res/drawable-hdpi/ic_cast_white_24dp.png
Normal file
After Width: | Height: | Size: 394 B |
BIN
app/src/main/res/drawable-hdpi/ic_file_download_black_24dp.png
Normal file
After Width: | Height: | Size: 148 B |
BIN
app/src/main/res/drawable-hdpi/ic_file_download_white_24dp.png
Normal file
After Width: | Height: | Size: 163 B |
BIN
app/src/main/res/drawable-hdpi/ic_headset_white_24dp.png
Normal file
After Width: | Height: | Size: 350 B |
BIN
app/src/main/res/drawable-hdpi/ic_rss_feed_black_24dp.png
Normal file
After Width: | Height: | Size: 410 B |
BIN
app/src/main/res/drawable-hdpi/ic_share_white_24dp.png
Normal file
After Width: | Height: | Size: 397 B |
BIN
app/src/main/res/drawable-hdpi/ic_thumb_down_black_24dp.png
Normal file
After Width: | Height: | Size: 269 B |
BIN
app/src/main/res/drawable-hdpi/ic_thumb_down_white_24dp.png
Normal file
After Width: | Height: | Size: 280 B |
BIN
app/src/main/res/drawable-hdpi/ic_thumb_up_black_24dp.png
Normal file
After Width: | Height: | Size: 262 B |
BIN
app/src/main/res/drawable-hdpi/ic_thumb_up_white_24dp.png
Normal file
After Width: | Height: | Size: 277 B |
BIN
app/src/main/res/drawable-mdpi/ic_cast_black_24dp.png
Normal file
After Width: | Height: | Size: 249 B |
BIN
app/src/main/res/drawable-mdpi/ic_cast_white_24dp.png
Normal file
After Width: | Height: | Size: 251 B |
BIN
app/src/main/res/drawable-mdpi/ic_file_download_black_24dp.png
Normal file
After Width: | Height: | Size: 114 B |
BIN
app/src/main/res/drawable-mdpi/ic_file_download_white_24dp.png
Normal file
After Width: | Height: | Size: 116 B |
BIN
app/src/main/res/drawable-mdpi/ic_headset_black_24dp.png
Normal file
After Width: | Height: | Size: 230 B |
BIN
app/src/main/res/drawable-mdpi/ic_headset_white_24dp.png
Normal file
After Width: | Height: | Size: 238 B |
BIN
app/src/main/res/drawable-mdpi/ic_rss_feed_black_24dp.png
Normal file
After Width: | Height: | Size: 286 B |
BIN
app/src/main/res/drawable-mdpi/ic_share_white_24dp.png
Normal file
After Width: | Height: | Size: 268 B |
BIN
app/src/main/res/drawable-mdpi/ic_thumb_down_black_24dp.png
Normal file
After Width: | Height: | Size: 195 B |
BIN
app/src/main/res/drawable-mdpi/ic_thumb_down_white_24dp.png
Normal file
After Width: | Height: | Size: 202 B |
BIN
app/src/main/res/drawable-mdpi/ic_thumb_up_black_24dp.png
Normal file
After Width: | Height: | Size: 181 B |
BIN
app/src/main/res/drawable-mdpi/ic_thumb_up_white_24dp.png
Normal file
After Width: | Height: | Size: 188 B |
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 3.6 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_cast_black_24dp.png
Normal file
After Width: | Height: | Size: 449 B |
BIN
app/src/main/res/drawable-xhdpi/ic_cast_white_24dp.png
Normal file
After Width: | Height: | Size: 460 B |
BIN
app/src/main/res/drawable-xhdpi/ic_file_download_black_24dp.png
Normal file
After Width: | Height: | Size: 144 B |
BIN
app/src/main/res/drawable-xhdpi/ic_file_download_white_24dp.png
Normal file
After Width: | Height: | Size: 157 B |
BIN
app/src/main/res/drawable-xhdpi/ic_headset_black_24dp.png
Normal file
After Width: | Height: | Size: 412 B |
BIN
app/src/main/res/drawable-xhdpi/ic_headset_white_24dp.png
Normal file
After Width: | Height: | Size: 433 B |
BIN
app/src/main/res/drawable-xhdpi/ic_rss_feed_black_24dp.png
Normal file
After Width: | Height: | Size: 532 B |
BIN
app/src/main/res/drawable-xhdpi/ic_share_white_24dp.png
Normal file
After Width: | Height: | Size: 496 B |
BIN
app/src/main/res/drawable-xhdpi/ic_thumb_down_black_24dp.png
Normal file
After Width: | Height: | Size: 281 B |
BIN
app/src/main/res/drawable-xhdpi/ic_thumb_down_white_24dp.png
Normal file
After Width: | Height: | Size: 296 B |
BIN
app/src/main/res/drawable-xhdpi/ic_thumb_up_black_24dp.png
Normal file
After Width: | Height: | Size: 291 B |
BIN
app/src/main/res/drawable-xhdpi/ic_thumb_up_white_24dp.png
Normal file
After Width: | Height: | Size: 307 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_cast_black_24dp.png
Normal file
After Width: | Height: | Size: 649 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_cast_white_24dp.png
Normal file
After Width: | Height: | Size: 666 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_file_download_black_24dp.png
Normal file
After Width: | Height: | Size: 173 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_file_download_white_24dp.png
Normal file
After Width: | Height: | Size: 197 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_headset_black_24dp.png
Normal file
After Width: | Height: | Size: 586 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_headset_white_24dp.png
Normal file
After Width: | Height: | Size: 610 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_rss_feed_black_24dp.png
Normal file
After Width: | Height: | Size: 768 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png
Normal file
After Width: | Height: | Size: 698 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_thumb_down_black_24dp.png
Normal file
After Width: | Height: | Size: 395 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_thumb_down_white_24dp.png
Normal file
After Width: | Height: | Size: 423 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_thumb_up_black_24dp.png
Normal file
After Width: | Height: | Size: 410 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_thumb_up_white_24dp.png
Normal file
After Width: | Height: | Size: 434 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_cast_black_24dp.png
Normal file
After Width: | Height: | Size: 869 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_cast_white_24dp.png
Normal file
After Width: | Height: | Size: 883 B |
After Width: | Height: | Size: 209 B |
After Width: | Height: | Size: 233 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_headset_black_24dp.png
Normal file
After Width: | Height: | Size: 786 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_headset_white_24dp.png
Normal file
After Width: | Height: | Size: 838 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_rss_feed_black_24dp.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png
Normal file
After Width: | Height: | Size: 938 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_thumb_down_black_24dp.png
Normal file
After Width: | Height: | Size: 496 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_thumb_down_white_24dp.png
Normal file
After Width: | Height: | Size: 533 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_thumb_up_black_24dp.png
Normal file
After Width: | Height: | Size: 510 B |