Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
11541310d6
|
@ -5,7 +5,7 @@ READ THIS GUIDELINES CAREFULLY BEFORE CONTRIBUTING.
|
||||||
|
|
||||||
## Crash reporting
|
## Crash reporting
|
||||||
|
|
||||||
Do not report crashes in the GitHub issue tracker. NewPipe has an automated crash report system that will ask you to send a report if a crash occures.
|
Do not report crashes in the GitHub issue tracker. NewPipe has an automated crash report system that will ask you to send a report if a crash occurs.
|
||||||
|
|
||||||
## Issue reporting/feature request
|
## Issue reporting/feature request
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ android {
|
||||||
targetSdkVersion 25
|
targetSdkVersion 25
|
||||||
versionCode 35
|
versionCode 35
|
||||||
versionName "0.9.8"
|
versionName "0.9.8"
|
||||||
|
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
|
@ -31,6 +33,11 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') {
|
||||||
|
exclude module: 'support-annotations'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||||
testCompile 'org.json:json:20160810'
|
testCompile 'org.json:json:20160810'
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.schabi.newpipe.report;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.support.test.filters.LargeTest;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.report.ErrorActivity.ErrorInfo;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented tests for {@link ErrorInfo}
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@LargeTest
|
||||||
|
public class ErrorInfoTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void errorInfo_testParcelable() {
|
||||||
|
ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request", R.string.general_error);
|
||||||
|
// Obtain a Parcel object and write the parcelable object to it:
|
||||||
|
Parcel parcel = Parcel.obtain();
|
||||||
|
info.writeToParcel(parcel, 0);
|
||||||
|
parcel.setDataPosition(0);
|
||||||
|
ErrorInfo infoFromParcel = ErrorInfo.CREATOR.createFromParcel(parcel);
|
||||||
|
|
||||||
|
assertEquals(UserAction.USER_REPORT, infoFromParcel.userAction);
|
||||||
|
assertEquals("youtube", infoFromParcel.serviceName);
|
||||||
|
assertEquals("request", infoFromParcel.request);
|
||||||
|
assertEquals(R.string.general_error, infoFromParcel.message);
|
||||||
|
|
||||||
|
parcel.recycle();
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ import org.acra.sender.ReportSenderFactory;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.report.AcraReportSenderFactory;
|
import org.schabi.newpipe.report.AcraReportSenderFactory;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.report.UserAction;
|
||||||
import org.schabi.newpipe.settings.SettingsActivity;
|
import org.schabi.newpipe.settings.SettingsActivity;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
|
@ -59,7 +60,7 @@ public class App extends Application {
|
||||||
} catch(ACRAConfigurationException ace) {
|
} catch(ACRAConfigurationException ace) {
|
||||||
ace.printStackTrace();
|
ace.printStackTrace();
|
||||||
ErrorActivity.reportError(this, ace, null, null,
|
ErrorActivity.reportError(this, ace, null, null,
|
||||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,"none",
|
ErrorActivity.ErrorInfo.make(UserAction.SEARCHED,"none",
|
||||||
"Could not initialize ACRA crash report", R.string.app_ui_crash));
|
"Could not initialize ACRA crash report", R.string.app_ui_crash));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.report.UserAction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Christian Schabesberger on 01.08.16.
|
* Created by Christian Schabesberger on 01.08.16.
|
||||||
|
@ -49,7 +50,7 @@ public class ImageErrorLoadingListener implements ImageLoadingListener {
|
||||||
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
||||||
ErrorActivity.reportError(context,
|
ErrorActivity.reportError(context,
|
||||||
failReason.getCause(), null, rootView,
|
failReason.getCause(), null, rootView,
|
||||||
ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
|
ErrorActivity.ErrorInfo.make(UserAction.LOAD_IMAGE,
|
||||||
NewPipe.getNameOfService(serviceId), imageUri,
|
NewPipe.getNameOfService(serviceId), imageUri,
|
||||||
R.string.could_not_load_image));
|
R.string.could_not_load_image));
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,6 @@ import us.shandian.giga.service.DownloadManagerService;
|
||||||
import us.shandian.giga.ui.fragment.AllMissionsFragment;
|
import us.shandian.giga.ui.fragment.AllMissionsFragment;
|
||||||
import us.shandian.giga.ui.fragment.MissionsFragment;
|
import us.shandian.giga.ui.fragment.MissionsFragment;
|
||||||
import us.shandian.giga.util.CrashHandler;
|
import us.shandian.giga.util.CrashHandler;
|
||||||
import us.shandian.giga.util.Utility;
|
|
||||||
|
|
||||||
public class DownloadActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
|
public class DownloadActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
|
||||||
|
|
||||||
|
@ -125,11 +124,11 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O
|
||||||
// Create the view
|
// Create the view
|
||||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
View v = inflater.inflate(R.layout.dialog_url, null);
|
View v = inflater.inflate(R.layout.dialog_url, null);
|
||||||
final EditText name = Utility.findViewById(v, R.id.file_name);
|
final EditText name = (EditText) v.findViewById(R.id.file_name);
|
||||||
final TextView tCount = Utility.findViewById(v, R.id.threads_count);
|
final TextView tCount = (TextView) v.findViewById(R.id.threads_count);
|
||||||
final SeekBar threads = Utility.findViewById(v, R.id.threads);
|
final SeekBar threads = (SeekBar) v.findViewById(R.id.threads);
|
||||||
final Toolbar toolbar = Utility.findViewById(v, R.id.toolbar);
|
final Toolbar toolbar = (Toolbar) v.findViewById(R.id.toolbar);
|
||||||
final RadioButton audioButton = (RadioButton) Utility.findViewById(v, R.id.audio_button);
|
final RadioButton audioButton = (RadioButton) v.findViewById(R.id.audio_button);
|
||||||
|
|
||||||
|
|
||||||
threads.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
threads.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||||
|
|
|
@ -253,7 +253,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* #143 #44 #42 #22: make shure that the filename does not contain illegal chars.
|
* #143 #44 #42 #22: make sure that the filename does not contain illegal chars.
|
||||||
* This should fix some of the "cannot download" problems.
|
* This should fix some of the "cannot download" problems.
|
||||||
*/
|
*/
|
||||||
private String createFileName(String fileName) {
|
private String createFileName(String fileName) {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||||
import org.schabi.newpipe.fragments.BaseFragment;
|
import org.schabi.newpipe.fragments.BaseFragment;
|
||||||
|
import org.schabi.newpipe.fragments.search.OnScrollBelowItemsListener;
|
||||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||||
import org.schabi.newpipe.info_list.InfoListAdapter;
|
import org.schabi.newpipe.info_list.InfoListAdapter;
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
|
@ -213,7 +214,7 @@ public class ChannelFragment extends BaseFragment implements ChannelExtractorWor
|
||||||
|
|
||||||
channelVideosList.setLayoutManager(new LinearLayoutManager(activity));
|
channelVideosList.setLayoutManager(new LinearLayoutManager(activity));
|
||||||
if (infoListAdapter == null) {
|
if (infoListAdapter == null) {
|
||||||
infoListAdapter = new InfoListAdapter(activity, rootView);
|
infoListAdapter = new InfoListAdapter(activity);
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
ArrayList<InfoItem> serializable = (ArrayList<InfoItem>) savedInstanceState.getSerializable(INFO_LIST_KEY);
|
ArrayList<InfoItem> serializable = (ArrayList<InfoItem>) savedInstanceState.getSerializable(INFO_LIST_KEY);
|
||||||
|
@ -245,23 +246,12 @@ public class ChannelFragment extends BaseFragment implements ChannelExtractorWor
|
||||||
});
|
});
|
||||||
|
|
||||||
channelVideosList.clearOnScrollListeners();
|
channelVideosList.clearOnScrollListeners();
|
||||||
channelVideosList.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
channelVideosList.addOnScrollListener(new OnScrollBelowItemsListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
public void onScrolledDown(RecyclerView recyclerView) {
|
||||||
int pastVisiblesItems, visibleItemCount, totalItemCount;
|
if ((currentChannelWorker == null || !currentChannelWorker.isRunning()) && hasNextPage && !isLoading.get()) {
|
||||||
super.onScrolled(recyclerView, dx, dy);
|
pageNumber++;
|
||||||
//check for scroll down
|
loadMoreVideos();
|
||||||
if (dy > 0) {
|
|
||||||
LinearLayoutManager layoutManager = (LinearLayoutManager) channelVideosList.getLayoutManager();
|
|
||||||
|
|
||||||
visibleItemCount = layoutManager.getChildCount();
|
|
||||||
totalItemCount = layoutManager.getItemCount();
|
|
||||||
pastVisiblesItems = layoutManager.findFirstVisibleItemPosition();
|
|
||||||
|
|
||||||
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount && (currentChannelWorker == null || !currentChannelWorker.isRunning()) && hasNextPage && !isLoading.get()) {
|
|
||||||
pageNumber++;
|
|
||||||
loadMoreVideos();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -67,12 +67,12 @@ class ActionBarHandler {
|
||||||
|
|
||||||
public void setupStreamList(final List<VideoStream> videoStreams, Spinner toolbarSpinner) {
|
public void setupStreamList(final List<VideoStream> videoStreams, Spinner toolbarSpinner) {
|
||||||
if (activity == null) return;
|
if (activity == null) return;
|
||||||
selectedVideoStream = 0;
|
|
||||||
|
|
||||||
int defaultResolutionIndex = Utils.getDefaultResolution(activity, videoStreams);
|
selectedVideoStream = Utils.getDefaultResolution(activity, videoStreams);
|
||||||
|
|
||||||
boolean isExternalPlayerEnabled = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(activity.getString(R.string.use_external_video_player_key), false);
|
boolean isExternalPlayerEnabled = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(activity.getString(R.string.use_external_video_player_key), false);
|
||||||
toolbarSpinner.setAdapter(new SpinnerToolbarAdapter(activity, videoStreams, isExternalPlayerEnabled));
|
toolbarSpinner.setAdapter(new SpinnerToolbarAdapter(activity, videoStreams, isExternalPlayerEnabled));
|
||||||
toolbarSpinner.setSelection(defaultResolutionIndex);
|
toolbarSpinner.setSelection(selectedVideoStream);
|
||||||
toolbarSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
toolbarSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
package org.schabi.newpipe.fragments.detail;
|
package org.schabi.newpipe.fragments.detail;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.FloatRange;
|
import android.support.annotation.FloatRange;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
@ -15,6 +20,7 @@ import android.support.v4.view.animation.FastOutSlowInInterpolator;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
|
import android.text.Spanned;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -26,7 +32,7 @@ import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
@ -55,6 +61,7 @@ import org.schabi.newpipe.player.MainVideoPlayer;
|
||||||
import org.schabi.newpipe.player.PlayVideoActivity;
|
import org.schabi.newpipe.player.PlayVideoActivity;
|
||||||
import org.schabi.newpipe.player.PopupVideoPlayer;
|
import org.schabi.newpipe.player.PopupVideoPlayer;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.report.UserAction;
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
|
@ -105,6 +112,10 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
private boolean showRelatedStreams;
|
private boolean showRelatedStreams;
|
||||||
private boolean wasRelatedStreamsExpanded = false;
|
private boolean wasRelatedStreamsExpanded = false;
|
||||||
|
|
||||||
|
private Handler uiHandler;
|
||||||
|
private Handler backgroundHandler;
|
||||||
|
private HandlerThread backgroundHandlerThread;
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Views
|
// Views
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -112,9 +123,9 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
private Spinner spinnerToolbar;
|
private Spinner spinnerToolbar;
|
||||||
|
|
||||||
private ParallaxScrollView parallaxScrollRootView;
|
private ParallaxScrollView parallaxScrollRootView;
|
||||||
private RelativeLayout contentRootLayoutHiding;
|
private LinearLayout contentRootLayoutHiding;
|
||||||
|
|
||||||
private Button thumbnailBackgroundButton;
|
private View thumbnailBackgroundButton;
|
||||||
private ImageView thumbnailImageView;
|
private ImageView thumbnailImageView;
|
||||||
private ImageView thumbnailPlayButton;
|
private ImageView thumbnailPlayButton;
|
||||||
|
|
||||||
|
@ -126,12 +137,11 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
private TextView detailControlsBackground;
|
private TextView detailControlsBackground;
|
||||||
private TextView detailControlsPopup;
|
private TextView detailControlsPopup;
|
||||||
|
|
||||||
private RelativeLayout videoDescriptionRootLayout;
|
private LinearLayout videoDescriptionRootLayout;
|
||||||
private TextView videoUploadDateView;
|
private TextView videoUploadDateView;
|
||||||
private TextView videoDescriptionView;
|
private TextView videoDescriptionView;
|
||||||
|
|
||||||
private View uploaderRootLayout;
|
private View uploaderRootLayout;
|
||||||
private Button uploaderButton;
|
|
||||||
private TextView uploaderTextView;
|
private TextView uploaderTextView;
|
||||||
private ImageView uploaderThumb;
|
private ImageView uploaderThumb;
|
||||||
|
|
||||||
|
@ -142,7 +152,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
private TextView thumbsDisabledTextView;
|
private TextView thumbsDisabledTextView;
|
||||||
|
|
||||||
private TextView nextStreamTitle;
|
private TextView nextStreamTitle;
|
||||||
private RelativeLayout relatedStreamRootLayout;
|
private LinearLayout relatedStreamRootLayout;
|
||||||
private LinearLayout relatedStreamsView;
|
private LinearLayout relatedStreamsView;
|
||||||
private ImageButton relatedStreamExpandButton;
|
private ImageButton relatedStreamExpandButton;
|
||||||
|
|
||||||
|
@ -194,6 +204,16 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
thousand = getString(R.string.short_thousand);
|
thousand = getString(R.string.short_thousand);
|
||||||
million = getString(R.string.short_million);
|
million = getString(R.string.short_million);
|
||||||
billion = getString(R.string.short_billion);
|
billion = getString(R.string.short_billion);
|
||||||
|
|
||||||
|
if (uiHandler == null) {
|
||||||
|
uiHandler = new Handler(Looper.getMainLooper(), new UICallback());
|
||||||
|
}
|
||||||
|
if (backgroundHandler == null) {
|
||||||
|
HandlerThread handlerThread = new HandlerThread("VideoDetailFragment-BG");
|
||||||
|
handlerThread.start();
|
||||||
|
backgroundHandlerThread = handlerThread;
|
||||||
|
backgroundHandler = new Handler(handlerThread.getLooper(), new BackgroundCallback(uiHandler, getContext()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -241,6 +261,11 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
if (backgroundHandlerThread != null) {
|
||||||
|
backgroundHandlerThread.quit();
|
||||||
|
}
|
||||||
|
backgroundHandlerThread = null;
|
||||||
|
backgroundHandler = null;
|
||||||
PreferenceManager.getDefaultSharedPreferences(activity).unregisterOnSharedPreferenceChangeListener(this);
|
PreferenceManager.getDefaultSharedPreferences(activity).unregisterOnSharedPreferenceChangeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,7 +297,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
videoUploadDateView = null;
|
videoUploadDateView = null;
|
||||||
videoDescriptionView = null;
|
videoDescriptionView = null;
|
||||||
|
|
||||||
uploaderButton = null;
|
uploaderRootLayout = null;
|
||||||
uploaderTextView = null;
|
uploaderTextView = null;
|
||||||
uploaderThumb = null;
|
uploaderThumb = null;
|
||||||
|
|
||||||
|
@ -353,10 +378,14 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
case R.id.detail_controls_popup:
|
case R.id.detail_controls_popup:
|
||||||
openInPopup();
|
openInPopup();
|
||||||
break;
|
break;
|
||||||
case R.id.detail_uploader_button:
|
case R.id.detail_uploader_root_layout:
|
||||||
NavigationHelper.openChannelFragment(getFragmentManager(), currentStreamInfo.service_id, currentStreamInfo.channel_url, currentStreamInfo.uploader);
|
if (currentStreamInfo.channel_url == null || currentStreamInfo.channel_url.isEmpty()) {
|
||||||
|
Log.w(TAG, "Can't open channel because we got no channel URL");
|
||||||
|
} else {
|
||||||
|
NavigationHelper.openChannelFragment(getFragmentManager(), currentStreamInfo.service_id, currentStreamInfo.channel_url, currentStreamInfo.uploader);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case R.id.detail_thumbnail_background_button:
|
case R.id.detail_thumbnail_root_layout:
|
||||||
playVideo(currentStreamInfo);
|
playVideo(currentStreamInfo);
|
||||||
break;
|
break;
|
||||||
case R.id.detail_title_root_layout:
|
case R.id.detail_title_root_layout:
|
||||||
|
@ -484,12 +513,11 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
|
|
||||||
parallaxScrollRootView = (ParallaxScrollView) rootView.findViewById(R.id.detail_main_content);
|
parallaxScrollRootView = (ParallaxScrollView) rootView.findViewById(R.id.detail_main_content);
|
||||||
|
|
||||||
//thumbnailRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_thumbnail_root_layout);
|
thumbnailBackgroundButton = rootView.findViewById(R.id.detail_thumbnail_root_layout);
|
||||||
thumbnailBackgroundButton = (Button) rootView.findViewById(R.id.detail_thumbnail_background_button);
|
|
||||||
thumbnailImageView = (ImageView) rootView.findViewById(R.id.detail_thumbnail_image_view);
|
thumbnailImageView = (ImageView) rootView.findViewById(R.id.detail_thumbnail_image_view);
|
||||||
thumbnailPlayButton = (ImageView) rootView.findViewById(R.id.detail_thumbnail_play_button);
|
thumbnailPlayButton = (ImageView) rootView.findViewById(R.id.detail_thumbnail_play_button);
|
||||||
|
|
||||||
contentRootLayoutHiding = (RelativeLayout) rootView.findViewById(R.id.detail_content_root_hiding);
|
contentRootLayoutHiding = (LinearLayout) rootView.findViewById(R.id.detail_content_root_hiding);
|
||||||
|
|
||||||
videoTitleRoot = rootView.findViewById(R.id.detail_title_root_layout);
|
videoTitleRoot = rootView.findViewById(R.id.detail_title_root_layout);
|
||||||
videoTitleTextView = (TextView) rootView.findViewById(R.id.detail_video_title_view);
|
videoTitleTextView = (TextView) rootView.findViewById(R.id.detail_video_title_view);
|
||||||
|
@ -499,7 +527,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
detailControlsBackground = (TextView) rootView.findViewById(R.id.detail_controls_background);
|
detailControlsBackground = (TextView) rootView.findViewById(R.id.detail_controls_background);
|
||||||
detailControlsPopup = (TextView) rootView.findViewById(R.id.detail_controls_popup);
|
detailControlsPopup = (TextView) rootView.findViewById(R.id.detail_controls_popup);
|
||||||
|
|
||||||
videoDescriptionRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_description_root_layout);
|
videoDescriptionRootLayout = (LinearLayout) rootView.findViewById(R.id.detail_description_root_layout);
|
||||||
videoUploadDateView = (TextView) rootView.findViewById(R.id.detail_upload_date_view);
|
videoUploadDateView = (TextView) rootView.findViewById(R.id.detail_upload_date_view);
|
||||||
videoDescriptionView = (TextView) rootView.findViewById(R.id.detail_description_view);
|
videoDescriptionView = (TextView) rootView.findViewById(R.id.detail_description_view);
|
||||||
|
|
||||||
|
@ -511,19 +539,19 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
thumbsDisabledTextView = (TextView) rootView.findViewById(R.id.detail_thumbs_disabled_view);
|
thumbsDisabledTextView = (TextView) rootView.findViewById(R.id.detail_thumbs_disabled_view);
|
||||||
|
|
||||||
uploaderRootLayout = rootView.findViewById(R.id.detail_uploader_root_layout);
|
uploaderRootLayout = rootView.findViewById(R.id.detail_uploader_root_layout);
|
||||||
uploaderButton = (Button) rootView.findViewById(R.id.detail_uploader_button);
|
|
||||||
uploaderTextView = (TextView) rootView.findViewById(R.id.detail_uploader_text_view);
|
uploaderTextView = (TextView) rootView.findViewById(R.id.detail_uploader_text_view);
|
||||||
uploaderThumb = (ImageView) rootView.findViewById(R.id.detail_uploader_thumbnail_view);
|
uploaderThumb = (ImageView) rootView.findViewById(R.id.detail_uploader_thumbnail_view);
|
||||||
|
|
||||||
relatedStreamRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_related_streams_root_layout);
|
relatedStreamRootLayout = (LinearLayout) rootView.findViewById(R.id.detail_related_streams_root_layout);
|
||||||
nextStreamTitle = (TextView) rootView.findViewById(R.id.detail_next_stream_title);
|
nextStreamTitle = (TextView) rootView.findViewById(R.id.detail_next_stream_title);
|
||||||
relatedStreamsView = (LinearLayout) rootView.findViewById(R.id.detail_related_streams_view);
|
relatedStreamsView = (LinearLayout) rootView.findViewById(R.id.detail_related_streams_view);
|
||||||
|
|
||||||
relatedStreamExpandButton = ((ImageButton) rootView.findViewById(R.id.detail_related_streams_expand));
|
relatedStreamExpandButton = ((ImageButton) rootView.findViewById(R.id.detail_related_streams_expand));
|
||||||
|
|
||||||
actionBarHandler = new ActionBarHandler(activity);
|
actionBarHandler = new ActionBarHandler(activity);
|
||||||
videoDescriptionView.setMovementMethod(LinkMovementMethod.getInstance());
|
videoDescriptionView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
|
||||||
infoItemBuilder = new InfoItemBuilder(activity, rootView.findViewById(android.R.id.content));
|
infoItemBuilder = new InfoItemBuilder(activity);
|
||||||
|
|
||||||
setHeightThumbnail();
|
setHeightThumbnail();
|
||||||
}
|
}
|
||||||
|
@ -539,7 +567,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
});
|
});
|
||||||
|
|
||||||
videoTitleRoot.setOnClickListener(this);
|
videoTitleRoot.setOnClickListener(this);
|
||||||
uploaderButton.setOnClickListener(this);
|
uploaderRootLayout.setOnClickListener(this);
|
||||||
thumbnailBackgroundButton.setOnClickListener(this);
|
thumbnailBackgroundButton.setOnClickListener(this);
|
||||||
detailControlsBackground.setOnClickListener(this);
|
detailControlsBackground.setOnClickListener(this);
|
||||||
detailControlsPopup.setOnClickListener(this);
|
detailControlsPopup.setOnClickListener(this);
|
||||||
|
@ -551,7 +579,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
imageLoader.displayImage(info.thumbnail_url, thumbnailImageView, displayImageOptions, new SimpleImageLoadingListener() {
|
imageLoader.displayImage(info.thumbnail_url, thumbnailImageView, displayImageOptions, new SimpleImageLoadingListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
||||||
ErrorActivity.reportError(activity, failReason.getCause(), null, activity.findViewById(android.R.id.content), ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE, NewPipe.getNameOfService(currentStreamInfo.service_id), imageUri, R.string.could_not_load_thumbnails));
|
ErrorActivity.reportError(activity, failReason.getCause(), null, activity.findViewById(android.R.id.content), ErrorActivity.ErrorInfo.make(UserAction.LOAD_IMAGE, NewPipe.getNameOfService(currentStreamInfo.service_id), imageUri, R.string.could_not_load_thumbnails));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else thumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
|
} else thumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
|
||||||
|
@ -739,8 +767,17 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
// Get url from the new top
|
// Get url from the new top
|
||||||
StackItem peek = stack.peek();
|
StackItem peek = stack.peek();
|
||||||
|
|
||||||
if (peek.getInfo() != null) selectAndHandleInfo(peek.getInfo());
|
if (peek.getInfo() != null) {
|
||||||
else selectAndLoadVideo(0, peek.getUrl(), !TextUtils.isEmpty(peek.getTitle()) ? peek.getTitle() : "");
|
final StreamInfo streamInfo = peek.getInfo();
|
||||||
|
getActivity().runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
selectAndHandleInfo(streamInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
selectAndLoadVideo(0, peek.getUrl(), !TextUtils.isEmpty(peek.getTitle()) ? peek.getTitle() : "");
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -848,7 +885,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleStreamInfo(@NonNull StreamInfo info, boolean fromNetwork) {
|
private void handleStreamInfo(@NonNull final StreamInfo info, boolean fromNetwork) {
|
||||||
if (DEBUG) Log.d(TAG, "handleStreamInfo() called with: info = [" + info + "]");
|
if (DEBUG) Log.d(TAG, "handleStreamInfo() called with: info = [" + info + "]");
|
||||||
currentStreamInfo = info;
|
currentStreamInfo = info;
|
||||||
selectVideo(info.service_id, info.webpage_url, info.title);
|
selectVideo(info.service_id, info.webpage_url, info.title);
|
||||||
|
@ -862,7 +899,6 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(info.uploader)) uploaderTextView.setText(info.uploader);
|
if (!TextUtils.isEmpty(info.uploader)) uploaderTextView.setText(info.uploader);
|
||||||
uploaderTextView.setVisibility(!TextUtils.isEmpty(info.uploader) ? View.VISIBLE : View.GONE);
|
uploaderTextView.setVisibility(!TextUtils.isEmpty(info.uploader) ? View.VISIBLE : View.GONE);
|
||||||
uploaderButton.setVisibility(!TextUtils.isEmpty(info.channel_url) ? View.VISIBLE : View.GONE);
|
|
||||||
uploaderThumb.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy));
|
uploaderThumb.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy));
|
||||||
|
|
||||||
if (info.view_count >= 0) videoCountView.setText(Localization.localizeViewCount(info.view_count, activity));
|
if (info.view_count >= 0) videoCountView.setText(Localization.localizeViewCount(info.view_count, activity));
|
||||||
|
@ -887,14 +923,8 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
thumbsUpImageView.setVisibility(info.like_count >= 0 ? View.VISIBLE : View.GONE);
|
thumbsUpImageView.setVisibility(info.like_count >= 0 ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(info.upload_date)) videoUploadDateView.setText(Localization.localizeDate(info.upload_date, activity));
|
|
||||||
videoUploadDateView.setVisibility(!TextUtils.isEmpty(info.upload_date) ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(info.description)) { //noinspection deprecation
|
|
||||||
videoDescriptionView.setText(Build.VERSION.SDK_INT >= 24 ? Html.fromHtml(info.description, 0) : Html.fromHtml(info.description));
|
|
||||||
}
|
|
||||||
videoDescriptionView.setVisibility(!TextUtils.isEmpty(info.description) ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
|
videoDescriptionView.setVisibility(View.GONE);
|
||||||
videoDescriptionRootLayout.setVisibility(View.GONE);
|
videoDescriptionRootLayout.setVisibility(View.GONE);
|
||||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
|
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
|
||||||
videoTitleToggleArrow.setVisibility(View.VISIBLE);
|
videoTitleToggleArrow.setVisibility(View.VISIBLE);
|
||||||
|
@ -908,9 +938,32 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
toggleExpandRelatedVideos(currentStreamInfo);
|
toggleExpandRelatedVideos(currentStreamInfo);
|
||||||
wasRelatedStreamsExpanded = false;
|
wasRelatedStreamsExpanded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTitleToUrl(info.webpage_url, info.title);
|
setTitleToUrl(info.webpage_url, info.title);
|
||||||
setStreamInfoToUrl(info.webpage_url, info);
|
setStreamInfoToUrl(info.webpage_url, info);
|
||||||
|
|
||||||
|
prepareDescription(info.description);
|
||||||
|
prepareUploadDate(info.upload_date);
|
||||||
|
|
||||||
|
if (autoPlayEnabled) {
|
||||||
|
playVideo(info);
|
||||||
|
// Only auto play in the first open
|
||||||
|
autoPlayEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareUploadDate(final String uploadDate) {
|
||||||
|
// Hide until date is prepared or forever if no date is supplied
|
||||||
|
videoUploadDateView.setVisibility(View.GONE);
|
||||||
|
if (!TextUtils.isEmpty(uploadDate)) {
|
||||||
|
backgroundHandler.sendMessage(Message.obtain(backgroundHandler, BackgroundCallback.MESSAGE_UPLOADER_DATE, uploadDate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareDescription(final String descriptionHtml) {
|
||||||
|
// Send the unparsed description to the handler as a message
|
||||||
|
if (!TextUtils.isEmpty(descriptionHtml)) {
|
||||||
|
backgroundHandler.sendMessage(Message.obtain(backgroundHandler, BackgroundCallback.MESSAGE_DESCRIPTION, descriptionHtml));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void playVideo(StreamInfo info) {
|
public void playVideo(StreamInfo info) {
|
||||||
|
@ -987,10 +1040,8 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
int height = isPortrait ? (int) (getResources().getDisplayMetrics().widthPixels / (16.0f / 9.0f))
|
int height = isPortrait ? (int) (getResources().getDisplayMetrics().widthPixels / (16.0f / 9.0f))
|
||||||
: (int) (getResources().getDisplayMetrics().heightPixels / 2f);
|
: (int) (getResources().getDisplayMetrics().heightPixels / 2f);
|
||||||
thumbnailImageView.setScaleType(isPortrait ? ImageView.ScaleType.CENTER_CROP : ImageView.ScaleType.FIT_CENTER);
|
thumbnailImageView.setScaleType(isPortrait ? ImageView.ScaleType.CENTER_CROP : ImageView.ScaleType.FIT_CENTER);
|
||||||
thumbnailImageView.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, height));
|
thumbnailImageView.setLayoutParams(new FrameLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, height));
|
||||||
thumbnailImageView.setMinimumHeight(height);
|
thumbnailImageView.setMinimumHeight(height);
|
||||||
thumbnailBackgroundButton.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, height));
|
|
||||||
thumbnailBackgroundButton.setMinimumHeight(height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getShortCount(Long viewCount) {
|
public String getShortCount(Long viewCount) {
|
||||||
|
@ -1032,9 +1083,6 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
.setStartDelay((long) (duration * .8f) + delay).setDuration(duration).setInterpolator(new FastOutSlowInInterpolator()).start();
|
.setStartDelay((long) (duration * .8f) + delay).setDuration(duration).setInterpolator(new FastOutSlowInInterpolator()).start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// OnStreamInfoReceivedListener callbacks
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
private void setErrorImage(final int imageResource) {
|
private void setErrorImage(final int imageResource) {
|
||||||
if (thumbnailImageView == null || activity == null) return;
|
if (thumbnailImageView == null || activity == null) return;
|
||||||
|
@ -1055,6 +1103,10 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
currentStreamInfo = null;
|
currentStreamInfo = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// OnStreamInfoReceivedListener callbacks
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(StreamInfo info) {
|
public void onReceive(StreamInfo info) {
|
||||||
if (DEBUG) Log.d(TAG, "onReceive() called with: info = [" + info + "]");
|
if (DEBUG) Log.d(TAG, "onReceive() called with: info = [" + info + "]");
|
||||||
|
@ -1062,15 +1114,8 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
|
|
||||||
handleStreamInfo(info, true);
|
handleStreamInfo(info, true);
|
||||||
showContentWithAnimation(300, 0, 0);
|
showContentWithAnimation(300, 0, 0);
|
||||||
|
|
||||||
animateView(loadingProgressBar, false, 200);
|
animateView(loadingProgressBar, false, 200);
|
||||||
|
|
||||||
if (autoPlayEnabled) {
|
|
||||||
playVideo(info);
|
|
||||||
// Only auto play in the first open
|
|
||||||
autoPlayEnabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamInfoCache.getInstance().putInfo(info);
|
StreamInfoCache.getInstance().putInfo(info);
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
|
|
||||||
|
@ -1126,4 +1171,69 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
public void onUnrecoverableError(Exception exception) {
|
public void onUnrecoverableError(Exception exception) {
|
||||||
activity.finish();
|
activity.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Background handling
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private static class BackgroundCallback implements Handler.Callback {
|
||||||
|
private static final int MESSAGE_DESCRIPTION = 1;
|
||||||
|
public static final int MESSAGE_UPLOADER_DATE = 2;
|
||||||
|
private final Handler uiHandler;
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
BackgroundCallback(Handler uiHandler, Context context) {
|
||||||
|
this.uiHandler = uiHandler;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handleMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case MESSAGE_DESCRIPTION:
|
||||||
|
handleDescription((String) msg.obj);
|
||||||
|
return true;
|
||||||
|
case MESSAGE_UPLOADER_DATE:
|
||||||
|
handleUploadDate((String) msg.obj);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUploadDate(String uploadDate) {
|
||||||
|
String localizedDate = Localization.localizeDate(uploadDate, context);
|
||||||
|
uiHandler.sendMessage(Message.obtain(uiHandler, MESSAGE_UPLOADER_DATE, localizedDate));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDescription(String description) {
|
||||||
|
Spanned parsedDescription;
|
||||||
|
if (TextUtils.isEmpty(description)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= 24) {
|
||||||
|
parsedDescription = Html.fromHtml(description, 0);
|
||||||
|
} else {
|
||||||
|
//noinspection deprecation
|
||||||
|
parsedDescription = Html.fromHtml(description);
|
||||||
|
}
|
||||||
|
uiHandler.sendMessage(Message.obtain(uiHandler, MESSAGE_DESCRIPTION, parsedDescription));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class UICallback implements Handler.Callback {
|
||||||
|
@Override
|
||||||
|
public boolean handleMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case BackgroundCallback.MESSAGE_DESCRIPTION:
|
||||||
|
videoDescriptionView.setText((Spanned) msg.obj);
|
||||||
|
videoDescriptionView.setVisibility(View.VISIBLE);
|
||||||
|
return true;
|
||||||
|
case BackgroundCallback.MESSAGE_UPLOADER_DATE:
|
||||||
|
videoUploadDateView.setText((String) msg.obj);
|
||||||
|
videoUploadDateView.setVisibility(View.VISIBLE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.schabi.newpipe.fragments.search;
|
||||||
|
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recycler view scroll listener which calls the method {@link #onScrolledDown(RecyclerView)}
|
||||||
|
* if the view is scrolled below the last item.
|
||||||
|
*/
|
||||||
|
public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy);
|
||||||
|
//check for scroll down
|
||||||
|
if (dy > 0) {
|
||||||
|
int pastVisibleItems, visibleItemCount, totalItemCount;
|
||||||
|
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
|
||||||
|
visibleItemCount = recyclerView.getLayoutManager().getChildCount();
|
||||||
|
totalItemCount = recyclerView.getLayoutManager().getItemCount();
|
||||||
|
pastVisibleItems = layoutManager.findFirstVisibleItemPosition();
|
||||||
|
if ((visibleItemCount + pastVisibleItems) >= totalItemCount) {
|
||||||
|
onScrolledDown(recyclerView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the recycler view is scrolled below the last item.
|
||||||
|
* @param recyclerView the recycler view
|
||||||
|
*/
|
||||||
|
public abstract void onScrolledDown(RecyclerView recyclerView);
|
||||||
|
}
|
|
@ -212,7 +212,7 @@ public class SearchFragment extends BaseFragment implements SuggestionWorker.OnS
|
||||||
resultRecyclerView.setLayoutManager(new LinearLayoutManager(activity));
|
resultRecyclerView.setLayoutManager(new LinearLayoutManager(activity));
|
||||||
|
|
||||||
if (infoListAdapter == null) {
|
if (infoListAdapter == null) {
|
||||||
infoListAdapter = new InfoListAdapter(getActivity(), getActivity().findViewById(android.R.id.content));
|
infoListAdapter = new InfoListAdapter(getActivity());
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
ArrayList<InfoItem> serializable = (ArrayList<InfoItem>) savedInstanceState.getSerializable(INFO_LIST_KEY);
|
ArrayList<InfoItem> serializable = (ArrayList<InfoItem>) savedInstanceState.getSerializable(INFO_LIST_KEY);
|
||||||
|
@ -242,33 +242,24 @@ public class SearchFragment extends BaseFragment implements SuggestionWorker.OnS
|
||||||
protected void initListeners() {
|
protected void initListeners() {
|
||||||
super.initListeners();
|
super.initListeners();
|
||||||
resultRecyclerView.clearOnScrollListeners();
|
resultRecyclerView.clearOnScrollListeners();
|
||||||
resultRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
resultRecyclerView.addOnScrollListener(new OnScrollBelowItemsListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
public void onScrolledDown(RecyclerView recyclerView) {
|
||||||
int pastVisiblesItems, visibleItemCount, totalItemCount;
|
if(!isLoading.get()) {
|
||||||
super.onScrolled(recyclerView, dx, dy);
|
pageNumber++;
|
||||||
//check for scroll down
|
recyclerView.post(new Runnable() {
|
||||||
if (dy > 0) {
|
@Override
|
||||||
LinearLayoutManager layoutManager = (LinearLayoutManager) resultRecyclerView.getLayoutManager();
|
public void run() {
|
||||||
visibleItemCount = resultRecyclerView.getLayoutManager().getChildCount();
|
infoListAdapter.showFooter(true);
|
||||||
totalItemCount = resultRecyclerView.getLayoutManager().getItemCount();
|
}
|
||||||
pastVisiblesItems = layoutManager.findFirstVisibleItemPosition();
|
});
|
||||||
|
search(searchQuery, pageNumber);
|
||||||
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount && !isLoading.get()) {
|
|
||||||
pageNumber++;
|
|
||||||
recyclerView.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
infoListAdapter.showFooter(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
search(searchQuery, pageNumber);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void reloadContent() {
|
protected void reloadContent() {
|
||||||
if (DEBUG) Log.d(TAG, "reloadContent() called");
|
if (DEBUG) Log.d(TAG, "reloadContent() called");
|
||||||
|
|
|
@ -31,8 +31,7 @@ import de.hdodenhof.circleimageview.CircleImageView;
|
||||||
public class ChannelInfoItemHolder extends InfoItemHolder {
|
public class ChannelInfoItemHolder extends InfoItemHolder {
|
||||||
public final CircleImageView itemThumbnailView;
|
public final CircleImageView itemThumbnailView;
|
||||||
public final TextView itemChannelTitleView;
|
public final TextView itemChannelTitleView;
|
||||||
public final TextView itemSubscriberCountView;
|
public final TextView itemAdditionalDetailView;
|
||||||
public final TextView itemVideoCountView;
|
|
||||||
public final TextView itemChannelDescriptionView;
|
public final TextView itemChannelDescriptionView;
|
||||||
|
|
||||||
public final View itemRoot;
|
public final View itemRoot;
|
||||||
|
@ -42,8 +41,7 @@ public class ChannelInfoItemHolder extends InfoItemHolder {
|
||||||
itemRoot = v.findViewById(R.id.itemRoot);
|
itemRoot = v.findViewById(R.id.itemRoot);
|
||||||
itemThumbnailView = (CircleImageView) v.findViewById(R.id.itemThumbnailView);
|
itemThumbnailView = (CircleImageView) v.findViewById(R.id.itemThumbnailView);
|
||||||
itemChannelTitleView = (TextView) v.findViewById(R.id.itemChannelTitleView);
|
itemChannelTitleView = (TextView) v.findViewById(R.id.itemChannelTitleView);
|
||||||
itemSubscriberCountView = (TextView) v.findViewById(R.id.itemSubscriberCountView);
|
itemAdditionalDetailView = (TextView) v.findViewById(R.id.itemAdditionalDetails);
|
||||||
itemVideoCountView = (TextView) v.findViewById(R.id.itemVideoCountView);
|
|
||||||
itemChannelDescriptionView = (TextView) v.findViewById(R.id.itemChannelDescriptionView);
|
itemChannelDescriptionView = (TextView) v.findViewById(R.id.itemChannelDescriptionView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@ import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||||
import org.schabi.newpipe.extractor.stream_info.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream_info.StreamInfoItem;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Christian Schabesberger on 26.09.16.
|
* Created by Christian Schabesberger on 26.09.16.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -54,18 +56,35 @@ public class InfoItemBuilder {
|
||||||
void selected(int serviceId, String url, String title);
|
void selected(int serviceId, String url, String title);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Context mContext = null;
|
|
||||||
private LayoutInflater inflater;
|
|
||||||
private View rootView = null;
|
|
||||||
private ImageLoader imageLoader = ImageLoader.getInstance();
|
private ImageLoader imageLoader = ImageLoader.getInstance();
|
||||||
private DisplayImageOptions displayImageOptions =
|
|
||||||
new DisplayImageOptions.Builder().cacheInMemory(true).build();
|
/** Base display options */
|
||||||
|
private static final DisplayImageOptions DISPLAY_IMAGE_OPTIONS =
|
||||||
|
new DisplayImageOptions.Builder()
|
||||||
|
.cacheInMemory(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
/** Display options for stream thumbnails */
|
||||||
|
private static final DisplayImageOptions DISPLAY_STREAM_THUMBNAIL_OPTIONS =
|
||||||
|
new DisplayImageOptions.Builder()
|
||||||
|
.cloneFrom(DISPLAY_IMAGE_OPTIONS)
|
||||||
|
.showImageOnFail(R.drawable.dummy_thumbnail)
|
||||||
|
.showImageForEmptyUri(R.drawable.dummy_thumbnail)
|
||||||
|
.showImageOnLoading(R.drawable.dummy_thumbnail)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
/** Display options for channel thumbnails */
|
||||||
|
private static final DisplayImageOptions DISPLAY_CHANNEL_THUMBNAIL_OPTIONS =
|
||||||
|
new DisplayImageOptions.Builder()
|
||||||
|
.cloneFrom(DISPLAY_IMAGE_OPTIONS)
|
||||||
|
.showImageOnLoading(R.drawable.buddy_channel_item)
|
||||||
|
.showImageForEmptyUri(R.drawable.buddy_channel_item)
|
||||||
|
.showImageOnFail(R.drawable.buddy_channel_item)
|
||||||
|
.build();
|
||||||
private OnInfoItemSelectedListener onStreamInfoItemSelectedListener;
|
private OnInfoItemSelectedListener onStreamInfoItemSelectedListener;
|
||||||
private OnInfoItemSelectedListener onChannelInfoItemSelectedListener;
|
private OnInfoItemSelectedListener onChannelInfoItemSelectedListener;
|
||||||
|
|
||||||
public InfoItemBuilder(Context context, View rootView) {
|
public InfoItemBuilder(Context context) {
|
||||||
mContext = context;
|
|
||||||
this.rootView = rootView;
|
|
||||||
viewsS = context.getString(R.string.views);
|
viewsS = context.getString(R.string.views);
|
||||||
videosS = context.getString(R.string.videos);
|
videosS = context.getString(R.string.videos);
|
||||||
subsS = context.getString(R.string.subscriber);
|
subsS = context.getString(R.string.subscriber);
|
||||||
|
@ -73,7 +92,6 @@ public class InfoItemBuilder {
|
||||||
thousand = context.getString(R.string.short_thousand);
|
thousand = context.getString(R.string.short_thousand);
|
||||||
million = context.getString(R.string.short_million);
|
million = context.getString(R.string.short_million);
|
||||||
billion = context.getString(R.string.short_billion);
|
billion = context.getString(R.string.short_billion);
|
||||||
inflater = LayoutInflater.from(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnStreamInfoItemSelectedListener(
|
public void setOnStreamInfoItemSelectedListener(
|
||||||
|
@ -107,6 +125,7 @@ public class InfoItemBuilder {
|
||||||
public View buildView(ViewGroup parent, final InfoItem info) {
|
public View buildView(ViewGroup parent, final InfoItem info) {
|
||||||
View itemView = null;
|
View itemView = null;
|
||||||
InfoItemHolder holder = null;
|
InfoItemHolder holder = null;
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||||
switch (info.infoType()) {
|
switch (info.infoType()) {
|
||||||
case STREAM:
|
case STREAM:
|
||||||
//long start = System.nanoTime();
|
//long start = System.nanoTime();
|
||||||
|
@ -127,6 +146,22 @@ public class InfoItemBuilder {
|
||||||
return itemView;
|
return itemView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String getStreamInfoDetailLine(final StreamInfoItem info) {
|
||||||
|
String viewsAndDate = "";
|
||||||
|
if(info.view_count >= 0) {
|
||||||
|
viewsAndDate = shortViewCount(info.view_count);
|
||||||
|
}
|
||||||
|
if(!TextUtils.isEmpty(info.upload_date)) {
|
||||||
|
if(viewsAndDate.isEmpty()) {
|
||||||
|
viewsAndDate = info.upload_date;
|
||||||
|
} else {
|
||||||
|
viewsAndDate += " • " + info.upload_date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return viewsAndDate;
|
||||||
|
}
|
||||||
|
|
||||||
private void buildStreamInfoItem(StreamInfoItemHolder holder, final StreamInfoItem info) {
|
private void buildStreamInfoItem(StreamInfoItemHolder holder, final StreamInfoItem info) {
|
||||||
if (info.infoType() != InfoItem.InfoType.STREAM) {
|
if (info.infoType() != InfoItem.InfoType.STREAM) {
|
||||||
Log.e("InfoItemBuilder", "Info type not yet supported");
|
Log.e("InfoItemBuilder", "Info type not yet supported");
|
||||||
|
@ -146,46 +181,59 @@ public class InfoItemBuilder {
|
||||||
holder.itemDurationView.setVisibility(View.GONE);
|
holder.itemDurationView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (info.view_count >= 0) {
|
|
||||||
holder.itemViewCountView.setText(shortViewCount(info.view_count));
|
|
||||||
} else {
|
|
||||||
holder.itemViewCountView.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
if (!TextUtils.isEmpty(info.upload_date)) holder.itemUploadDateView.setText(info.upload_date + " • ");
|
|
||||||
|
|
||||||
holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
|
holder.itemAdditionalDetails.setText(getStreamInfoDetailLine(info));
|
||||||
if (!TextUtils.isEmpty(info.thumbnail_url)) {
|
|
||||||
imageLoader.displayImage(info.thumbnail_url,
|
// Default thumbnail is shown on error, while loading and if the url is empty
|
||||||
holder.itemThumbnailView, displayImageOptions,
|
imageLoader.displayImage(info.thumbnail_url,
|
||||||
new ImageErrorLoadingListener(mContext, rootView, info.service_id));
|
holder.itemThumbnailView,
|
||||||
}
|
DISPLAY_STREAM_THUMBNAIL_OPTIONS,
|
||||||
|
new ImageErrorLoadingListener(holder.itemRoot.getContext(), holder.itemRoot.getRootView(), info.service_id));
|
||||||
|
|
||||||
|
|
||||||
holder.itemRoot.setOnClickListener(new View.OnClickListener() {
|
holder.itemRoot.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
onStreamInfoItemSelectedListener.selected(info.service_id, info.webpage_url, info.getTitle());
|
if(onStreamInfoItemSelectedListener != null) {
|
||||||
|
onStreamInfoItemSelectedListener.selected(info.service_id, info.webpage_url, info.getTitle());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getChannelInfoDetailLine(final ChannelInfoItem info) {
|
||||||
|
String details = "";
|
||||||
|
if(info.subscriberCount >= 0) {
|
||||||
|
details = shortSubscriber(info.subscriberCount);
|
||||||
|
}
|
||||||
|
if(info.videoAmount >= 0) {
|
||||||
|
String formattedVideoAmount = info.videoAmount + " " + videosS;
|
||||||
|
if(!details.isEmpty()) {
|
||||||
|
details += " • " + formattedVideoAmount;
|
||||||
|
} else {
|
||||||
|
details = formattedVideoAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
private void buildChannelInfoItem(ChannelInfoItemHolder holder, final ChannelInfoItem info) {
|
private void buildChannelInfoItem(ChannelInfoItemHolder holder, final ChannelInfoItem info) {
|
||||||
if (!TextUtils.isEmpty(info.getTitle())) holder.itemChannelTitleView.setText(info.getTitle());
|
if (!TextUtils.isEmpty(info.getTitle())) holder.itemChannelTitleView.setText(info.getTitle());
|
||||||
holder.itemSubscriberCountView.setText(shortSubscriber(info.subscriberCount) + " • ");
|
holder.itemAdditionalDetailView.setText(getChannelInfoDetailLine(info));
|
||||||
holder.itemVideoCountView.setText(info.videoAmount + " " + videosS);
|
|
||||||
if (!TextUtils.isEmpty(info.description)) holder.itemChannelDescriptionView.setText(info.description);
|
if (!TextUtils.isEmpty(info.description)) holder.itemChannelDescriptionView.setText(info.description);
|
||||||
|
|
||||||
holder.itemThumbnailView.setImageResource(R.drawable.buddy_channel_item);
|
imageLoader.displayImage(info.thumbnailUrl,
|
||||||
if (!TextUtils.isEmpty(info.thumbnailUrl)) {
|
holder.itemThumbnailView,
|
||||||
imageLoader.displayImage(info.thumbnailUrl,
|
DISPLAY_CHANNEL_THUMBNAIL_OPTIONS,
|
||||||
holder.itemThumbnailView,
|
new ImageErrorLoadingListener(holder.itemRoot.getContext(), holder.itemRoot.getRootView(), info.serviceId));
|
||||||
displayImageOptions,
|
|
||||||
new ImageErrorLoadingListener(mContext, rootView, info.serviceId));
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.itemRoot.setOnClickListener(new View.OnClickListener() {
|
holder.itemRoot.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
onChannelInfoItemSelectedListener.selected(info.serviceId, info.getLink(), info.channelName);
|
if(onStreamInfoItemSelectedListener != null) {
|
||||||
|
onChannelInfoItemSelectedListener.selected(info.serviceId, info.getLink(), info.channelName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -218,7 +266,10 @@ public class InfoItemBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getDurationString(int duration) {
|
public static String getDurationString(int duration) {
|
||||||
String output = "";
|
if(duration < 0) {
|
||||||
|
duration = 0;
|
||||||
|
}
|
||||||
|
String output;
|
||||||
int days = duration / (24 * 60 * 60); /* greater than a day */
|
int days = duration / (24 * 60 * 60); /* greater than a day */
|
||||||
duration %= (24 * 60 * 60);
|
duration %= (24 * 60 * 60);
|
||||||
int hours = duration / (60 * 60); /* greater than an hour */
|
int hours = duration / (60 * 60); /* greater than an hour */
|
||||||
|
@ -228,46 +279,12 @@ public class InfoItemBuilder {
|
||||||
|
|
||||||
//handle days
|
//handle days
|
||||||
if (days > 0) {
|
if (days > 0) {
|
||||||
output = Integer.toString(days) + ":";
|
output = String.format(Locale.US, "%d:%02d:%02d:%02d", days, hours, minutes, seconds);
|
||||||
}
|
} else if(hours > 0) {
|
||||||
// handle hours
|
output = String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds);
|
||||||
if (hours > 0 || !output.isEmpty()) {
|
|
||||||
if (hours > 0) {
|
|
||||||
if (hours >= 10 || output.isEmpty()) {
|
|
||||||
output += Integer.toString(hours);
|
|
||||||
} else {
|
|
||||||
output += "0" + Integer.toString(hours);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
output += "00";
|
|
||||||
}
|
|
||||||
output += ":";
|
|
||||||
}
|
|
||||||
//handle minutes
|
|
||||||
if (minutes > 0 || !output.isEmpty()) {
|
|
||||||
if (minutes > 0) {
|
|
||||||
if (minutes >= 10 || output.isEmpty()) {
|
|
||||||
output += Integer.toString(minutes);
|
|
||||||
} else {
|
|
||||||
output += "0" + Integer.toString(minutes);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
output += "00";
|
|
||||||
}
|
|
||||||
output += ":";
|
|
||||||
}
|
|
||||||
|
|
||||||
//handle seconds
|
|
||||||
if (output.isEmpty()) {
|
|
||||||
output += "0:";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seconds >= 10) {
|
|
||||||
output += Integer.toString(seconds);
|
|
||||||
} else {
|
} else {
|
||||||
output += "0" + Integer.toString(seconds);
|
output = String.format(Locale.US, "%d:%02d", minutes, seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,8 +55,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public InfoListAdapter(Activity a, View rootView) {
|
public InfoListAdapter(Activity a) {
|
||||||
infoItemBuilder = new InfoItemBuilder(a, rootView);
|
infoItemBuilder = new InfoItemBuilder(a);
|
||||||
infoItemList = new ArrayList<>();
|
infoItemList = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +78,9 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearStreamItemList() {
|
public void clearStreamItemList() {
|
||||||
|
if(infoItemList.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
infoItemList.clear();
|
infoItemList.clear();
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
@ -152,7 +155,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) {
|
public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) {
|
||||||
//god damen f*** ANDROID SH**
|
//god damn f*** ANDROID SH**
|
||||||
if(holder instanceof InfoItemHolder) {
|
if(holder instanceof InfoItemHolder) {
|
||||||
if(header != null) {
|
if(header != null) {
|
||||||
i--;
|
i--;
|
||||||
|
|
|
@ -33,8 +33,7 @@ public class StreamInfoItemHolder extends InfoItemHolder {
|
||||||
public final TextView itemVideoTitleView,
|
public final TextView itemVideoTitleView,
|
||||||
itemUploaderView,
|
itemUploaderView,
|
||||||
itemDurationView,
|
itemDurationView,
|
||||||
itemUploadDateView,
|
itemAdditionalDetails;
|
||||||
itemViewCountView;
|
|
||||||
public final View itemRoot;
|
public final View itemRoot;
|
||||||
|
|
||||||
public StreamInfoItemHolder(View v) {
|
public StreamInfoItemHolder(View v) {
|
||||||
|
@ -44,8 +43,7 @@ public class StreamInfoItemHolder extends InfoItemHolder {
|
||||||
itemVideoTitleView = (TextView) v.findViewById(R.id.itemVideoTitleView);
|
itemVideoTitleView = (TextView) v.findViewById(R.id.itemVideoTitleView);
|
||||||
itemUploaderView = (TextView) v.findViewById(R.id.itemUploaderView);
|
itemUploaderView = (TextView) v.findViewById(R.id.itemUploaderView);
|
||||||
itemDurationView = (TextView) v.findViewById(R.id.itemDurationView);
|
itemDurationView = (TextView) v.findViewById(R.id.itemDurationView);
|
||||||
itemUploadDateView = (TextView) v.findViewById(R.id.itemUploadDateView);
|
itemAdditionalDetails = (TextView) v.findViewById(R.id.itemAdditionalDetails);
|
||||||
itemViewCountView = (TextView) v.findViewById(R.id.itemViewCountView);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.schabi.newpipe.report;
|
package org.schabi.newpipe.report;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import org.acra.collector.CrashReportData;
|
import org.acra.collector.CrashReportData;
|
||||||
import org.acra.sender.ReportSender;
|
import org.acra.sender.ReportSender;
|
||||||
|
@ -30,9 +31,9 @@ import org.schabi.newpipe.R;
|
||||||
public class AcraReportSender implements ReportSender {
|
public class AcraReportSender implements ReportSender {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send(Context context, CrashReportData report) throws ReportSenderException {
|
public void send(@NonNull Context context, @NonNull CrashReportData report) throws ReportSenderException {
|
||||||
ErrorActivity.reportError(context, report,
|
ErrorActivity.reportError(context, report,
|
||||||
ErrorActivity.ErrorInfo.make(ErrorActivity.UI_ERROR,"none",
|
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,"none",
|
||||||
"App crash, UI failure", R.string.app_ui_crash));
|
"App crash, UI failure", R.string.app_ui_crash));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.schabi.newpipe.report;
|
package org.schabi.newpipe.report;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import org.acra.config.ACRAConfiguration;
|
import org.acra.config.ACRAConfiguration;
|
||||||
import org.acra.sender.ReportSender;
|
import org.acra.sender.ReportSender;
|
||||||
|
@ -28,7 +29,8 @@ import org.schabi.newpipe.report.AcraReportSender;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class AcraReportSenderFactory implements ReportSenderFactory {
|
public class AcraReportSenderFactory implements ReportSenderFactory {
|
||||||
public ReportSender create(Context context, ACRAConfiguration config) {
|
@NonNull
|
||||||
|
public ReportSender create(@NonNull Context context, @NonNull ACRAConfiguration config) {
|
||||||
return new AcraReportSender();
|
return new AcraReportSender();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ import android.os.Handler;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.v4.app.NavUtils;
|
import android.support.v4.app.NavUtils;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
|
@ -71,24 +73,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
// BUNDLE TAGS
|
// BUNDLE TAGS
|
||||||
public static final String ERROR_INFO = "error_info";
|
public static final String ERROR_INFO = "error_info";
|
||||||
public static final String ERROR_LIST = "error_list";
|
public static final String ERROR_LIST = "error_list";
|
||||||
// MESSAGE ID
|
|
||||||
public static final int SEARCHED = 0;
|
|
||||||
public static final int REQUESTED_STREAM = 1;
|
|
||||||
public static final int GET_SUGGESTIONS = 2;
|
|
||||||
public static final int SOMETHING_ELSE = 3;
|
|
||||||
public static final int USER_REPORT = 4;
|
|
||||||
public static final int LOAD_IMAGE = 5;
|
|
||||||
public static final int UI_ERROR = 6;
|
|
||||||
public static final int REQUESTED_CHANNEL = 7;
|
|
||||||
// MESSAGE STRING
|
|
||||||
public static final String SEARCHED_STRING = "searched";
|
|
||||||
public static final String REQUESTED_STREAM_STRING = "requested stream";
|
|
||||||
public static final String GET_SUGGESTIONS_STRING = "get suggestions";
|
|
||||||
public static final String SOMETHING_ELSE_STRING = "something";
|
|
||||||
public static final String USER_REPORT_STRING = "user report";
|
|
||||||
public static final String LOAD_IMAGE_STRING = "load image";
|
|
||||||
public static final String UI_ERROR_STRING = "ui error";
|
|
||||||
public static final String REQUESTED_CHANNEL_STRING = "requested channel";
|
|
||||||
public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org";
|
public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org";
|
||||||
public static final String ERROR_EMAIL_SUBJECT = "Exception in NewPipe " + BuildConfig.VERSION_NAME;
|
public static final String ERROR_EMAIL_SUBJECT = "Exception in NewPipe " + BuildConfig.VERSION_NAME;
|
||||||
Thread globIpRangeThread;
|
Thread globIpRangeThread;
|
||||||
|
@ -105,11 +90,11 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
private TextView errorMessageView;
|
private TextView errorMessageView;
|
||||||
|
|
||||||
public static void reportUiError(final AppCompatActivity activity, final Throwable el) {
|
public static void reportUiError(final AppCompatActivity activity, final Throwable el) {
|
||||||
reportError(activity, el, activity.getClass(), null, ErrorInfo.make(UI_ERROR, "none", "", R.string.app_ui_crash));
|
reportError(activity, el, activity.getClass(), null, ErrorInfo.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void reportError(final Context context, final List<Throwable> el,
|
public static void reportError(final Context context, final List<Throwable> el,
|
||||||
final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) {
|
final Class returnActivity, View rootView, final ErrorInfo errorInfo) {
|
||||||
|
|
||||||
if (rootView != null) {
|
if (rootView != null) {
|
||||||
Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG)
|
Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG)
|
||||||
|
@ -118,7 +103,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
|
ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
|
||||||
ac.returnActivity = returnAcitivty;
|
ac.returnActivity = returnActivity;
|
||||||
Intent intent = new Intent(context, ErrorActivity.class);
|
Intent intent = new Intent(context, ErrorActivity.class);
|
||||||
intent.putExtra(ERROR_INFO, errorInfo);
|
intent.putExtra(ERROR_INFO, errorInfo);
|
||||||
intent.putExtra(ERROR_LIST, elToSl(el));
|
intent.putExtra(ERROR_LIST, elToSl(el));
|
||||||
|
@ -128,7 +113,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
}).show();
|
}).show();
|
||||||
} else {
|
} else {
|
||||||
ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
|
ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
|
||||||
ac.returnActivity = returnAcitivty;
|
ac.returnActivity = returnActivity;
|
||||||
Intent intent = new Intent(context, ErrorActivity.class);
|
Intent intent = new Intent(context, ErrorActivity.class);
|
||||||
intent.putExtra(ERROR_INFO, errorInfo);
|
intent.putExtra(ERROR_INFO, errorInfo);
|
||||||
intent.putExtra(ERROR_LIST, elToSl(el));
|
intent.putExtra(ERROR_LIST, elToSl(el));
|
||||||
|
@ -138,34 +123,34 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void reportError(final Context context, final Throwable e,
|
public static void reportError(final Context context, final Throwable e,
|
||||||
final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) {
|
final Class returnActivity, View rootView, final ErrorInfo errorInfo) {
|
||||||
List<Throwable> el = null;
|
List<Throwable> el = null;
|
||||||
if(e != null) {
|
if(e != null) {
|
||||||
el = new Vector<>();
|
el = new Vector<>();
|
||||||
el.add(e);
|
el.add(e);
|
||||||
}
|
}
|
||||||
reportError(context, el, returnAcitivty, rootView, errorInfo);
|
reportError(context, el, returnActivity, rootView, errorInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// async call
|
// async call
|
||||||
public static void reportError(Handler handler, final Context context, final Throwable e,
|
public static void reportError(Handler handler, final Context context, final Throwable e,
|
||||||
final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) {
|
final Class returnActivity, final View rootView, final ErrorInfo errorInfo) {
|
||||||
|
|
||||||
List<Throwable> el = null;
|
List<Throwable> el = null;
|
||||||
if(e != null) {
|
if(e != null) {
|
||||||
el = new Vector<>();
|
el = new Vector<>();
|
||||||
el.add(e);
|
el.add(e);
|
||||||
}
|
}
|
||||||
reportError(handler, context, el, returnAcitivty, rootView, errorInfo);
|
reportError(handler, context, el, returnActivity, rootView, errorInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// async call
|
// async call
|
||||||
public static void reportError(Handler handler, final Context context, final List<Throwable> el,
|
public static void reportError(Handler handler, final Context context, final List<Throwable> el,
|
||||||
final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) {
|
final Class returnActivity, final View rootView, final ErrorInfo errorInfo) {
|
||||||
handler.post(new Runnable() {
|
handler.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
reportError(context, el, returnAcitivty, rootView, errorInfo);
|
reportError(context, el, returnActivity, rootView, errorInfo);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -232,7 +217,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
errorInfo = intent.getParcelableExtra(ERROR_INFO);
|
errorInfo = intent.getParcelableExtra(ERROR_INFO);
|
||||||
errorList = intent.getStringArrayExtra(ERROR_LIST);
|
errorList = intent.getStringArrayExtra(ERROR_LIST);
|
||||||
|
|
||||||
//importand add gurumeditaion
|
// important add guru meditation
|
||||||
addGuruMeditaion();
|
addGuruMeditaion();
|
||||||
currentTimeStamp = getCurrentTimeStamp();
|
currentTimeStamp = getCurrentTimeStamp();
|
||||||
|
|
||||||
|
@ -250,7 +235,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
});
|
});
|
||||||
reportButton.setEnabled(false);
|
reportButton.setEnabled(false);
|
||||||
|
|
||||||
globIpRangeThread = new Thread(new IpRagneRequester());
|
globIpRangeThread = new Thread(new IpRangeRequester());
|
||||||
globIpRangeThread.start();
|
globIpRangeThread.start();
|
||||||
|
|
||||||
// normal bugreport
|
// normal bugreport
|
||||||
|
@ -308,17 +293,30 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the checked activity.
|
||||||
|
* @param returnActivity the activity to return to
|
||||||
|
* @return the casted return activity or null
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
static Class<? extends Activity> getReturnActivity(Class<?> returnActivity) {
|
||||||
|
Class<? extends Activity> checkedReturnActivity = null;
|
||||||
|
if (returnActivity != null){
|
||||||
|
if (Activity.class.isAssignableFrom(returnActivity)) {
|
||||||
|
checkedReturnActivity = returnActivity.asSubclass(Activity.class);
|
||||||
|
} else {
|
||||||
|
checkedReturnActivity = MainActivity.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return checkedReturnActivity;
|
||||||
|
}
|
||||||
|
|
||||||
private void goToReturnActivity() {
|
private void goToReturnActivity() {
|
||||||
if (returnActivity == null) {
|
Class<? extends Activity> checkedReturnActivity = getReturnActivity(returnActivity);
|
||||||
|
if (checkedReturnActivity == null) {
|
||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
} else {
|
} else {
|
||||||
Intent intent;
|
Intent intent = new Intent(this, checkedReturnActivity);
|
||||||
if (returnActivity != null &&
|
|
||||||
returnActivity.isAssignableFrom(Activity.class)) {
|
|
||||||
intent = new Intent(this, returnActivity);
|
|
||||||
} else {
|
|
||||||
intent = new Intent(this, MainActivity.class);
|
|
||||||
}
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
NavUtils.navigateUpTo(this, intent);
|
NavUtils.navigateUpTo(this, intent);
|
||||||
}
|
}
|
||||||
|
@ -376,26 +374,11 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getUserActionString(int userAction) {
|
private String getUserActionString(UserAction userAction) {
|
||||||
switch (userAction) {
|
if(userAction == null) {
|
||||||
case REQUESTED_STREAM:
|
return "Your description is in another castle.";
|
||||||
return REQUESTED_STREAM_STRING;
|
} else {
|
||||||
case SEARCHED:
|
return userAction.getMessage();
|
||||||
return SEARCHED_STRING;
|
|
||||||
case GET_SUGGESTIONS:
|
|
||||||
return GET_SUGGESTIONS_STRING;
|
|
||||||
case SOMETHING_ELSE:
|
|
||||||
return SOMETHING_ELSE_STRING;
|
|
||||||
case USER_REPORT:
|
|
||||||
return USER_REPORT_STRING;
|
|
||||||
case LOAD_IMAGE:
|
|
||||||
return LOAD_IMAGE_STRING;
|
|
||||||
case UI_ERROR:
|
|
||||||
return UI_ERROR_STRING;
|
|
||||||
case REQUESTED_CHANNEL:
|
|
||||||
return REQUESTED_CHANNEL_STRING;
|
|
||||||
default:
|
|
||||||
return "Your description is in another castle.";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,28 +427,28 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
return new ErrorInfo[size];
|
return new ErrorInfo[size];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
public int userAction;
|
final public UserAction userAction;
|
||||||
public String request;
|
final public String request;
|
||||||
public String serviceName;
|
final public String serviceName;
|
||||||
public int message;
|
@StringRes
|
||||||
|
final public int message;
|
||||||
|
|
||||||
public ErrorInfo() {
|
private ErrorInfo(UserAction userAction, String serviceName, String request, @StringRes int message) {
|
||||||
|
this.userAction = userAction;
|
||||||
|
this.serviceName = serviceName;
|
||||||
|
this.request = request;
|
||||||
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ErrorInfo(Parcel in) {
|
protected ErrorInfo(Parcel in) {
|
||||||
this.userAction = in.readInt();
|
this.userAction = UserAction.valueOf(in.readString());
|
||||||
this.request = in.readString();
|
this.request = in.readString();
|
||||||
this.serviceName = in.readString();
|
this.serviceName = in.readString();
|
||||||
this.message = in.readInt();
|
this.message = in.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ErrorInfo make(int userAction, String serviceName, String request, int message) {
|
public static ErrorInfo make(UserAction userAction, String serviceName, String request, @StringRes int message) {
|
||||||
ErrorInfo info = new ErrorInfo();
|
return new ErrorInfo(userAction, serviceName, request, message);
|
||||||
info.userAction = userAction;
|
|
||||||
info.serviceName = serviceName;
|
|
||||||
info.request = request;
|
|
||||||
info.message = message;
|
|
||||||
return info;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -475,14 +458,14 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
dest.writeInt(this.userAction);
|
dest.writeString(this.userAction.name());
|
||||||
dest.writeString(this.request);
|
dest.writeString(this.request);
|
||||||
dest.writeString(this.serviceName);
|
dest.writeString(this.serviceName);
|
||||||
dest.writeInt(this.message);
|
dest.writeInt(this.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class IpRagneRequester implements Runnable {
|
private class IpRangeRequester implements Runnable {
|
||||||
Handler h = new Handler();
|
Handler h = new Handler();
|
||||||
public void run() {
|
public void run() {
|
||||||
String ipRange = "none";
|
String ipRange = "none";
|
||||||
|
@ -493,17 +476,16 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
ipRange = Parser.matchGroup1("([0-9]*\\.[0-9]*\\.)[0-9]*\\.[0-9]*", ip)
|
ipRange = Parser.matchGroup1("([0-9]*\\.[0-9]*\\.)[0-9]*\\.[0-9]*", ip)
|
||||||
+ "0.0";
|
+ "0.0";
|
||||||
} catch(Throwable e) {
|
} catch(Throwable e) {
|
||||||
Log.d(TAG, "Error while error: could not get iprange");
|
Log.w(TAG, "Error while error: could not get iprange", e);
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
} finally {
|
||||||
h.post(new IpRageReturnRunnable(ipRange));
|
h.post(new IpRangeReturnRunnable(ipRange));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class IpRageReturnRunnable implements Runnable {
|
private class IpRangeReturnRunnable implements Runnable {
|
||||||
String ipRange;
|
String ipRange;
|
||||||
public IpRageReturnRunnable(String ipRange) {
|
public IpRangeReturnRunnable(String ipRange) {
|
||||||
this.ipRange = ipRange;
|
this.ipRange = ipRange;
|
||||||
}
|
}
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.schabi.newpipe.report;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user actions that can cause an error.
|
||||||
|
*/
|
||||||
|
public enum UserAction {
|
||||||
|
SEARCHED("searched"),
|
||||||
|
REQUESTED_STREAM("requested stream"),
|
||||||
|
GET_SUGGESTIONS("get suggestions"),
|
||||||
|
SOMETHING_ELSE("something"),
|
||||||
|
USER_REPORT("user report"),
|
||||||
|
LOAD_IMAGE("load image"),
|
||||||
|
UI_ERROR("ui error"),
|
||||||
|
REQUESTED_CHANNEL("requested channel");
|
||||||
|
|
||||||
|
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
UserAction(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
|
@ -72,9 +72,7 @@ public class NewPipeSettings {
|
||||||
public static String getVideoDownloadPath(Context context) {
|
public static String getVideoDownloadPath(Context context) {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
final String key = context.getString(R.string.download_path_key);
|
final String key = context.getString(R.string.download_path_key);
|
||||||
String downloadPath = prefs.getString(key, Environment.DIRECTORY_MOVIES);
|
return prefs.getString(key, Environment.DIRECTORY_MOVIES);
|
||||||
|
|
||||||
return downloadPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static File getAudioDownloadFolder(Context context) {
|
public static File getAudioDownloadFolder(Context context) {
|
||||||
|
@ -84,9 +82,7 @@ public class NewPipeSettings {
|
||||||
public static String getAudioDownloadPath(Context context) {
|
public static String getAudioDownloadPath(Context context) {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
final String key = context.getString(R.string.download_path_audio_key);
|
final String key = context.getString(R.string.download_path_audio_key);
|
||||||
String downloadPath = prefs.getString(key, Environment.DIRECTORY_MUSIC);
|
return prefs.getString(key, Environment.DIRECTORY_MUSIC);
|
||||||
|
|
||||||
return downloadPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getFolder(Context context, int keyID, String defaultDirectoryName) {
|
private static File getFolder(Context context, int keyID, String defaultDirectoryName) {
|
||||||
|
|
|
@ -77,14 +77,14 @@ public class NavigationHelper {
|
||||||
public static void openMainFragment(FragmentManager fragmentManager) {
|
public static void openMainFragment(FragmentManager fragmentManager) {
|
||||||
ImageLoader.getInstance().clearMemoryCache();
|
ImageLoader.getInstance().clearMemoryCache();
|
||||||
fragmentManager.beginTransaction()
|
fragmentManager.beginTransaction()
|
||||||
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out, android.R.anim.fade_in, android.R.anim.fade_out)
|
.setCustomAnimations(R.anim.custom_fade_in, R.anim.custom_fade_out, R.anim.custom_fade_in, R.anim.custom_fade_out)
|
||||||
.replace(R.id.fragment_holder, new MainFragment())
|
.replace(R.id.fragment_holder, new MainFragment())
|
||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void openSearchFragment(FragmentManager fragmentManager, int serviceId, String query) {
|
public static void openSearchFragment(FragmentManager fragmentManager, int serviceId, String query) {
|
||||||
fragmentManager.beginTransaction()
|
fragmentManager.beginTransaction()
|
||||||
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out, android.R.anim.fade_in, android.R.anim.fade_out)
|
.setCustomAnimations(R.anim.custom_fade_in, R.anim.custom_fade_out, R.anim.custom_fade_in, R.anim.custom_fade_out)
|
||||||
.replace(R.id.fragment_holder, SearchFragment.getInstance(serviceId, query))
|
.replace(R.id.fragment_holder, SearchFragment.getInstance(serviceId, query))
|
||||||
.addToBackStack(null)
|
.addToBackStack(null)
|
||||||
.commit();
|
.commit();
|
||||||
|
@ -109,7 +109,7 @@ public class NavigationHelper {
|
||||||
instance.setAutoplay(autoPlay);
|
instance.setAutoplay(autoPlay);
|
||||||
|
|
||||||
fragmentManager.beginTransaction()
|
fragmentManager.beginTransaction()
|
||||||
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out, android.R.anim.fade_in, android.R.anim.fade_out)
|
.setCustomAnimations(R.anim.custom_fade_in, R.anim.custom_fade_out, R.anim.custom_fade_in, R.anim.custom_fade_out)
|
||||||
.replace(R.id.fragment_holder, instance)
|
.replace(R.id.fragment_holder, instance)
|
||||||
.addToBackStack(null)
|
.addToBackStack(null)
|
||||||
.commit();
|
.commit();
|
||||||
|
@ -118,7 +118,7 @@ public class NavigationHelper {
|
||||||
public static void openChannelFragment(FragmentManager fragmentManager, int serviceId, String url, String name) {
|
public static void openChannelFragment(FragmentManager fragmentManager, int serviceId, String url, String name) {
|
||||||
if (name == null) name = "";
|
if (name == null) name = "";
|
||||||
fragmentManager.beginTransaction()
|
fragmentManager.beginTransaction()
|
||||||
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out, android.R.anim.fade_in, android.R.anim.fade_out)
|
.setCustomAnimations(R.anim.custom_fade_in, R.anim.custom_fade_out, R.anim.custom_fade_in, R.anim.custom_fade_out)
|
||||||
.replace(R.id.fragment_holder, ChannelFragment.getInstance(serviceId, url, name))
|
.replace(R.id.fragment_holder, ChannelFragment.getInstance(serviceId, url, name))
|
||||||
.addToBackStack(null)
|
.addToBackStack(null)
|
||||||
.commit();
|
.commit();
|
||||||
|
|
|
@ -12,6 +12,8 @@ import org.schabi.newpipe.report.ErrorActivity;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.report.UserAction.REQUESTED_CHANNEL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract {@link ChannelInfo} with {@link ChannelExtractor} from the given url of the given service
|
* Extract {@link ChannelInfo} with {@link ChannelExtractor} from the given url of the given service
|
||||||
*
|
*
|
||||||
|
@ -67,7 +69,7 @@ public class ChannelExtractorWorker extends ExtractorWorker {
|
||||||
ChannelExtractor extractor = getService().getChannelExtractorInstance(url, pageNumber);
|
ChannelExtractor extractor = getService().getChannelExtractorInstance(url, pageNumber);
|
||||||
channelInfo = ChannelInfo.getInfo(extractor);
|
channelInfo = ChannelInfo.getInfo(extractor);
|
||||||
|
|
||||||
if (!channelInfo.errors.isEmpty()) handleErrorsDuringExtraction(channelInfo.errors, ErrorActivity.REQUESTED_CHANNEL);
|
if (!channelInfo.errors.isEmpty()) handleErrorsDuringExtraction(channelInfo.errors, REQUESTED_CHANNEL);
|
||||||
|
|
||||||
if (callback != null && channelInfo != null && !isInterrupted()) getHandler().post(new Runnable() {
|
if (callback != null && channelInfo != null && !isInterrupted()) getHandler().post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -93,7 +95,7 @@ public class ChannelExtractorWorker extends ExtractorWorker {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (exception instanceof ParsingException || exception instanceof ExtractionException) {
|
} else if (exception instanceof ParsingException || exception instanceof ExtractionException) {
|
||||||
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_CHANNEL, getServiceName(), url, R.string.parsing_error));
|
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(REQUESTED_CHANNEL, getServiceName(), url, R.string.parsing_error));
|
||||||
getHandler().post(new Runnable() {
|
getHandler().post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
@ -101,7 +103,7 @@ public class ChannelExtractorWorker extends ExtractorWorker {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_CHANNEL, getServiceName(), url, R.string.general_error));
|
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(REQUESTED_CHANNEL, getServiceName(), url, R.string.general_error));
|
||||||
getHandler().post(new Runnable() {
|
getHandler().post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.report.UserAction;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -59,14 +60,14 @@ public abstract class ExtractorWorker extends AbstractWorker {
|
||||||
* @param errorUserAction what action was the user performing during the error.
|
* @param errorUserAction what action was the user performing during the error.
|
||||||
* (One of the {@link ErrorActivity}.REQUEST_* error (message) ids)
|
* (One of the {@link ErrorActivity}.REQUEST_* error (message) ids)
|
||||||
*/
|
*/
|
||||||
protected void handleErrorsDuringExtraction(List<Throwable> errorsList, int errorUserAction){
|
protected void handleErrorsDuringExtraction(List<Throwable> errorsList, UserAction errorUserAction){
|
||||||
String errorString = "<error id>";
|
String errorString = "<error id>";
|
||||||
switch (errorUserAction) {
|
switch (errorUserAction) {
|
||||||
case ErrorActivity.REQUESTED_STREAM:
|
case REQUESTED_STREAM:
|
||||||
errorString= ErrorActivity.REQUESTED_STREAM_STRING;
|
errorString= errorUserAction.getMessage();
|
||||||
break;
|
break;
|
||||||
case ErrorActivity.REQUESTED_CHANNEL:
|
case REQUESTED_CHANNEL:
|
||||||
errorString= ErrorActivity.REQUESTED_CHANNEL_STRING;
|
errorString= errorUserAction.getMessage();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,13 @@ import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||||
import org.schabi.newpipe.extractor.search.SearchEngine;
|
import org.schabi.newpipe.extractor.search.SearchEngine;
|
||||||
import org.schabi.newpipe.extractor.search.SearchResult;
|
import org.schabi.newpipe.extractor.search.SearchResult;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.report.UserAction;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.report.UserAction.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of results based on a query
|
* Return list of results based on a query
|
||||||
*
|
*
|
||||||
|
@ -106,7 +109,7 @@ public class SearchWorker extends AbstractWorker {
|
||||||
});
|
});
|
||||||
} else if (exception instanceof ExtractionException) {
|
} else if (exception instanceof ExtractionException) {
|
||||||
View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
|
View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
|
||||||
ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, getServiceName(), query, R.string.parsing_error));
|
ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(SEARCHED, getServiceName(), query, R.string.parsing_error));
|
||||||
getHandler().post(new Runnable() {
|
getHandler().post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
@ -115,7 +118,7 @@ public class SearchWorker extends AbstractWorker {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
|
View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
|
||||||
ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, getServiceName(), query, R.string.general_error));
|
ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(SEARCHED, getServiceName(), query, R.string.general_error));
|
||||||
getHandler().post(new Runnable() {
|
getHandler().post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
|
@ -10,9 +10,12 @@ import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
|
||||||
import org.schabi.newpipe.extractor.stream_info.StreamExtractor;
|
import org.schabi.newpipe.extractor.stream_info.StreamExtractor;
|
||||||
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
|
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.report.UserAction;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.report.UserAction.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract {@link StreamInfo} with {@link StreamExtractor} from the given url of the given service
|
* Extract {@link StreamInfo} with {@link StreamExtractor} from the given url of the given service
|
||||||
*
|
*
|
||||||
|
@ -66,7 +69,7 @@ public class StreamExtractorWorker extends ExtractorWorker {
|
||||||
StreamExtractor streamExtractor = getService().getExtractorInstance(url);
|
StreamExtractor streamExtractor = getService().getExtractorInstance(url);
|
||||||
streamInfo = StreamInfo.getVideoInfo(streamExtractor);
|
streamInfo = StreamInfo.getVideoInfo(streamExtractor);
|
||||||
|
|
||||||
if (streamInfo != null && !streamInfo.errors.isEmpty()) handleErrorsDuringExtraction(streamInfo.errors, ErrorActivity.REQUESTED_STREAM);
|
if (streamInfo != null && !streamInfo.errors.isEmpty()) handleErrorsDuringExtraction(streamInfo.errors, REQUESTED_STREAM);
|
||||||
|
|
||||||
if (callback != null && getHandler() != null && streamInfo != null && !isInterrupted()) getHandler().post(new Runnable() {
|
if (callback != null && getHandler() != null && streamInfo != null && !isInterrupted()) getHandler().post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -121,7 +124,7 @@ public class StreamExtractorWorker extends ExtractorWorker {
|
||||||
});
|
});
|
||||||
} else if (exception instanceof YoutubeStreamExtractor.DecryptException) {
|
} else if (exception instanceof YoutubeStreamExtractor.DecryptException) {
|
||||||
// custom service related exceptions
|
// custom service related exceptions
|
||||||
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.youtube_signature_decryption_error));
|
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(REQUESTED_STREAM, getServiceName(), url, R.string.youtube_signature_decryption_error));
|
||||||
getHandler().post(new Runnable() {
|
getHandler().post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
@ -131,9 +134,9 @@ public class StreamExtractorWorker extends ExtractorWorker {
|
||||||
} else if (exception instanceof StreamInfo.StreamExctractException) {
|
} else if (exception instanceof StreamInfo.StreamExctractException) {
|
||||||
if (!streamInfo.errors.isEmpty()) {
|
if (!streamInfo.errors.isEmpty()) {
|
||||||
// !!! if this case ever kicks in someone gets kicked out !!!
|
// !!! if this case ever kicks in someone gets kicked out !!!
|
||||||
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.could_not_get_stream));
|
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(REQUESTED_STREAM, getServiceName(), url, R.string.could_not_get_stream));
|
||||||
} else {
|
} else {
|
||||||
ErrorActivity.reportError(getHandler(), getContext(), streamInfo.errors, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.could_not_get_stream));
|
ErrorActivity.reportError(getHandler(), getContext(), streamInfo.errors, MainActivity.class, null, ErrorActivity.ErrorInfo.make(REQUESTED_STREAM, getServiceName(), url, R.string.could_not_get_stream));
|
||||||
}
|
}
|
||||||
|
|
||||||
getHandler().post(new Runnable() {
|
getHandler().post(new Runnable() {
|
||||||
|
@ -143,7 +146,7 @@ public class StreamExtractorWorker extends ExtractorWorker {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (exception instanceof ParsingException) {
|
} else if (exception instanceof ParsingException) {
|
||||||
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.parsing_error));
|
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(REQUESTED_STREAM, getServiceName(), url, R.string.parsing_error));
|
||||||
getHandler().post(new Runnable() {
|
getHandler().post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
@ -151,7 +154,7 @@ public class StreamExtractorWorker extends ExtractorWorker {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.general_error));
|
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(REQUESTED_STREAM, getServiceName(), url, R.string.general_error));
|
||||||
getHandler().post(new Runnable() {
|
getHandler().post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
|
@ -11,10 +11,13 @@ import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.SuggestionExtractor;
|
import org.schabi.newpipe.extractor.SuggestionExtractor;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.report.UserAction;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.report.UserAction.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Worker that get suggestions based on the query
|
* Worker that get suggestions based on the query
|
||||||
*
|
*
|
||||||
|
@ -79,7 +82,7 @@ public class SuggestionWorker extends AbstractWorker {
|
||||||
|
|
||||||
if (exception instanceof ExtractionException) {
|
if (exception instanceof ExtractionException) {
|
||||||
View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
|
View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
|
||||||
ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(ErrorActivity.GET_SUGGESTIONS, getServiceName(), query, R.string.parsing_error));
|
ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(GET_SUGGESTIONS, getServiceName(), query, R.string.parsing_error));
|
||||||
getHandler().post(new Runnable() {
|
getHandler().post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
@ -95,7 +98,7 @@ public class SuggestionWorker extends AbstractWorker {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
|
View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
|
||||||
ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(ErrorActivity.GET_SUGGESTIONS, getServiceName(), query, R.string.general_error));
|
ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(GET_SUGGESTIONS, getServiceName(), query, R.string.general_error));
|
||||||
getHandler().post(new Runnable() {
|
getHandler().post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
|
@ -9,12 +9,14 @@ public interface DownloadDataSource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load all missions
|
* Load all missions
|
||||||
|
*
|
||||||
* @return a list of download missions
|
* @return a list of download missions
|
||||||
*/
|
*/
|
||||||
List<DownloadMission> loadMissions();
|
List<DownloadMission> loadMissions();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a downlaod mission to the storage
|
* Add a download mission to the storage
|
||||||
|
*
|
||||||
* @param downloadMission the download mission to add
|
* @param downloadMission the download mission to add
|
||||||
* @return the identifier of the mission
|
* @return the identifier of the mission
|
||||||
*/
|
*/
|
||||||
|
@ -22,6 +24,7 @@ public interface DownloadDataSource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a download mission which exists in the storage
|
* Update a download mission which exists in the storage
|
||||||
|
*
|
||||||
* @param downloadMission the download mission to update
|
* @param downloadMission the download mission to update
|
||||||
* @throws IllegalArgumentException if the mission was not added to storage
|
* @throws IllegalArgumentException if the mission was not added to storage
|
||||||
*/
|
*/
|
||||||
|
@ -30,6 +33,7 @@ public interface DownloadDataSource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a download mission
|
* Delete a download mission
|
||||||
|
*
|
||||||
* @param downloadMission the mission to delete
|
* @param downloadMission the mission to delete
|
||||||
*/
|
*/
|
||||||
void deleteMission(DownloadMission downloadMission);
|
void deleteMission(DownloadMission downloadMission);
|
||||||
|
|
|
@ -1,48 +1,53 @@
|
||||||
package us.shandian.giga.get;
|
package us.shandian.giga.get;
|
||||||
|
|
||||||
public interface DownloadManager
|
public interface DownloadManager {
|
||||||
{
|
int BLOCK_SIZE = 512 * 1024;
|
||||||
int BLOCK_SIZE = 512 * 1024;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start a new download mission
|
* Start a new download mission
|
||||||
* @param url the url to download
|
*
|
||||||
* @param location the location
|
* @param url the url to download
|
||||||
* @param name the name of the file to create
|
* @param location the location
|
||||||
* @param isAudio true if the download is an audio file
|
* @param name the name of the file to create
|
||||||
* @param threads the number of threads maximal used to download chunks of the file. @return the identifier of the mission.
|
* @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);
|
int startMission(String url, String location, String name, boolean isAudio, int threads);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resume the execution of a download mission.
|
* Resume the execution of a download mission.
|
||||||
* @param id the identifier of the mission to resume.
|
*
|
||||||
*/
|
* @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);
|
void resumeMission(int id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the mission from the downloaded list but keeps the downloaded file.
|
* Pause the execution of a download mission.
|
||||||
* @param id The mission identifier
|
*
|
||||||
|
* @param id the identifier of the mission to pause.
|
||||||
*/
|
*/
|
||||||
void deleteMission(int id);
|
void pauseMission(int id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the download mission by its identifier
|
* Deletes the mission from the downloaded list but keeps the downloaded file.
|
||||||
* @param id the identifier of the download mission
|
*
|
||||||
* @return the download mission or null if the mission doesn't exist
|
* @param id The mission identifier
|
||||||
*/
|
*/
|
||||||
DownloadMission getMission(int id);
|
void deleteMission(int id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the number of download missions.
|
* Get the download mission by its identifier
|
||||||
* @return the number of download missions.
|
*
|
||||||
|
* @param id the identifier of the download mission
|
||||||
|
* @return the download mission or null if the mission doesn't exist
|
||||||
*/
|
*/
|
||||||
int getCount();
|
DownloadMission getMission(int id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of download missions.
|
||||||
|
*
|
||||||
|
* @return the number of download missions.
|
||||||
|
*/
|
||||||
|
int getCount();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,95 +18,96 @@ import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import us.shandian.giga.util.Utility;
|
import us.shandian.giga.util.Utility;
|
||||||
|
|
||||||
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
||||||
|
|
||||||
public class DownloadManagerImpl implements DownloadManager
|
public class DownloadManagerImpl implements DownloadManager {
|
||||||
{
|
private static final String TAG = DownloadManagerImpl.class.getSimpleName();
|
||||||
private static final String TAG = DownloadManagerImpl.class.getSimpleName();
|
private final DownloadDataSource mDownloadDataSource;
|
||||||
private final DownloadDataSource mDownloadDataSource;
|
|
||||||
|
|
||||||
private final ArrayList<DownloadMission> mMissions = new ArrayList<DownloadMission>();
|
private final ArrayList<DownloadMission> mMissions = new ArrayList<DownloadMission>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new instance
|
* Create a new instance
|
||||||
* @param searchLocations the directories to search for unfinished downloads
|
*
|
||||||
|
* @param searchLocations the directories to search for unfinished downloads
|
||||||
* @param downloadDataSource the data source for finished downloads
|
* @param downloadDataSource the data source for finished downloads
|
||||||
*/
|
*/
|
||||||
public DownloadManagerImpl(Collection<String> searchLocations, DownloadDataSource downloadDataSource) {
|
public DownloadManagerImpl(Collection<String> searchLocations, DownloadDataSource downloadDataSource) {
|
||||||
mDownloadDataSource = downloadDataSource;
|
mDownloadDataSource = downloadDataSource;
|
||||||
loadMissions(searchLocations);
|
loadMissions(searchLocations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int startMission(String url, String location, String name, boolean isAudio, int threads) {
|
public int startMission(String url, String location, String name, boolean isAudio, int threads) {
|
||||||
DownloadMission existingMission = getMissionByLocation(location, name);
|
DownloadMission existingMission = getMissionByLocation(location, name);
|
||||||
if(existingMission != null) {
|
if (existingMission != null) {
|
||||||
// Already downloaded or downloading
|
// Already downloaded or downloading
|
||||||
if(existingMission.finished) {
|
if (existingMission.finished) {
|
||||||
// Overwrite mission
|
// Overwrite mission
|
||||||
deleteMission(mMissions.indexOf(existingMission));
|
deleteMission(mMissions.indexOf(existingMission));
|
||||||
} else {
|
} else {
|
||||||
// Rename file (?)
|
// Rename file (?)
|
||||||
try {
|
try {
|
||||||
name = generateUniqueName(location, name);
|
name = generateUniqueName(location, name);
|
||||||
}catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Unable to generate unique name", e);
|
Log.e(TAG, "Unable to generate unique name", e);
|
||||||
name = System.currentTimeMillis() + name ;
|
name = System.currentTimeMillis() + name;
|
||||||
Log.i(TAG, "Using " + name);
|
Log.i(TAG, "Using " + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadMission mission = new DownloadMission(name, url, location);
|
DownloadMission mission = new DownloadMission(name, url, location);
|
||||||
mission.timestamp = System.currentTimeMillis();
|
mission.timestamp = System.currentTimeMillis();
|
||||||
mission.threadCount = threads;
|
mission.threadCount = threads;
|
||||||
mission.addListener(new MissionListener(mission));
|
mission.addListener(new MissionListener(mission));
|
||||||
new Initializer(mission).start();
|
new Initializer(mission).start();
|
||||||
return insertMission(mission);
|
return insertMission(mission);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resumeMission(int i) {
|
public void resumeMission(int i) {
|
||||||
DownloadMission d = getMission(i);
|
DownloadMission d = getMission(i);
|
||||||
if (!d.running && d.errCode == -1) {
|
if (!d.running && d.errCode == -1) {
|
||||||
d.start();
|
d.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void pauseMission(int i) {
|
public void pauseMission(int i) {
|
||||||
DownloadMission d = getMission(i);
|
DownloadMission d = getMission(i);
|
||||||
if (d.running) {
|
if (d.running) {
|
||||||
d.pause();
|
d.pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteMission(int i) {
|
public void deleteMission(int i) {
|
||||||
DownloadMission mission = getMission(i);
|
DownloadMission mission = getMission(i);
|
||||||
if(mission.finished) {
|
if (mission.finished) {
|
||||||
mDownloadDataSource.deleteMission(mission);
|
mDownloadDataSource.deleteMission(mission);
|
||||||
}
|
}
|
||||||
mission.delete();
|
mission.delete();
|
||||||
mMissions.remove(i);
|
mMissions.remove(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadMissions(Iterable<String> searchLocations) {
|
private void loadMissions(Iterable<String> searchLocations) {
|
||||||
mMissions.clear();
|
mMissions.clear();
|
||||||
loadFinishedMissions();
|
loadFinishedMissions();
|
||||||
for(String location: searchLocations) {
|
for (String location : searchLocations) {
|
||||||
loadMissions(location);
|
loadMissions(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads finished missions from the data source
|
* Loads finished missions from the data source
|
||||||
*/
|
*/
|
||||||
private void loadFinishedMissions() {
|
private void loadFinishedMissions() {
|
||||||
List<DownloadMission> finishedMissions = mDownloadDataSource.loadMissions();
|
List<DownloadMission> finishedMissions = mDownloadDataSource.loadMissions();
|
||||||
if(finishedMissions == null) {
|
if (finishedMissions == null) {
|
||||||
finishedMissions = new ArrayList<>();
|
finishedMissions = new ArrayList<>();
|
||||||
}
|
}
|
||||||
// Ensure its sorted
|
// Ensure its sorted
|
||||||
|
@ -117,251 +118,255 @@ public class DownloadManagerImpl implements DownloadManager
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mMissions.ensureCapacity(mMissions.size() + finishedMissions.size());
|
mMissions.ensureCapacity(mMissions.size() + finishedMissions.size());
|
||||||
for(DownloadMission mission: finishedMissions) {
|
for (DownloadMission mission : finishedMissions) {
|
||||||
File downloadedFile = mission.getDownloadedFile();
|
File downloadedFile = mission.getDownloadedFile();
|
||||||
if(!downloadedFile.isFile()) {
|
if (!downloadedFile.isFile()) {
|
||||||
if(DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "downloaded file removed: " + downloadedFile.getAbsolutePath());
|
Log.d(TAG, "downloaded file removed: " + downloadedFile.getAbsolutePath());
|
||||||
}
|
}
|
||||||
mDownloadDataSource.deleteMission(mission);
|
mDownloadDataSource.deleteMission(mission);
|
||||||
} else {
|
} else {
|
||||||
mission.length = downloadedFile.length();
|
mission.length = downloadedFile.length();
|
||||||
mission.finished = true;
|
mission.finished = true;
|
||||||
mission.running = false;
|
mission.running = false;
|
||||||
mMissions.add(mission);
|
mMissions.add(mission);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadMissions(String location) {
|
private void loadMissions(String location) {
|
||||||
|
|
||||||
File f = new File(location);
|
File f = new File(location);
|
||||||
|
|
||||||
if (f.exists() && f.isDirectory()) {
|
if (f.exists() && f.isDirectory()) {
|
||||||
File[] subs = f.listFiles();
|
File[] subs = f.listFiles();
|
||||||
|
|
||||||
if(subs == null) {
|
if (subs == null) {
|
||||||
Log.e(TAG, "listFiles() returned null");
|
Log.e(TAG, "listFiles() returned null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (File sub : subs) {
|
for (File sub : subs) {
|
||||||
if (sub.isFile() && sub.getName().endsWith(".giga")) {
|
if (sub.isFile() && sub.getName().endsWith(".giga")) {
|
||||||
String str = Utility.readFromFile(sub.getAbsolutePath());
|
String str = Utility.readFromFile(sub.getAbsolutePath());
|
||||||
if (str != null && !str.trim().equals("")) {
|
if (str != null && !str.trim().equals("")) {
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "loading mission " + sub.getName());
|
Log.d(TAG, "loading mission " + sub.getName());
|
||||||
Log.d(TAG, str);
|
Log.d(TAG, str);
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadMission mis = new Gson().fromJson(str, DownloadMission.class);
|
DownloadMission mis = new Gson().fromJson(str, DownloadMission.class);
|
||||||
|
|
||||||
if (mis.finished) {
|
if (mis.finished) {
|
||||||
if(!sub.delete()) {
|
if (!sub.delete()) {
|
||||||
Log.w(TAG, "Unable to delete .giga file: " + sub.getPath());
|
Log.w(TAG, "Unable to delete .giga file: " + sub.getPath());
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
mis.running = false;
|
mis.running = false;
|
||||||
mis.recovered = true;
|
mis.recovered = true;
|
||||||
insertMission(mis);
|
insertMission(mis);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DownloadMission getMission(int i) {
|
public DownloadMission getMission(int i) {
|
||||||
return mMissions.get(i);
|
return mMissions.get(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
return mMissions.size();
|
return mMissions.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int insertMission(DownloadMission mission) {
|
private int insertMission(DownloadMission mission) {
|
||||||
int i = -1;
|
int i = -1;
|
||||||
|
|
||||||
DownloadMission m = null;
|
DownloadMission m = null;
|
||||||
|
|
||||||
if (mMissions.size() > 0) {
|
if (mMissions.size() > 0) {
|
||||||
do {
|
do {
|
||||||
m = mMissions.get(++i);
|
m = mMissions.get(++i);
|
||||||
} while (m.timestamp > mission.timestamp && i < mMissions.size() - 1);
|
} while (m.timestamp > mission.timestamp && i < mMissions.size() - 1);
|
||||||
|
|
||||||
//if (i > 0) i--;
|
//if (i > 0) i--;
|
||||||
} else {
|
} else {
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
mMissions.add(i, mission);
|
mMissions.add(i, mission);
|
||||||
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a mission by its location and name
|
* Get a mission by its location and name
|
||||||
* @param location the location
|
*
|
||||||
* @param name the name
|
* @param location the location
|
||||||
* @return the mission or null if no such mission exists
|
* @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) {
|
private
|
||||||
int dotIndex = name.lastIndexOf('.');
|
@Nullable
|
||||||
if(dotIndex <= 0 || (dotIndex == name.length() - 1)) {
|
DownloadMission getMissionByLocation(String location, String name) {
|
||||||
return new String[]{name, ""};
|
for (DownloadMission mission : mMissions) {
|
||||||
} else {
|
if (location.equals(mission.location) && name.equals(mission.name)) {
|
||||||
return new String[]{name.substring(0, dotIndex), name.substring(dotIndex + 1)};
|
return mission;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a unique file name.
|
* Splits the filename into name and extension
|
||||||
*
|
* <p>
|
||||||
* e.g. "myname (1).txt" if the name "myname.txt" exists.
|
* Dots are ignored if they appear: not at all, at the beginning of the file,
|
||||||
* @param location the location (to check for existing files)
|
* at the end of the file
|
||||||
* @param name the name 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.
|
||||||
|
* <p>
|
||||||
|
* 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
|
* @return the unique file name
|
||||||
* @throws IllegalArgumentException if the location is not a directory
|
* @throws IllegalArgumentException if the location is not a directory
|
||||||
* @throws SecurityException if the location is not readable
|
* @throws SecurityException if the location is not readable
|
||||||
*/
|
*/
|
||||||
private static String generateUniqueName(String location, String name) {
|
private static String generateUniqueName(String location, String name) {
|
||||||
if(location == null) throw new NullPointerException("location is null");
|
if (location == null) throw new NullPointerException("location is null");
|
||||||
if(name == null) throw new NullPointerException("name is null");
|
if (name == null) throw new NullPointerException("name is null");
|
||||||
File destination = new File(location);
|
File destination = new File(location);
|
||||||
if(!destination.isDirectory()) {
|
if (!destination.isDirectory()) {
|
||||||
throw new IllegalArgumentException("location is not a directory: " + location);
|
throw new IllegalArgumentException("location is not a directory: " + location);
|
||||||
}
|
}
|
||||||
final String[] nameParts = splitName(name);
|
final String[] nameParts = splitName(name);
|
||||||
String[] existingName = destination.list(new FilenameFilter() {
|
String[] existingName = destination.list(new FilenameFilter() {
|
||||||
@Override
|
@Override
|
||||||
public boolean accept(File dir, String name) {
|
public boolean accept(File dir, String name) {
|
||||||
return name.startsWith(nameParts[0]);
|
return name.startsWith(nameParts[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Arrays.sort(existingName);
|
Arrays.sort(existingName);
|
||||||
String newName;
|
String newName;
|
||||||
int downloadIndex = 0;
|
int downloadIndex = 0;
|
||||||
do {
|
do {
|
||||||
newName = nameParts[0] + " (" + downloadIndex + ")." + nameParts[1];
|
newName = nameParts[0] + " (" + downloadIndex + ")." + nameParts[1];
|
||||||
++downloadIndex;
|
++downloadIndex;
|
||||||
if(downloadIndex == 1000) { // Probably an error on our side
|
if (downloadIndex == 1000) { // Probably an error on our side
|
||||||
throw new RuntimeException("Too many existing files");
|
throw new RuntimeException("Too many existing files");
|
||||||
}
|
}
|
||||||
} while (Arrays.binarySearch(existingName, newName) >= 0);
|
} while (Arrays.binarySearch(existingName, newName) >= 0);
|
||||||
return newName;
|
return newName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Initializer extends Thread {
|
private class Initializer extends Thread {
|
||||||
private DownloadMission mission;
|
private DownloadMission mission;
|
||||||
|
|
||||||
public Initializer(DownloadMission mission) {
|
|
||||||
this.mission = mission;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
URL url = new URL(mission.url);
|
|
||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
||||||
mission.length = conn.getContentLength();
|
|
||||||
|
|
||||||
if (mission.length <= 0) {
|
|
||||||
mission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED;
|
|
||||||
//mission.notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open again
|
|
||||||
conn = (HttpURLConnection) url.openConnection();
|
|
||||||
conn.setRequestProperty("Range", "bytes=" + (mission.length - 10) + "-" + mission.length);
|
|
||||||
|
|
||||||
if (conn.getResponseCode() != 206) {
|
|
||||||
// Fallback to single thread if no partial content support
|
|
||||||
mission.fallback = true;
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "falling back");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "response = " + conn.getResponseCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
mission.blocks = mission.length / BLOCK_SIZE;
|
|
||||||
|
|
||||||
if (mission.threadCount > mission.blocks) {
|
|
||||||
mission.threadCount = (int) mission.blocks;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mission.threadCount <= 0) {
|
|
||||||
mission.threadCount = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mission.blocks * BLOCK_SIZE < mission.length) {
|
|
||||||
mission.blocks++;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
new File(mission.location).mkdirs();
|
public Initializer(DownloadMission mission) {
|
||||||
new File(mission.location + "/" + mission.name).createNewFile();
|
this.mission = mission;
|
||||||
RandomAccessFile af = new RandomAccessFile(mission.location + "/" + mission.name, "rw");
|
}
|
||||||
af.setLength(mission.length);
|
|
||||||
af.close();
|
|
||||||
|
|
||||||
mission.start();
|
|
||||||
} catch (Exception e) {
|
|
||||||
// TODO Notify
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Waits for mission to finish to add it to the {@link #mDownloadDataSource}
|
public void run() {
|
||||||
*/
|
try {
|
||||||
private class MissionListener implements DownloadMission.MissionListener {
|
URL url = new URL(mission.url);
|
||||||
private final DownloadMission mMission;
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
|
mission.length = conn.getContentLength();
|
||||||
|
|
||||||
private MissionListener(DownloadMission mission) {
|
if (mission.length <= 0) {
|
||||||
if(mission == null) throw new NullPointerException("mission is null");
|
mission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED;
|
||||||
// Could the mission be passed in onFinish()?
|
//mission.notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
|
||||||
mMission = mission;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// Open again
|
||||||
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
|
conn = (HttpURLConnection) url.openConnection();
|
||||||
}
|
conn.setRequestProperty("Range", "bytes=" + (mission.length - 10) + "-" + mission.length);
|
||||||
|
|
||||||
@Override
|
if (conn.getResponseCode() != 206) {
|
||||||
public void onFinish(DownloadMission downloadMission) {
|
// Fallback to single thread if no partial content support
|
||||||
mDownloadDataSource.addMission(mMission);
|
mission.fallback = true;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
if (DEBUG) {
|
||||||
public void onError(DownloadMission downloadMission, int errCode) {
|
Log.d(TAG, "falling back");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "response = " + conn.getResponseCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
mission.blocks = mission.length / BLOCK_SIZE;
|
||||||
|
|
||||||
|
if (mission.threadCount > mission.blocks) {
|
||||||
|
mission.threadCount = (int) mission.blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mission.threadCount <= 0) {
|
||||||
|
mission.threadCount = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mission.blocks * BLOCK_SIZE < mission.length) {
|
||||||
|
mission.blocks++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
new File(mission.location).mkdirs();
|
||||||
|
new File(mission.location + "/" + mission.name).createNewFile();
|
||||||
|
RandomAccessFile af = new RandomAccessFile(mission.location + "/" + mission.name, "rw");
|
||||||
|
af.setLength(mission.length);
|
||||||
|
af.close();
|
||||||
|
|
||||||
|
mission.start();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// TODO Notify
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,311 +18,315 @@ import us.shandian.giga.util.Utility;
|
||||||
|
|
||||||
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
||||||
|
|
||||||
public class DownloadMission
|
public class DownloadMission {
|
||||||
{
|
private static final String TAG = DownloadMission.class.getSimpleName();
|
||||||
private static final String TAG = DownloadMission.class.getSimpleName();
|
|
||||||
|
|
||||||
public interface MissionListener {
|
public interface MissionListener {
|
||||||
HashMap<MissionListener, Handler> handlerStore = new HashMap<>();
|
HashMap<MissionListener, Handler> handlerStore = new HashMap<>();
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
void onProgressUpdate(DownloadMission downloadMission, long done, long total);
|
||||||
* The filename
|
|
||||||
*/
|
|
||||||
public String name;
|
|
||||||
|
|
||||||
/**
|
void onFinish(DownloadMission downloadMission);
|
||||||
* The url of the file to download
|
|
||||||
*/
|
|
||||||
public String url;
|
|
||||||
|
|
||||||
/**
|
void onError(DownloadMission downloadMission, int errCode);
|
||||||
* The directory to store the download
|
}
|
||||||
*/
|
|
||||||
public String location;
|
|
||||||
|
|
||||||
/**
|
public static final int ERROR_SERVER_UNSUPPORTED = 206;
|
||||||
* Number of blocks the size of {@link DownloadManager#BLOCK_SIZE}
|
public static final int ERROR_UNKNOWN = 233;
|
||||||
*/
|
|
||||||
public long blocks;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of bytes
|
* The filename
|
||||||
*/
|
|
||||||
public long length;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of bytes downloaded
|
|
||||||
*/
|
|
||||||
public long done;
|
|
||||||
public int threadCount = 3;
|
|
||||||
public int finishCount;
|
|
||||||
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;
|
|
||||||
public int errCode = -1;
|
|
||||||
public long timestamp;
|
|
||||||
|
|
||||||
public transient boolean recovered;
|
|
||||||
|
|
||||||
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) {
|
public String name;
|
||||||
checkBlock(block);
|
|
||||||
return blockState.containsKey(block) ? blockState.get(block) : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void preserveBlock(long block) {
|
|
||||||
checkBlock(block);
|
|
||||||
synchronized (blockState) {
|
|
||||||
blockState.put(block, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the download position of the file
|
* The url of the file to download
|
||||||
* @param threadId the identifier of the thread
|
|
||||||
* @param position the download position of the thread
|
|
||||||
*/
|
*/
|
||||||
public void setPosition(int threadId, long position) {
|
public String url;
|
||||||
threadPositions.set(threadId, position);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the position of a thread
|
* The directory to store the download
|
||||||
* @param threadId the identifier of the thread
|
|
||||||
* @return the position for the thread
|
|
||||||
*/
|
*/
|
||||||
public long getPosition(int threadId) {
|
public String location;
|
||||||
return threadPositions.get(threadId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void notifyProgress(long deltaLen) {
|
|
||||||
if (!running) return;
|
|
||||||
|
|
||||||
if (recovered) {
|
|
||||||
recovered = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
done += deltaLen;
|
|
||||||
|
|
||||||
if (done > length) {
|
|
||||||
done = length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (done != length) {
|
|
||||||
writeThisToFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (WeakReference<MissionListener> ref: mListeners) {
|
|
||||||
final MissionListener listener = ref.get();
|
|
||||||
if (listener != null) {
|
|
||||||
MissionListener.handlerStore.get(listener).post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
listener.onProgressUpdate(DownloadMission.this, done, length);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by a download thread when it finished.
|
* Number of blocks the size of {@link DownloadManager#BLOCK_SIZE}
|
||||||
*/
|
|
||||||
public synchronized void notifyFinished() {
|
|
||||||
if (errCode > 0) return;
|
|
||||||
|
|
||||||
finishCount++;
|
|
||||||
|
|
||||||
if (finishCount == threadCount) {
|
|
||||||
onFinish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when all parts are downloaded
|
|
||||||
*/
|
|
||||||
private void onFinish() {
|
|
||||||
if (errCode > 0) return;
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onFinish");
|
|
||||||
}
|
|
||||||
|
|
||||||
running = false;
|
|
||||||
finished = true;
|
|
||||||
|
|
||||||
deleteThisFromFile();
|
|
||||||
|
|
||||||
for (WeakReference<MissionListener> ref : mListeners) {
|
|
||||||
final MissionListener listener = ref.get();
|
|
||||||
if (listener != null) {
|
|
||||||
MissionListener.handlerStore.get(listener).post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
listener.onFinish(DownloadMission.this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void notifyError(int err) {
|
|
||||||
errCode = err;
|
|
||||||
|
|
||||||
writeThisToFile();
|
|
||||||
|
|
||||||
for (WeakReference<MissionListener> ref : mListeners) {
|
|
||||||
final MissionListener listener = ref.get();
|
|
||||||
MissionListener.handlerStore.get(listener).post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
listener.onError(DownloadMission.this, errCode);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void addListener(MissionListener listener) {
|
|
||||||
Handler handler = new Handler(Looper.getMainLooper());
|
|
||||||
MissionListener.handlerStore.put(listener, handler);
|
|
||||||
mListeners.add(new WeakReference<MissionListener>(listener));
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void removeListener(MissionListener listener) {
|
|
||||||
for (Iterator<WeakReference<MissionListener>> iterator = mListeners.iterator();
|
|
||||||
iterator.hasNext(); ) {
|
|
||||||
WeakReference<MissionListener> weakRef = iterator.next();
|
|
||||||
if (listener!=null && listener == weakRef.get())
|
|
||||||
{
|
|
||||||
iterator.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start downloading with multiple threads.
|
|
||||||
*/
|
|
||||||
public void start() {
|
|
||||||
if (!running && !finished) {
|
|
||||||
running = true;
|
|
||||||
|
|
||||||
if (!fallback) {
|
|
||||||
for (int i = 0; i < threadCount; i++) {
|
|
||||||
if (threadPositions.size() <= i && !recovered) {
|
|
||||||
threadPositions.add((long) i);
|
|
||||||
}
|
|
||||||
new Thread(new DownloadRunnable(this, i)).start();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// In fallback mode, resuming is not supported.
|
|
||||||
threadCount = 1;
|
|
||||||
done = 0;
|
|
||||||
blocks = 0;
|
|
||||||
new Thread(new DownloadRunnableFallback(this)).start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pause() {
|
|
||||||
if (running) {
|
|
||||||
running = false;
|
|
||||||
recovered = true;
|
|
||||||
|
|
||||||
// TODO: Notify & Write state to info file
|
|
||||||
// if (err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the file and the meta file
|
|
||||||
*/
|
|
||||||
public void delete() {
|
|
||||||
deleteThisFromFile();
|
|
||||||
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;
|
|
||||||
new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
doWriteThisToFile();
|
|
||||||
mWritingToFile = false;
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write this {@link DownloadMission} to the meta file.
|
|
||||||
*/
|
|
||||||
private void doWriteThisToFile() {
|
|
||||||
synchronized (blockState) {
|
|
||||||
Utility.writeToFile(getMetaFilename(), new Gson().toJson(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteThisFromFile() {
|
|
||||||
new File(getMetaFilename()).delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the path of the meta file
|
|
||||||
* @return the path to the meta file
|
|
||||||
*/
|
*/
|
||||||
private String getMetaFilename() {
|
public long blocks;
|
||||||
return location + "/" + name + ".giga";
|
|
||||||
}
|
|
||||||
|
|
||||||
public File getDownloadedFile() {
|
/**
|
||||||
return new File(location, name);
|
* Number of bytes
|
||||||
}
|
*/
|
||||||
|
public long length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of bytes downloaded
|
||||||
|
*/
|
||||||
|
public long done;
|
||||||
|
public int threadCount = 3;
|
||||||
|
public int finishCount;
|
||||||
|
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;
|
||||||
|
public int errCode = -1;
|
||||||
|
public long timestamp;
|
||||||
|
|
||||||
|
public transient boolean recovered;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
if (!running) return;
|
||||||
|
|
||||||
|
if (recovered) {
|
||||||
|
recovered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
done += deltaLen;
|
||||||
|
|
||||||
|
if (done > length) {
|
||||||
|
done = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (done != length) {
|
||||||
|
writeThisToFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (WeakReference<MissionListener> ref : mListeners) {
|
||||||
|
final MissionListener listener = ref.get();
|
||||||
|
if (listener != null) {
|
||||||
|
MissionListener.handlerStore.get(listener).post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
listener.onProgressUpdate(DownloadMission.this, done, length);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by a download thread when it finished.
|
||||||
|
*/
|
||||||
|
public synchronized void notifyFinished() {
|
||||||
|
if (errCode > 0) return;
|
||||||
|
|
||||||
|
finishCount++;
|
||||||
|
|
||||||
|
if (finishCount == threadCount) {
|
||||||
|
onFinish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when all parts are downloaded
|
||||||
|
*/
|
||||||
|
private void onFinish() {
|
||||||
|
if (errCode > 0) return;
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onFinish");
|
||||||
|
}
|
||||||
|
|
||||||
|
running = false;
|
||||||
|
finished = true;
|
||||||
|
|
||||||
|
deleteThisFromFile();
|
||||||
|
|
||||||
|
for (WeakReference<MissionListener> ref : mListeners) {
|
||||||
|
final MissionListener listener = ref.get();
|
||||||
|
if (listener != null) {
|
||||||
|
MissionListener.handlerStore.get(listener).post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
listener.onFinish(DownloadMission.this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void notifyError(int err) {
|
||||||
|
errCode = err;
|
||||||
|
|
||||||
|
writeThisToFile();
|
||||||
|
|
||||||
|
for (WeakReference<MissionListener> ref : mListeners) {
|
||||||
|
final MissionListener listener = ref.get();
|
||||||
|
MissionListener.handlerStore.get(listener).post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
listener.onError(DownloadMission.this, errCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void addListener(MissionListener listener) {
|
||||||
|
Handler handler = new Handler(Looper.getMainLooper());
|
||||||
|
MissionListener.handlerStore.put(listener, handler);
|
||||||
|
mListeners.add(new WeakReference<MissionListener>(listener));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void removeListener(MissionListener listener) {
|
||||||
|
for (Iterator<WeakReference<MissionListener>> iterator = mListeners.iterator();
|
||||||
|
iterator.hasNext(); ) {
|
||||||
|
WeakReference<MissionListener> weakRef = iterator.next();
|
||||||
|
if (listener != null && listener == weakRef.get()) {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start downloading with multiple threads.
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
if (!running && !finished) {
|
||||||
|
running = true;
|
||||||
|
|
||||||
|
if (!fallback) {
|
||||||
|
for (int i = 0; i < threadCount; i++) {
|
||||||
|
if (threadPositions.size() <= i && !recovered) {
|
||||||
|
threadPositions.add((long) i);
|
||||||
|
}
|
||||||
|
new Thread(new DownloadRunnable(this, i)).start();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// In fallback mode, resuming is not supported.
|
||||||
|
threadCount = 1;
|
||||||
|
done = 0;
|
||||||
|
blocks = 0;
|
||||||
|
new Thread(new DownloadRunnableFallback(this)).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pause() {
|
||||||
|
if (running) {
|
||||||
|
running = false;
|
||||||
|
recovered = true;
|
||||||
|
|
||||||
|
// TODO: Notify & Write state to info file
|
||||||
|
// if (err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the file and the meta file
|
||||||
|
*/
|
||||||
|
public void delete() {
|
||||||
|
deleteThisFromFile();
|
||||||
|
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;
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
doWriteThisToFile();
|
||||||
|
mWritingToFile = false;
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write this {@link DownloadMission} to the meta file.
|
||||||
|
*/
|
||||||
|
private void doWriteThisToFile() {
|
||||||
|
synchronized (blockState) {
|
||||||
|
Utility.writeToFile(getMetaFilename(), new Gson().toJson(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteThisFromFile() {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,166 +13,165 @@ import static org.schabi.newpipe.BuildConfig.DEBUG;
|
||||||
* Runnable to download blocks of a file until the file is completely downloaded,
|
* Runnable to download blocks of a file until the file is completely downloaded,
|
||||||
* an error occurs or the process is stopped.
|
* an error occurs or the process is stopped.
|
||||||
*/
|
*/
|
||||||
public class DownloadRunnable implements Runnable
|
public class DownloadRunnable implements Runnable {
|
||||||
{
|
private static final String TAG = DownloadRunnable.class.getSimpleName();
|
||||||
private static final String TAG = DownloadRunnable.class.getSimpleName();
|
|
||||||
|
private final DownloadMission mMission;
|
||||||
private final DownloadMission mMission;
|
private final int mId;
|
||||||
private final int mId;
|
|
||||||
|
public DownloadRunnable(DownloadMission mission, int id) {
|
||||||
public DownloadRunnable(DownloadMission mission, int id) {
|
if (mission == null) throw new NullPointerException("mission is null");
|
||||||
if(mission == null) throw new NullPointerException("mission is null");
|
mMission = mission;
|
||||||
mMission = mission;
|
mId = id;
|
||||||
mId = id;
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
public void run() {
|
||||||
public void run() {
|
boolean retry = mMission.recovered;
|
||||||
boolean retry = mMission.recovered;
|
long position = mMission.getPosition(mId);
|
||||||
long position = mMission.getPosition(mId);
|
|
||||||
|
if (DEBUG) {
|
||||||
if (DEBUG) {
|
Log.d(TAG, mId + ":default pos " + position);
|
||||||
Log.d(TAG, mId + ":default pos " + position);
|
Log.d(TAG, mId + ":recovered: " + mMission.recovered);
|
||||||
Log.d(TAG, mId + ":recovered: " + mMission.recovered);
|
}
|
||||||
}
|
|
||||||
|
while (mMission.errCode == -1 && mMission.running && position < mMission.blocks) {
|
||||||
while (mMission.errCode == -1 && mMission.running && position < mMission.blocks) {
|
|
||||||
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
mMission.pause();
|
||||||
mMission.pause();
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
if (DEBUG && retry) {
|
||||||
if (DEBUG && retry) {
|
Log.d(TAG, mId + ":retry is true. Resuming at " + position);
|
||||||
Log.d(TAG, mId + ":retry is true. Resuming at " + position);
|
}
|
||||||
}
|
|
||||||
|
// Wait for an unblocked position
|
||||||
// Wait for an unblocked position
|
while (!retry && position < mMission.blocks && mMission.isBlockPreserved(position)) {
|
||||||
while (!retry && position < mMission.blocks && mMission.isBlockPreserved(position)) {
|
|
||||||
|
if (DEBUG) {
|
||||||
if (DEBUG) {
|
Log.d(TAG, mId + ":position " + position + " preserved, passing");
|
||||||
Log.d(TAG, mId + ":position " + position + " preserved, passing");
|
}
|
||||||
}
|
|
||||||
|
position++;
|
||||||
position++;
|
}
|
||||||
}
|
|
||||||
|
retry = false;
|
||||||
retry = false;
|
|
||||||
|
if (position >= mMission.blocks) {
|
||||||
if (position >= mMission.blocks) {
|
break;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
|
if (DEBUG) {
|
||||||
if (DEBUG) {
|
Log.d(TAG, mId + ":preserving position " + position);
|
||||||
Log.d(TAG, mId + ":preserving position " + position);
|
}
|
||||||
}
|
|
||||||
|
mMission.preserveBlock(position);
|
||||||
mMission.preserveBlock(position);
|
mMission.setPosition(mId, position);
|
||||||
mMission.setPosition(mId, position);
|
|
||||||
|
long start = position * DownloadManager.BLOCK_SIZE;
|
||||||
long start = position * DownloadManager.BLOCK_SIZE;
|
long end = start + DownloadManager.BLOCK_SIZE - 1;
|
||||||
long end = start + DownloadManager.BLOCK_SIZE - 1;
|
|
||||||
|
if (end >= mMission.length) {
|
||||||
if (end >= mMission.length) {
|
end = mMission.length - 1;
|
||||||
end = mMission.length - 1;
|
}
|
||||||
}
|
|
||||||
|
HttpURLConnection conn = null;
|
||||||
HttpURLConnection conn = null;
|
|
||||||
|
int total = 0;
|
||||||
int total = 0;
|
|
||||||
|
try {
|
||||||
try {
|
URL url = new URL(mMission.url);
|
||||||
URL url = new URL(mMission.url);
|
conn = (HttpURLConnection) url.openConnection();
|
||||||
conn = (HttpURLConnection) url.openConnection();
|
conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
|
||||||
conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
|
|
||||||
|
if (DEBUG) {
|
||||||
if (DEBUG) {
|
Log.d(TAG, mId + ":" + conn.getRequestProperty("Range"));
|
||||||
Log.d(TAG, mId + ":" + conn.getRequestProperty("Range"));
|
Log.d(TAG, mId + ":Content-Length=" + conn.getContentLength() + " Code:" + conn.getResponseCode());
|
||||||
Log.d(TAG, mId + ":Content-Length=" + conn.getContentLength() + " Code:" + conn.getResponseCode());
|
}
|
||||||
}
|
|
||||||
|
// A server may be ignoring the range request
|
||||||
// A server may be ignoring the range request
|
if (conn.getResponseCode() != 206) {
|
||||||
if (conn.getResponseCode() != 206) {
|
mMission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED;
|
||||||
mMission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED;
|
notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
|
||||||
notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
|
|
||||||
|
if (DEBUG) {
|
||||||
if (DEBUG) {
|
Log.e(TAG, mId + ":Unsupported " + conn.getResponseCode());
|
||||||
Log.e(TAG, mId + ":Unsupported " + conn.getResponseCode());
|
}
|
||||||
}
|
|
||||||
|
break;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
|
RandomAccessFile f = new RandomAccessFile(mMission.location + "/" + mMission.name, "rw");
|
||||||
RandomAccessFile f = new RandomAccessFile(mMission.location + "/" + mMission.name, "rw");
|
f.seek(start);
|
||||||
f.seek(start);
|
BufferedInputStream ipt = new BufferedInputStream(conn.getInputStream());
|
||||||
BufferedInputStream ipt = new BufferedInputStream(conn.getInputStream());
|
byte[] buf = new byte[512];
|
||||||
byte[] buf = new byte[512];
|
|
||||||
|
while (start < end && mMission.running) {
|
||||||
while (start < end && mMission.running) {
|
int len = ipt.read(buf, 0, 512);
|
||||||
int len = ipt.read(buf, 0, 512);
|
|
||||||
|
if (len == -1) {
|
||||||
if (len == -1) {
|
break;
|
||||||
break;
|
} else {
|
||||||
} else {
|
start += len;
|
||||||
start += len;
|
total += len;
|
||||||
total += len;
|
f.write(buf, 0, len);
|
||||||
f.write(buf, 0, len);
|
notifyProgress(len);
|
||||||
notifyProgress(len);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (DEBUG && mMission.running) {
|
||||||
if (DEBUG && mMission.running) {
|
Log.d(TAG, mId + ":position " + position + " finished, total length " + total);
|
||||||
Log.d(TAG, mId + ":position " + position + " finished, total length " + total);
|
}
|
||||||
}
|
|
||||||
|
f.close();
|
||||||
f.close();
|
ipt.close();
|
||||||
ipt.close();
|
|
||||||
|
// TODO We should save progress for each thread
|
||||||
// TODO We should save progress for each thread
|
} catch (Exception e) {
|
||||||
} catch (Exception e) {
|
// TODO Retry count limit & notify error
|
||||||
// TODO Retry count limit & notify error
|
retry = true;
|
||||||
retry = true;
|
|
||||||
|
notifyProgress(-total);
|
||||||
notifyProgress(-total);
|
|
||||||
|
if (DEBUG) {
|
||||||
if (DEBUG) {
|
Log.d(TAG, mId + ":position " + position + " retrying", e);
|
||||||
Log.d(TAG, mId + ":position " + position + " retrying", e);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (DEBUG) {
|
||||||
if (DEBUG) {
|
Log.d(TAG, "thread " + mId + " exited main loop");
|
||||||
Log.d(TAG, "thread " + mId + " exited main loop");
|
}
|
||||||
}
|
|
||||||
|
if (mMission.errCode == -1 && mMission.running) {
|
||||||
if (mMission.errCode == -1 && mMission.running) {
|
if (DEBUG) {
|
||||||
if (DEBUG) {
|
Log.d(TAG, "no error has happened, notifying");
|
||||||
Log.d(TAG, "no error has happened, notifying");
|
}
|
||||||
}
|
notifyFinished();
|
||||||
notifyFinished();
|
}
|
||||||
}
|
|
||||||
|
if (DEBUG && !mMission.running) {
|
||||||
if (DEBUG && !mMission.running) {
|
Log.d(TAG, "The mission has been paused. Passing.");
|
||||||
Log.d(TAG, "The mission has been paused. Passing.");
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private void notifyProgress(final long len) {
|
||||||
private void notifyProgress(final long len) {
|
synchronized (mMission) {
|
||||||
synchronized (mMission) {
|
mMission.notifyProgress(len);
|
||||||
mMission.notifyProgress(len);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private void notifyError(final int err) {
|
||||||
private void notifyError(final int err) {
|
synchronized (mMission) {
|
||||||
synchronized (mMission) {
|
mMission.notifyError(err);
|
||||||
mMission.notifyError(err);
|
mMission.pause();
|
||||||
mMission.pause();
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private void notifyFinished() {
|
||||||
private void notifyFinished() {
|
synchronized (mMission) {
|
||||||
synchronized (mMission) {
|
mMission.notifyFinished();
|
||||||
mMission.notifyFinished();
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,70 +6,69 @@ import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
// Single-threaded fallback mode
|
// Single-threaded fallback mode
|
||||||
public class DownloadRunnableFallback implements Runnable
|
public class DownloadRunnableFallback implements Runnable {
|
||||||
{
|
private final DownloadMission mMission;
|
||||||
private final DownloadMission mMission;
|
//private int mId;
|
||||||
//private int mId;
|
|
||||||
|
|
||||||
public DownloadRunnableFallback(DownloadMission mission) {
|
|
||||||
if(mission == null) throw new NullPointerException("mission is null");
|
|
||||||
//mId = id;
|
|
||||||
mMission = mission;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public DownloadRunnableFallback(DownloadMission mission) {
|
||||||
public void run() {
|
if (mission == null) throw new NullPointerException("mission is null");
|
||||||
try {
|
//mId = id;
|
||||||
URL url = new URL(mMission.url);
|
mMission = mission;
|
||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
}
|
||||||
|
|
||||||
if (conn.getResponseCode() != 200 && conn.getResponseCode() != 206) {
|
|
||||||
notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
|
|
||||||
} else {
|
|
||||||
RandomAccessFile f = new RandomAccessFile(mMission.location + "/" + mMission.name, "rw");
|
|
||||||
f.seek(0);
|
|
||||||
BufferedInputStream ipt = new BufferedInputStream(conn.getInputStream());
|
|
||||||
byte[] buf = new byte[512];
|
|
||||||
int len = 0;
|
|
||||||
|
|
||||||
while ((len = ipt.read(buf, 0, 512)) != -1 && mMission.running) {
|
|
||||||
f.write(buf, 0, len);
|
|
||||||
notifyProgress(len);
|
|
||||||
|
|
||||||
if (Thread.interrupted()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
f.close();
|
|
||||||
ipt.close();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
notifyError(DownloadMission.ERROR_UNKNOWN);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mMission.errCode == -1 && mMission.running) {
|
|
||||||
notifyFinished();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyProgress(final long len) {
|
|
||||||
synchronized (mMission) {
|
|
||||||
mMission.notifyProgress(len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyError(final int err) {
|
|
||||||
synchronized (mMission) {
|
|
||||||
mMission.notifyError(err);
|
|
||||||
mMission.pause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyFinished() {
|
@Override
|
||||||
synchronized (mMission) {
|
public void run() {
|
||||||
mMission.notifyFinished();
|
try {
|
||||||
}
|
URL url = new URL(mMission.url);
|
||||||
}
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
|
|
||||||
|
if (conn.getResponseCode() != 200 && conn.getResponseCode() != 206) {
|
||||||
|
notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
|
||||||
|
} else {
|
||||||
|
RandomAccessFile f = new RandomAccessFile(mMission.location + "/" + mMission.name, "rw");
|
||||||
|
f.seek(0);
|
||||||
|
BufferedInputStream ipt = new BufferedInputStream(conn.getInputStream());
|
||||||
|
byte[] buf = new byte[512];
|
||||||
|
int len = 0;
|
||||||
|
|
||||||
|
while ((len = ipt.read(buf, 0, 512)) != -1 && mMission.running) {
|
||||||
|
f.write(buf, 0, len);
|
||||||
|
notifyProgress(len);
|
||||||
|
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
f.close();
|
||||||
|
ipt.close();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
notifyError(DownloadMission.ERROR_UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mMission.errCode == -1 && mMission.running) {
|
||||||
|
notifyFinished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyProgress(final long len) {
|
||||||
|
synchronized (mMission) {
|
||||||
|
mMission.notifyProgress(len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyError(final int err) {
|
||||||
|
synchronized (mMission) {
|
||||||
|
mMission.notifyError(err);
|
||||||
|
mMission.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyFinished() {
|
||||||
|
synchronized (mMission) {
|
||||||
|
mMission.notifyFinished();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,7 @@ public class DownloadMissionSQLiteHelper extends SQLiteOpenHelper {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all values of the download mission as ContentValues.
|
* Returns all values of the download mission as ContentValues.
|
||||||
|
*
|
||||||
* @param downloadMission the download mission
|
* @param downloadMission the download mission
|
||||||
* @return the content values
|
* @return the content values
|
||||||
*/
|
*/
|
||||||
|
@ -88,7 +89,7 @@ public class DownloadMissionSQLiteHelper extends SQLiteOpenHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DownloadMission getMissionFromCursor(Cursor cursor) {
|
public static DownloadMission getMissionFromCursor(Cursor cursor) {
|
||||||
if(cursor == null) throw new NullPointerException("cursor is null");
|
if (cursor == null) throw new NullPointerException("cursor is null");
|
||||||
int pos;
|
int pos;
|
||||||
String name = cursor.getString(cursor.getColumnIndexOrThrow(KEY_NAME));
|
String name = cursor.getString(cursor.getColumnIndexOrThrow(KEY_NAME));
|
||||||
String location = cursor.getString(cursor.getColumnIndexOrThrow(KEY_LOCATION));
|
String location = cursor.getString(cursor.getColumnIndexOrThrow(KEY_LOCATION));
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class SQLiteDownloadDataSource implements DownloadDataSource {
|
||||||
null, null, null, DownloadMissionSQLiteHelper.KEY_TIMESTAMP);
|
null, null, null, DownloadMissionSQLiteHelper.KEY_TIMESTAMP);
|
||||||
|
|
||||||
int count = cursor.getCount();
|
int count = cursor.getCount();
|
||||||
if(count == 0) return new ArrayList<>();
|
if (count == 0) return new ArrayList<>();
|
||||||
result = new ArrayList<>(count);
|
result = new ArrayList<>(count);
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
result.add(DownloadMissionSQLiteHelper.getMissionFromCursor(cursor));
|
result.add(DownloadMissionSQLiteHelper.getMissionFromCursor(cursor));
|
||||||
|
@ -47,7 +47,7 @@ public class SQLiteDownloadDataSource implements DownloadDataSource {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addMission(DownloadMission downloadMission) {
|
public void addMission(DownloadMission downloadMission) {
|
||||||
if(downloadMission == null) throw new NullPointerException("downloadMission is null");
|
if (downloadMission == null) throw new NullPointerException("downloadMission is null");
|
||||||
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
|
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
|
||||||
ContentValues values = DownloadMissionSQLiteHelper.getValuesOfMission(downloadMission);
|
ContentValues values = DownloadMissionSQLiteHelper.getValuesOfMission(downloadMission);
|
||||||
database.insert(MISSIONS_TABLE_NAME, null, values);
|
database.insert(MISSIONS_TABLE_NAME, null, values);
|
||||||
|
@ -55,25 +55,25 @@ public class SQLiteDownloadDataSource implements DownloadDataSource {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateMission(DownloadMission downloadMission) {
|
public void updateMission(DownloadMission downloadMission) {
|
||||||
if(downloadMission == null) throw new NullPointerException("downloadMission is null");
|
if (downloadMission == null) throw new NullPointerException("downloadMission is null");
|
||||||
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
|
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
|
||||||
ContentValues values = DownloadMissionSQLiteHelper.getValuesOfMission(downloadMission);
|
ContentValues values = DownloadMissionSQLiteHelper.getValuesOfMission(downloadMission);
|
||||||
String whereClause = KEY_LOCATION+ " = ? AND " +
|
String whereClause = KEY_LOCATION + " = ? AND " +
|
||||||
KEY_NAME + " = ?";
|
KEY_NAME + " = ?";
|
||||||
int rowsAffected = database.update(MISSIONS_TABLE_NAME, values,
|
int rowsAffected = database.update(MISSIONS_TABLE_NAME, values,
|
||||||
whereClause, new String[]{downloadMission.location, downloadMission.name});
|
whereClause, new String[]{downloadMission.location, downloadMission.name});
|
||||||
if(rowsAffected != 1) {
|
if (rowsAffected != 1) {
|
||||||
Log.e(TAG, "Expected 1 row to be affected by update but got " + rowsAffected);
|
Log.e(TAG, "Expected 1 row to be affected by update but got " + rowsAffected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteMission(DownloadMission downloadMission) {
|
public void deleteMission(DownloadMission downloadMission) {
|
||||||
if(downloadMission == null) throw new NullPointerException("downloadMission is null");
|
if (downloadMission == null) throw new NullPointerException("downloadMission is null");
|
||||||
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
|
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
|
||||||
database.delete(MISSIONS_TABLE_NAME,
|
database.delete(MISSIONS_TABLE_NAME,
|
||||||
KEY_LOCATION + " = ? AND " +
|
KEY_LOCATION + " = ? AND " +
|
||||||
KEY_NAME + " = ?",
|
KEY_NAME + " = ?",
|
||||||
new String[]{downloadMission.location, downloadMission.name});
|
new String[]{downloadMission.location, downloadMission.name});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
@ -16,6 +15,7 @@ import android.os.HandlerThread;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.support.v4.app.NotificationCompat.Builder;
|
import android.support.v4.app.NotificationCompat.Builder;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v4.content.PermissionChecker;
|
import android.support.v4.content.PermissionChecker;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
@ -34,237 +34,235 @@ import us.shandian.giga.get.sqlite.SQLiteDownloadDataSource;
|
||||||
|
|
||||||
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
||||||
|
|
||||||
public class DownloadManagerService extends Service
|
public class DownloadManagerService extends Service {
|
||||||
{
|
|
||||||
|
|
||||||
private static final String TAG = DownloadManagerService.class.getSimpleName();
|
private static final String TAG = DownloadManagerService.class.getSimpleName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message code of update messages stored as {@link Message#what}.
|
* Message code of update messages stored as {@link Message#what}.
|
||||||
*/
|
*/
|
||||||
private static final int UPDATE_MESSAGE = 0;
|
private static final int UPDATE_MESSAGE = 0;
|
||||||
private static final int NOTIFICATION_ID = 1000;
|
private static final int NOTIFICATION_ID = 1000;
|
||||||
private static final String EXTRA_NAME = "DownloadManagerService.extra.name";
|
private static final String EXTRA_NAME = "DownloadManagerService.extra.name";
|
||||||
private static final String EXTRA_LOCATION = "DownloadManagerService.extra.location";
|
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_IS_AUDIO = "DownloadManagerService.extra.is_audio";
|
||||||
private static final String EXTRA_THREADS = "DownloadManagerService.extra.threads";
|
private static final String EXTRA_THREADS = "DownloadManagerService.extra.threads";
|
||||||
|
|
||||||
|
|
||||||
private DMBinder mBinder;
|
private DMBinder mBinder;
|
||||||
private DownloadManager mManager;
|
private DownloadManager mManager;
|
||||||
private Notification mNotification;
|
private Notification mNotification;
|
||||||
private Handler mHandler;
|
private Handler mHandler;
|
||||||
private long mLastTimeStamp = System.currentTimeMillis();
|
private long mLastTimeStamp = System.currentTimeMillis();
|
||||||
private DownloadDataSource mDataSource;
|
private DownloadDataSource mDataSource;
|
||||||
|
|
||||||
|
|
||||||
|
private MissionListener missionListener = new MissionListener();
|
||||||
private MissionListener missionListener = new MissionListener();
|
|
||||||
|
|
||||||
|
|
||||||
private void notifyMediaScanner(DownloadMission mission) {
|
private void notifyMediaScanner(DownloadMission mission) {
|
||||||
Uri uri = Uri.parse("file://" + mission.location + "/" + mission.name);
|
Uri uri = Uri.parse("file://" + mission.location + "/" + mission.name);
|
||||||
// notify media scanner on downloaded media file ...
|
// notify media scanner on downloaded media file ...
|
||||||
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
|
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onCreate");
|
Log.d(TAG, "onCreate");
|
||||||
}
|
}
|
||||||
|
|
||||||
mBinder = new DMBinder();
|
mBinder = new DMBinder();
|
||||||
if(mDataSource == null) {
|
if (mDataSource == null) {
|
||||||
mDataSource = new SQLiteDownloadDataSource(this);
|
mDataSource = new SQLiteDownloadDataSource(this);
|
||||||
}
|
}
|
||||||
if (mManager == null) {
|
if (mManager == null) {
|
||||||
ArrayList<String> paths = new ArrayList<>(2);
|
ArrayList<String> paths = new ArrayList<>(2);
|
||||||
paths.add(NewPipeSettings.getVideoDownloadPath(this));
|
paths.add(NewPipeSettings.getVideoDownloadPath(this));
|
||||||
paths.add(NewPipeSettings.getAudioDownloadPath(this));
|
paths.add(NewPipeSettings.getAudioDownloadPath(this));
|
||||||
mManager = new DownloadManagerImpl(paths, mDataSource);
|
mManager = new DownloadManagerImpl(paths, mDataSource);
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "mManager == null");
|
Log.d(TAG, "mManager == null");
|
||||||
Log.d(TAG, "Download directory: " + paths);
|
Log.d(TAG, "Download directory: " + paths);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent i = new Intent();
|
Intent i = new Intent();
|
||||||
i.setAction(Intent.ACTION_MAIN);
|
i.setAction(Intent.ACTION_MAIN);
|
||||||
i.setClass(this, DownloadActivity.class);
|
i.setClass(this, DownloadActivity.class);
|
||||||
|
|
||||||
Drawable icon = this.getResources().getDrawable(R.mipmap.ic_launcher);
|
Drawable icon = ContextCompat.getDrawable(this, R.mipmap.ic_launcher);
|
||||||
|
|
||||||
Builder builder = new Builder(this)
|
Builder builder = new Builder(this)
|
||||||
.setContentIntent(PendingIntent.getActivity(this, 0, i, 0))
|
.setContentIntent(PendingIntent.getActivity(this, 0, i, 0))
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
.setLargeIcon(((BitmapDrawable) icon).getBitmap())
|
.setLargeIcon(((BitmapDrawable) icon).getBitmap())
|
||||||
.setContentTitle(getString(R.string.msg_running))
|
.setContentTitle(getString(R.string.msg_running))
|
||||||
.setContentText(getString(R.string.msg_running_detail));
|
.setContentText(getString(R.string.msg_running_detail));
|
||||||
|
|
||||||
PendingIntent pendingIntent =
|
PendingIntent pendingIntent =
|
||||||
PendingIntent.getActivity(
|
PendingIntent.getActivity(
|
||||||
this,
|
this,
|
||||||
0,
|
0,
|
||||||
new Intent(this, DownloadActivity.class)
|
new Intent(this, DownloadActivity.class)
|
||||||
.setAction(DownloadActivity.INTENT_LIST),
|
.setAction(DownloadActivity.INTENT_LIST),
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
);
|
);
|
||||||
|
|
||||||
builder.setContentIntent(pendingIntent);
|
builder.setContentIntent(pendingIntent);
|
||||||
|
|
||||||
mNotification = builder.build();
|
mNotification = builder.build();
|
||||||
|
|
||||||
HandlerThread thread = new HandlerThread("ServiceMessenger");
|
HandlerThread thread = new HandlerThread("ServiceMessenger");
|
||||||
thread.start();
|
thread.start();
|
||||||
|
|
||||||
mHandler = new Handler(thread.getLooper()) {
|
|
||||||
@Override
|
|
||||||
public void handleMessage(Message msg) {
|
|
||||||
switch (msg.what) {
|
|
||||||
case UPDATE_MESSAGE: {
|
|
||||||
int runningCount = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < mManager.getCount(); i++) {
|
mHandler = new Handler(thread.getLooper()) {
|
||||||
if (mManager.getMission(i).running) {
|
@Override
|
||||||
runningCount++;
|
public void handleMessage(Message msg) {
|
||||||
}
|
switch (msg.what) {
|
||||||
}
|
case UPDATE_MESSAGE: {
|
||||||
updateState(runningCount);
|
int runningCount = 0;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startMissionAsync(final String url, final String location, final String name,
|
for (int i = 0; i < mManager.getCount(); i++) {
|
||||||
final boolean isAudio, final int threads) {
|
if (mManager.getMission(i).running) {
|
||||||
mHandler.post(new Runnable() {
|
runningCount++;
|
||||||
@Override
|
}
|
||||||
public void run() {
|
}
|
||||||
int missionId = mManager.startMission(url, location, name, isAudio, threads);
|
updateState(runningCount);
|
||||||
mBinder.onMissionAdded(mManager.getMission(missionId));
|
break;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private void startMissionAsync(final String url, final String location, final String name,
|
||||||
public void onDestroy() {
|
final boolean isAudio, final int threads) {
|
||||||
super.onDestroy();
|
mHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
if (DEBUG) {
|
public void run() {
|
||||||
Log.d(TAG, "Destroying");
|
int missionId = mManager.startMission(url, location, name, isAudio, threads);
|
||||||
}
|
mBinder.onMissionAdded(mManager.getMission(missionId));
|
||||||
|
}
|
||||||
for (int i = 0; i < mManager.getCount(); i++) {
|
});
|
||||||
mManager.pauseMission(i);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
stopForeground(true);
|
@Override
|
||||||
}
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
if (DEBUG) {
|
||||||
@Override
|
Log.d(TAG, "Starting");
|
||||||
public IBinder onBind(Intent intent) {
|
}
|
||||||
int permissionCheck;
|
Log.i(TAG, "Got intent: " + intent);
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
|
String action = intent.getAction();
|
||||||
permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
|
if (action != null && action.equals(Intent.ACTION_RUN)) {
|
||||||
if(permissionCheck == PermissionChecker.PERMISSION_DENIED) {
|
String name = intent.getStringExtra(EXTRA_NAME);
|
||||||
Toast.makeText(this, "Permission denied (read)", Toast.LENGTH_SHORT).show();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
@Override
|
||||||
if(permissionCheck == PermissionChecker.PERMISSION_DENIED) {
|
public void onDestroy() {
|
||||||
Toast.makeText(this, "Permission denied (write)", Toast.LENGTH_SHORT).show();
|
super.onDestroy();
|
||||||
}
|
|
||||||
|
|
||||||
return mBinder;
|
if (DEBUG) {
|
||||||
}
|
Log.d(TAG, "Destroying");
|
||||||
|
}
|
||||||
|
|
||||||
private void postUpdateMessage() {
|
for (int i = 0; i < mManager.getCount(); i++) {
|
||||||
mHandler.sendEmptyMessage(UPDATE_MESSAGE);
|
mManager.pauseMission(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateState(int runningCount) {
|
|
||||||
if (runningCount == 0) {
|
|
||||||
stopForeground(true);
|
|
||||||
} else {
|
|
||||||
startForeground(NOTIFICATION_ID, mNotification);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void startMission(Context context, String url, String location, String name, boolean isAudio, int threads) {
|
stopForeground(true);
|
||||||
Intent intent = new Intent(context, DownloadManagerService.class);
|
}
|
||||||
intent.setAction(Intent.ACTION_RUN);
|
|
||||||
intent.setData(Uri.parse(url));
|
@Override
|
||||||
intent.putExtra(EXTRA_NAME, name);
|
public IBinder onBind(Intent intent) {
|
||||||
intent.putExtra(EXTRA_LOCATION, location);
|
int permissionCheck;
|
||||||
intent.putExtra(EXTRA_IS_AUDIO, isAudio);
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
intent.putExtra(EXTRA_THREADS, threads);
|
permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||||
context.startService(intent);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postUpdateMessage() {
|
||||||
|
mHandler.sendEmptyMessage(UPDATE_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState(int runningCount) {
|
||||||
|
if (runningCount == 0) {
|
||||||
|
stopForeground(true);
|
||||||
|
} else {
|
||||||
|
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 {
|
private class MissionListener implements DownloadMission.MissionListener {
|
||||||
@Override
|
@Override
|
||||||
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
|
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long delta = now - mLastTimeStamp;
|
long delta = now - mLastTimeStamp;
|
||||||
if (delta > 2000) {
|
if (delta > 2000) {
|
||||||
postUpdateMessage();
|
postUpdateMessage();
|
||||||
mLastTimeStamp = now;
|
mLastTimeStamp = now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFinish(DownloadMission downloadMission) {
|
public void onFinish(DownloadMission downloadMission) {
|
||||||
postUpdateMessage();
|
postUpdateMessage();
|
||||||
notifyMediaScanner(downloadMission);
|
notifyMediaScanner(downloadMission);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(DownloadMission downloadMission, int errCode) {
|
public void onError(DownloadMission downloadMission, int errCode) {
|
||||||
postUpdateMessage();
|
postUpdateMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Wrapper of DownloadManager
|
// Wrapper of DownloadManager
|
||||||
public class DMBinder extends Binder {
|
public class DMBinder extends Binder {
|
||||||
public DownloadManager getDownloadManager() {
|
public DownloadManager getDownloadManager() {
|
||||||
return mManager;
|
return mManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onMissionAdded(DownloadMission mission) {
|
public void onMissionAdded(DownloadMission mission) {
|
||||||
mission.addListener(missionListener);
|
mission.addListener(missionListener);
|
||||||
postUpdateMessage();
|
postUpdateMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onMissionRemoved(DownloadMission mission) {
|
public void onMissionRemoved(DownloadMission mission) {
|
||||||
mission.removeListener(missionListener);
|
mission.removeListener(missionListener);
|
||||||
postUpdateMessage();
|
postUpdateMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.support.v4.content.FileProvider;
|
import android.support.v4.content.FileProvider;
|
||||||
|
import android.support.v4.view.ViewCompat;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
|
@ -18,14 +20,13 @@ import android.widget.ImageView;
|
||||||
import android.widget.PopupMenu;
|
import android.widget.PopupMenu;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
|
||||||
import us.shandian.giga.get.DownloadManager;
|
import us.shandian.giga.get.DownloadManager;
|
||||||
import us.shandian.giga.get.DownloadMission;
|
import us.shandian.giga.get.DownloadMission;
|
||||||
import us.shandian.giga.service.DownloadManagerService;
|
import us.shandian.giga.service.DownloadManagerService;
|
||||||
|
@ -35,341 +36,340 @@ import us.shandian.giga.util.Utility;
|
||||||
import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
|
import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
|
||||||
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
||||||
|
|
||||||
public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHolder>
|
public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHolder> {
|
||||||
{
|
private static final Map<Integer, String> ALGORITHMS = new HashMap<>();
|
||||||
private static final Map<Integer, String> ALGORITHMS = new HashMap<>();
|
private static final String TAG = "MissionAdapter";
|
||||||
private static final String TAG = "MissionAdapter";
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ALGORITHMS.put(R.id.md5, "MD5");
|
ALGORITHMS.put(R.id.md5, "MD5");
|
||||||
ALGORITHMS.put(R.id.sha1, "SHA1");
|
ALGORITHMS.put(R.id.sha1, "SHA1");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private LayoutInflater mInflater;
|
private LayoutInflater mInflater;
|
||||||
private DownloadManager mManager;
|
private DownloadManager mManager;
|
||||||
private DownloadManagerService.DMBinder mBinder;
|
private DownloadManagerService.DMBinder mBinder;
|
||||||
private int mLayout;
|
private int mLayout;
|
||||||
|
|
||||||
public MissionAdapter(Context context, DownloadManagerService.DMBinder binder, DownloadManager manager, boolean isLinear) {
|
public MissionAdapter(Context context, DownloadManagerService.DMBinder binder, DownloadManager manager, boolean isLinear) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mManager = manager;
|
mManager = manager;
|
||||||
mBinder = binder;
|
mBinder = binder;
|
||||||
|
|
||||||
mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
|
||||||
mLayout = isLinear ? R.layout.mission_item_linear : R.layout.mission_item;
|
mLayout = isLinear ? R.layout.mission_item_linear : R.layout.mission_item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MissionAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
final ViewHolder h = new ViewHolder(mInflater.inflate(mLayout, parent, false));
|
||||||
|
|
||||||
|
h.menu.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
buildPopup(h);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
@Override
|
|
||||||
public MissionAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
|
||||||
final ViewHolder h = new ViewHolder(mInflater.inflate(mLayout, parent, false));
|
|
||||||
|
|
||||||
h.menu.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
buildPopup(h);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/*h.itemView.setOnClickListener(new View.OnClickListener() {
|
/*h.itemView.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
showDetail(h);
|
showDetail(h);
|
||||||
}
|
}
|
||||||
});*/
|
});*/
|
||||||
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
return h;
|
||||||
public void onViewRecycled(MissionAdapter.ViewHolder h) {
|
}
|
||||||
super.onViewRecycled(h);
|
|
||||||
h.mission.removeListener(h.observer);
|
|
||||||
h.mission = null;
|
|
||||||
h.observer = null;
|
|
||||||
h.progress = null;
|
|
||||||
h.position = -1;
|
|
||||||
h.lastTimeStamp = -1;
|
|
||||||
h.lastDone = -1;
|
|
||||||
h.colorId = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(MissionAdapter.ViewHolder h, int pos) {
|
public void onViewRecycled(MissionAdapter.ViewHolder h) {
|
||||||
DownloadMission ms = mManager.getMission(pos);
|
super.onViewRecycled(h);
|
||||||
h.mission = ms;
|
h.mission.removeListener(h.observer);
|
||||||
h.position = pos;
|
h.mission = null;
|
||||||
|
h.observer = null;
|
||||||
Utility.FileType type = Utility.getFileType(ms.name);
|
h.progress = null;
|
||||||
|
h.position = -1;
|
||||||
h.icon.setImageResource(Utility.getIconForFileType(type));
|
h.lastTimeStamp = -1;
|
||||||
h.name.setText(ms.name);
|
h.lastDone = -1;
|
||||||
h.size.setText(Utility.formatBytes(ms.length));
|
h.colorId = 0;
|
||||||
|
}
|
||||||
h.progress = new ProgressDrawable(mContext, Utility.getBackgroundForFileType(type), Utility.getForegroundForFileType(type));
|
|
||||||
h.bkg.setBackgroundDrawable(h.progress);
|
|
||||||
|
|
||||||
h.observer = new MissionObserver(this, h);
|
|
||||||
ms.addListener(h.observer);
|
|
||||||
|
|
||||||
updateProgress(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public void onBindViewHolder(MissionAdapter.ViewHolder h, int pos) {
|
||||||
return mManager.getCount();
|
DownloadMission ms = mManager.getMission(pos);
|
||||||
}
|
h.mission = ms;
|
||||||
|
h.position = pos;
|
||||||
|
|
||||||
@Override
|
Utility.FileType type = Utility.getFileType(ms.name);
|
||||||
public long getItemId(int position) {
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateProgress(ViewHolder h) {
|
|
||||||
updateProgress(h, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateProgress(ViewHolder h, boolean finished) {
|
|
||||||
if (h.mission == null) return;
|
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
|
|
||||||
if (h.lastTimeStamp == -1) {
|
|
||||||
h.lastTimeStamp = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (h.lastDone == -1) {
|
|
||||||
h.lastDone = h.mission.done;
|
|
||||||
}
|
|
||||||
|
|
||||||
long deltaTime = now - h.lastTimeStamp;
|
|
||||||
long deltaDone = h.mission.done - h.lastDone;
|
|
||||||
|
|
||||||
if (deltaTime == 0 || deltaTime > 1000 || finished) {
|
|
||||||
if (h.mission.errCode > 0) {
|
|
||||||
h.status.setText(R.string.msg_error);
|
|
||||||
} else {
|
|
||||||
float progress = (float) h.mission.done / h.mission.length;
|
|
||||||
h.status.setText(String.format(Locale.US, "%.2f%%", progress * 100));
|
|
||||||
h.progress.setProgress(progress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deltaTime > 1000 && deltaDone > 0) {
|
|
||||||
float speed = (float) deltaDone / deltaTime;
|
|
||||||
String speedStr = Utility.formatSpeed(speed * 1000);
|
|
||||||
String sizeStr = Utility.formatBytes(h.mission.length);
|
|
||||||
|
|
||||||
h.size.setText(sizeStr + " " + speedStr);
|
|
||||||
|
|
||||||
h.lastTimeStamp = now;
|
|
||||||
h.lastDone = h.mission.done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void buildPopup(final ViewHolder h) {
|
h.icon.setImageResource(Utility.getIconForFileType(type));
|
||||||
PopupMenu popup = new PopupMenu(mContext, h.menu);
|
h.name.setText(ms.name);
|
||||||
popup.inflate(R.menu.mission);
|
h.size.setText(Utility.formatBytes(ms.length));
|
||||||
|
|
||||||
Menu menu = popup.getMenu();
|
|
||||||
MenuItem start = menu.findItem(R.id.start);
|
|
||||||
MenuItem pause = menu.findItem(R.id.pause);
|
|
||||||
MenuItem view = menu.findItem(R.id.view);
|
|
||||||
MenuItem delete = menu.findItem(R.id.delete);
|
|
||||||
MenuItem checksum = menu.findItem(R.id.checksum);
|
|
||||||
|
|
||||||
// Set to false first
|
|
||||||
start.setVisible(false);
|
|
||||||
pause.setVisible(false);
|
|
||||||
view.setVisible(false);
|
|
||||||
delete.setVisible(false);
|
|
||||||
checksum.setVisible(false);
|
|
||||||
|
|
||||||
if (!h.mission.finished) {
|
|
||||||
if (!h.mission.running) {
|
|
||||||
if (h.mission.errCode == -1) {
|
|
||||||
start.setVisible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete.setVisible(true);
|
|
||||||
} else {
|
|
||||||
pause.setVisible(true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
view.setVisible(true);
|
|
||||||
delete.setVisible(true);
|
|
||||||
checksum.setVisible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onMenuItemClick(MenuItem item) {
|
|
||||||
int id = item.getItemId();
|
|
||||||
switch (id) {
|
|
||||||
case R.id.start:
|
|
||||||
mManager.resumeMission(h.position);
|
|
||||||
mBinder.onMissionAdded(mManager.getMission(h.position));
|
|
||||||
return true;
|
|
||||||
case R.id.pause:
|
|
||||||
mManager.pauseMission(h.position);
|
|
||||||
mBinder.onMissionRemoved(mManager.getMission(h.position));
|
|
||||||
h.lastTimeStamp = -1;
|
|
||||||
h.lastDone = -1;
|
|
||||||
return true;
|
|
||||||
case R.id.view:
|
|
||||||
File f = new File(h.mission.location, h.mission.name);
|
|
||||||
String ext = Utility.getFileExt(h.mission.name);
|
|
||||||
|
|
||||||
Log.d(TAG, "Viewing file: " + f.getAbsolutePath() + " ext: " + ext);
|
h.progress = new ProgressDrawable(mContext, Utility.getBackgroundForFileType(type), Utility.getForegroundForFileType(type));
|
||||||
|
ViewCompat.setBackground(h.bkg, h.progress);
|
||||||
|
|
||||||
if (ext == null) {
|
h.observer = new MissionObserver(this, h);
|
||||||
Log.w(TAG, "Can't view file because it has no extension: " +
|
ms.addListener(h.observer);
|
||||||
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()) {
|
|
||||||
viewFileWithFileProvider(f, mime);
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "File doesn't exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
case R.id.delete:
|
|
||||||
mManager.deleteMission(h.position);
|
|
||||||
notifyDataSetChanged();
|
|
||||||
return true;
|
|
||||||
case R.id.md5:
|
|
||||||
case R.id.sha1:
|
|
||||||
DownloadMission mission = mManager.getMission(h.position);
|
|
||||||
new ChecksumTask().execute(mission.location + "/" + mission.name, ALGORITHMS.get(id));
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
popup.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void viewFile(File file, String mimetype) {
|
updateProgress(h);
|
||||||
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) {
|
@Override
|
||||||
String ourPackage = mContext.getApplicationContext().getPackageName();
|
public int getItemCount() {
|
||||||
Uri uri = FileProvider.getUriForFile(mContext, ourPackage + ".provider", file);
|
return mManager.getCount();
|
||||||
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;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreExecute() {
|
public long getItemId(int position) {
|
||||||
super.onPreExecute();
|
return position;
|
||||||
|
}
|
||||||
// Create dialog
|
|
||||||
prog = new ProgressDialog(mContext);
|
|
||||||
prog.setCancelable(false);
|
|
||||||
prog.setMessage(mContext.getString(R.string.msg_wait));
|
|
||||||
prog.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private void updateProgress(ViewHolder h) {
|
||||||
protected String doInBackground(String... params) {
|
updateProgress(h, false);
|
||||||
return Utility.checksum(params[0], params[1]);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private void updateProgress(ViewHolder h, boolean finished) {
|
||||||
protected void onPostExecute(String result) {
|
if (h.mission == null) return;
|
||||||
super.onPostExecute(result);
|
|
||||||
prog.dismiss();
|
|
||||||
Utility.copyToClipboard(mContext, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
public DownloadMission mission;
|
|
||||||
public int position;
|
|
||||||
|
|
||||||
public TextView status;
|
long now = System.currentTimeMillis();
|
||||||
public ImageView icon;
|
|
||||||
public TextView name;
|
|
||||||
public TextView size;
|
|
||||||
public View bkg;
|
|
||||||
public ImageView menu;
|
|
||||||
public ProgressDrawable progress;
|
|
||||||
public MissionObserver observer;
|
|
||||||
|
|
||||||
public long lastTimeStamp = -1;
|
if (h.lastTimeStamp == -1) {
|
||||||
public long lastDone = -1;
|
h.lastTimeStamp = now;
|
||||||
public int colorId;
|
}
|
||||||
|
|
||||||
public ViewHolder(View v) {
|
|
||||||
super(v);
|
|
||||||
|
|
||||||
status = Utility.findViewById(v, R.id.item_status);
|
|
||||||
icon = Utility.findViewById(v, R.id.item_icon);
|
|
||||||
name = Utility.findViewById(v, R.id.item_name);
|
|
||||||
size = Utility.findViewById(v, R.id.item_size);
|
|
||||||
bkg = Utility.findViewById(v, R.id.item_bkg);
|
|
||||||
menu = Utility.findViewById(v, R.id.item_more);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class MissionObserver implements DownloadMission.MissionListener {
|
|
||||||
private MissionAdapter mAdapter;
|
|
||||||
private ViewHolder mHolder;
|
|
||||||
|
|
||||||
public MissionObserver(MissionAdapter adapter, ViewHolder holder) {
|
|
||||||
mAdapter = adapter;
|
|
||||||
mHolder = holder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
|
|
||||||
mAdapter.updateProgress(mHolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
if (h.lastDone == -1) {
|
||||||
public void onFinish(DownloadMission downloadMission) {
|
h.lastDone = h.mission.done;
|
||||||
//mAdapter.mManager.deleteMission(mHolder.position);
|
}
|
||||||
// TODO Notification
|
|
||||||
//mAdapter.notifyDataSetChanged();
|
|
||||||
if (mHolder.mission != null) {
|
|
||||||
mHolder.size.setText(Utility.formatBytes(mHolder.mission.length));
|
|
||||||
mAdapter.updateProgress(mHolder, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
long deltaTime = now - h.lastTimeStamp;
|
||||||
public void onError(DownloadMission downloadMission, int errCode) {
|
long deltaDone = h.mission.done - h.lastDone;
|
||||||
mAdapter.updateProgress(mHolder);
|
|
||||||
}
|
if (deltaTime == 0 || deltaTime > 1000 || finished) {
|
||||||
|
if (h.mission.errCode > 0) {
|
||||||
}
|
h.status.setText(R.string.msg_error);
|
||||||
|
} else {
|
||||||
|
float progress = (float) h.mission.done / h.mission.length;
|
||||||
|
h.status.setText(String.format(Locale.US, "%.2f%%", progress * 100));
|
||||||
|
h.progress.setProgress(progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deltaTime > 1000 && deltaDone > 0) {
|
||||||
|
float speed = (float) deltaDone / deltaTime;
|
||||||
|
String speedStr = Utility.formatSpeed(speed * 1000);
|
||||||
|
String sizeStr = Utility.formatBytes(h.mission.length);
|
||||||
|
|
||||||
|
h.size.setText(sizeStr + " " + speedStr);
|
||||||
|
|
||||||
|
h.lastTimeStamp = now;
|
||||||
|
h.lastDone = h.mission.done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void buildPopup(final ViewHolder h) {
|
||||||
|
PopupMenu popup = new PopupMenu(mContext, h.menu);
|
||||||
|
popup.inflate(R.menu.mission);
|
||||||
|
|
||||||
|
Menu menu = popup.getMenu();
|
||||||
|
MenuItem start = menu.findItem(R.id.start);
|
||||||
|
MenuItem pause = menu.findItem(R.id.pause);
|
||||||
|
MenuItem view = menu.findItem(R.id.view);
|
||||||
|
MenuItem delete = menu.findItem(R.id.delete);
|
||||||
|
MenuItem checksum = menu.findItem(R.id.checksum);
|
||||||
|
|
||||||
|
// Set to false first
|
||||||
|
start.setVisible(false);
|
||||||
|
pause.setVisible(false);
|
||||||
|
view.setVisible(false);
|
||||||
|
delete.setVisible(false);
|
||||||
|
checksum.setVisible(false);
|
||||||
|
|
||||||
|
if (!h.mission.finished) {
|
||||||
|
if (!h.mission.running) {
|
||||||
|
if (h.mission.errCode == -1) {
|
||||||
|
start.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete.setVisible(true);
|
||||||
|
} else {
|
||||||
|
pause.setVisible(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
view.setVisible(true);
|
||||||
|
delete.setVisible(true);
|
||||||
|
checksum.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onMenuItemClick(MenuItem item) {
|
||||||
|
int id = item.getItemId();
|
||||||
|
switch (id) {
|
||||||
|
case R.id.start:
|
||||||
|
mManager.resumeMission(h.position);
|
||||||
|
mBinder.onMissionAdded(mManager.getMission(h.position));
|
||||||
|
return true;
|
||||||
|
case R.id.pause:
|
||||||
|
mManager.pauseMission(h.position);
|
||||||
|
mBinder.onMissionRemoved(mManager.getMission(h.position));
|
||||||
|
h.lastTimeStamp = -1;
|
||||||
|
h.lastDone = -1;
|
||||||
|
return true;
|
||||||
|
case R.id.view:
|
||||||
|
File f = new File(h.mission.location, h.mission.name);
|
||||||
|
String ext = Utility.getFileExt(h.mission.name);
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
viewFileWithFileProvider(f, mime);
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "File doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
case R.id.delete:
|
||||||
|
mManager.deleteMission(h.position);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
return true;
|
||||||
|
case R.id.md5:
|
||||||
|
case R.id.sha1:
|
||||||
|
DownloadMission mission = mManager.getMission(h.position);
|
||||||
|
new ChecksumTask().execute(mission.location + "/" + mission.name, ALGORITHMS.get(id));
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
popup.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
public DownloadMission mission;
|
||||||
|
public int position;
|
||||||
|
|
||||||
|
public TextView status;
|
||||||
|
public ImageView icon;
|
||||||
|
public TextView name;
|
||||||
|
public TextView size;
|
||||||
|
public View bkg;
|
||||||
|
public ImageView menu;
|
||||||
|
public ProgressDrawable progress;
|
||||||
|
public MissionObserver observer;
|
||||||
|
|
||||||
|
public long lastTimeStamp = -1;
|
||||||
|
public long lastDone = -1;
|
||||||
|
public int colorId;
|
||||||
|
|
||||||
|
public ViewHolder(View v) {
|
||||||
|
super(v);
|
||||||
|
|
||||||
|
status = (TextView) v.findViewById(R.id.item_status);
|
||||||
|
icon = (ImageView) v.findViewById(R.id.item_icon);
|
||||||
|
name = (TextView) v.findViewById(R.id.item_name);
|
||||||
|
size = (TextView) v.findViewById(R.id.item_size);
|
||||||
|
bkg = v.findViewById(R.id.item_bkg);
|
||||||
|
menu = (ImageView) v.findViewById(R.id.item_more);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MissionObserver implements DownloadMission.MissionListener {
|
||||||
|
private MissionAdapter mAdapter;
|
||||||
|
private ViewHolder mHolder;
|
||||||
|
|
||||||
|
public MissionObserver(MissionAdapter adapter, ViewHolder holder) {
|
||||||
|
mAdapter = adapter;
|
||||||
|
mHolder = holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
|
||||||
|
mAdapter.updateProgress(mHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish(DownloadMission downloadMission) {
|
||||||
|
//mAdapter.mManager.deleteMission(mHolder.position);
|
||||||
|
// TODO Notification
|
||||||
|
//mAdapter.notifyDataSetChanged();
|
||||||
|
if (mHolder.mission != null) {
|
||||||
|
mHolder.size.setText(Utility.formatBytes(mHolder.mission.length));
|
||||||
|
mAdapter.updateProgress(mHolder, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(DownloadMission downloadMission, int errCode) {
|
||||||
|
mAdapter.updateProgress(mHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ChecksumTask extends AsyncTask<String, Void, String> {
|
||||||
|
ProgressDialog prog;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
super.onPreExecute();
|
||||||
|
|
||||||
|
// Create dialog
|
||||||
|
prog = new ProgressDialog(mContext);
|
||||||
|
prog.setCancelable(false);
|
||||||
|
prog.setMessage(mContext.getString(R.string.msg_wait));
|
||||||
|
prog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String doInBackground(String... params) {
|
||||||
|
return Utility.checksum(params[0], params[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(String result) {
|
||||||
|
super.onPostExecute(result);
|
||||||
|
prog.dismiss();
|
||||||
|
Utility.copyToClipboard(mContext, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,53 +6,55 @@ import android.graphics.ColorFilter;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.PixelFormat;
|
import android.graphics.PixelFormat;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.support.annotation.ColorRes;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
|
|
||||||
public class ProgressDrawable extends Drawable
|
public class ProgressDrawable extends Drawable {
|
||||||
{
|
private float mProgress;
|
||||||
private float mProgress;
|
private int mBackgroundColor, mForegroundColor;
|
||||||
private int mBackgroundColor, mForegroundColor;
|
|
||||||
|
|
||||||
public ProgressDrawable(Context context, int background, int foreground) {
|
|
||||||
this(context.getResources().getColor(background), context.getResources().getColor(foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProgressDrawable(int background, int foreground) {
|
|
||||||
mBackgroundColor = background;
|
|
||||||
mForegroundColor = foreground;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProgress(float progress) {
|
|
||||||
mProgress = progress;
|
|
||||||
invalidateSelf();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public ProgressDrawable(Context context, @ColorRes int background, @ColorRes int foreground) {
|
||||||
public void draw(Canvas canvas) {
|
this(ContextCompat.getColor(context, background), ContextCompat.getColor(context, foreground));
|
||||||
int width = canvas.getWidth();
|
}
|
||||||
int height = canvas.getHeight();
|
|
||||||
|
|
||||||
Paint paint = new Paint();
|
|
||||||
|
|
||||||
paint.setColor(mBackgroundColor);
|
|
||||||
canvas.drawRect(0, 0, width, height, paint);
|
|
||||||
|
|
||||||
paint.setColor(mForegroundColor);
|
|
||||||
canvas.drawRect(0, 0, (int) (mProgress * width), height, paint);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public ProgressDrawable(int background, int foreground) {
|
||||||
public void setAlpha(int alpha) {
|
mBackgroundColor = background;
|
||||||
// Unsupported
|
mForegroundColor = foreground;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void setProgress(float progress) {
|
||||||
public void setColorFilter(ColorFilter filter) {
|
mProgress = progress;
|
||||||
// Unsupported
|
invalidateSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getOpacity() {
|
public void draw(@NonNull Canvas canvas) {
|
||||||
return PixelFormat.OPAQUE;
|
int width = canvas.getWidth();
|
||||||
}
|
int height = canvas.getHeight();
|
||||||
|
|
||||||
|
Paint paint = new Paint();
|
||||||
|
|
||||||
|
paint.setColor(mBackgroundColor);
|
||||||
|
canvas.drawRect(0, 0, width, height, paint);
|
||||||
|
|
||||||
|
paint.setColor(mForegroundColor);
|
||||||
|
canvas.drawRect(0, 0, (int) (mProgress * width), height, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAlpha(int alpha) {
|
||||||
|
// Unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setColorFilter(ColorFilter filter) {
|
||||||
|
// Unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOpacity() {
|
||||||
|
return PixelFormat.OPAQUE;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,23 @@
|
||||||
package us.shandian.giga.ui.common;
|
package us.shandian.giga.ui.common;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import android.support.v7.app.ActionBarActivity;
|
import android.support.v7.app.ActionBarActivity;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import us.shandian.giga.util.Utility;
|
|
||||||
|
|
||||||
public abstract class ToolbarActivity extends ActionBarActivity
|
public abstract class ToolbarActivity extends ActionBarActivity {
|
||||||
{
|
protected Toolbar mToolbar;
|
||||||
protected Toolbar mToolbar;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(getLayoutResource());
|
setContentView(getLayoutResource());
|
||||||
|
|
||||||
mToolbar = Utility.findViewById(this, R.id.toolbar);
|
mToolbar = (Toolbar) this.findViewById(R.id.toolbar);
|
||||||
|
|
||||||
setSupportActionBar(mToolbar);
|
setSupportActionBar(mToolbar);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract int getLayoutResource();
|
protected abstract int getLayoutResource();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,10 @@ package us.shandian.giga.ui.fragment;
|
||||||
import us.shandian.giga.get.DownloadManager;
|
import us.shandian.giga.get.DownloadManager;
|
||||||
import us.shandian.giga.service.DownloadManagerService;
|
import us.shandian.giga.service.DownloadManagerService;
|
||||||
|
|
||||||
public class AllMissionsFragment extends MissionsFragment
|
public class AllMissionsFragment extends MissionsFragment {
|
||||||
{
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder) {
|
protected DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder) {
|
||||||
return binder.getDownloadManager();
|
return binder.getDownloadManager();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,126 +23,128 @@ import org.schabi.newpipe.R;
|
||||||
import us.shandian.giga.get.DownloadManager;
|
import us.shandian.giga.get.DownloadManager;
|
||||||
import us.shandian.giga.service.DownloadManagerService;
|
import us.shandian.giga.service.DownloadManagerService;
|
||||||
import us.shandian.giga.ui.adapter.MissionAdapter;
|
import us.shandian.giga.ui.adapter.MissionAdapter;
|
||||||
import us.shandian.giga.util.Utility;
|
|
||||||
|
|
||||||
public abstract class MissionsFragment extends Fragment
|
public abstract class MissionsFragment extends Fragment {
|
||||||
{
|
private DownloadManager mManager;
|
||||||
private DownloadManager mManager;
|
private DownloadManagerService.DMBinder mBinder;
|
||||||
private DownloadManagerService.DMBinder mBinder;
|
|
||||||
|
|
||||||
private SharedPreferences mPrefs;
|
|
||||||
private boolean mLinear;
|
|
||||||
private MenuItem mSwitch;
|
|
||||||
|
|
||||||
private RecyclerView mList;
|
|
||||||
private MissionAdapter mAdapter;
|
|
||||||
private GridLayoutManager mGridManager;
|
|
||||||
private LinearLayoutManager mLinearManager;
|
|
||||||
private Context mActivity;
|
|
||||||
|
|
||||||
private ServiceConnection mConnection = new ServiceConnection() {
|
|
||||||
|
|
||||||
@Override
|
private SharedPreferences mPrefs;
|
||||||
public void onServiceConnected(ComponentName name, IBinder binder) {
|
private boolean mLinear;
|
||||||
mBinder = (DownloadManagerService.DMBinder) binder;
|
private MenuItem mSwitch;
|
||||||
mManager = setupDownloadManager(mBinder);
|
|
||||||
updateList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private RecyclerView mList;
|
||||||
public void onServiceDisconnected(ComponentName name) {
|
private MissionAdapter mAdapter;
|
||||||
// What to do?
|
private GridLayoutManager mGridManager;
|
||||||
}
|
private LinearLayoutManager mLinearManager;
|
||||||
|
private Context mActivity;
|
||||||
|
|
||||||
|
private ServiceConnection mConnection = new ServiceConnection() {
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public void onServiceConnected(ComponentName name, IBinder binder) {
|
||||||
View v = inflater.inflate(R.layout.missions, container, false);
|
mBinder = (DownloadManagerService.DMBinder) binder;
|
||||||
|
mManager = setupDownloadManager(mBinder);
|
||||||
|
updateList();
|
||||||
|
}
|
||||||
|
|
||||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
@Override
|
||||||
mLinear = mPrefs.getBoolean("linear", false);
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
|
// What to do?
|
||||||
|
}
|
||||||
|
|
||||||
// Bind the service
|
|
||||||
Intent i = new Intent();
|
|
||||||
i.setClass(getActivity(), DownloadManagerService.class);
|
|
||||||
getActivity().bindService(i, mConnection, Context.BIND_AUTO_CREATE);
|
|
||||||
|
|
||||||
// Views
|
|
||||||
mList = Utility.findViewById(v, R.id.mission_recycler);
|
|
||||||
|
|
||||||
// Init
|
|
||||||
mGridManager = new GridLayoutManager(getActivity(), 2);
|
|
||||||
mLinearManager = new LinearLayoutManager(getActivity());
|
|
||||||
mList.setLayoutManager(mGridManager);
|
|
||||||
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Added in API level 23. */
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context activity) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
super.onAttach(activity);
|
View v = inflater.inflate(R.layout.missions, container, false);
|
||||||
|
|
||||||
// Bug: in api< 23 this is never called
|
mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||||
// so mActivity=null
|
mLinear = mPrefs.getBoolean("linear", false);
|
||||||
// so app crashes with nullpointer exception
|
|
||||||
mActivity = activity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** deprecated in API level 23,
|
// Bind the service
|
||||||
* but must remain to allow compatibility with api<23 */
|
Intent i = new Intent();
|
||||||
@Override
|
i.setClass(getActivity(), DownloadManagerService.class);
|
||||||
public void onAttach(Activity activity) {
|
getActivity().bindService(i, mConnection, Context.BIND_AUTO_CREATE);
|
||||||
super.onAttach(activity);
|
|
||||||
|
|
||||||
mActivity = activity;
|
// Views
|
||||||
}
|
mList = (RecyclerView) v.findViewById(R.id.mission_recycler);
|
||||||
|
|
||||||
@Override
|
// Init
|
||||||
public void onDestroyView() {
|
mGridManager = new GridLayoutManager(getActivity(), 2);
|
||||||
super.onDestroyView();
|
mLinearManager = new LinearLayoutManager(getActivity());
|
||||||
getActivity().unbindService(mConnection);
|
mList.setLayoutManager(mGridManager);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
setHasOptionsMenu(true);
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
return super.onOptionsItemSelected(item);
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Added in API level 23.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onAttach(Context activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
|
||||||
|
// Bug: in api< 23 this is never called
|
||||||
|
// so mActivity=null
|
||||||
|
// so app crashes with nullpointer exception
|
||||||
|
mActivity = activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* deprecated in API level 23,
|
||||||
|
* but must remain to allow compatibility with api<23
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
|
||||||
|
mActivity = activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
getActivity().unbindService(mConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
|
||||||
/*switch (item.getItemId()) {
|
/*switch (item.getItemId()) {
|
||||||
case R.id.switch_mode:
|
case R.id.switch_mode:
|
||||||
mLinear = !mLinear;
|
mLinear = !mLinear;
|
||||||
updateList();
|
updateList();
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyChange() {
|
public void notifyChange() {
|
||||||
mAdapter.notifyDataSetChanged();
|
mAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateList() {
|
private void updateList() {
|
||||||
mAdapter = new MissionAdapter(mActivity, mBinder, mManager, mLinear);
|
mAdapter = new MissionAdapter(mActivity, mBinder, mManager, mLinear);
|
||||||
|
|
||||||
if (mLinear) {
|
if (mLinear) {
|
||||||
mList.setLayoutManager(mLinearManager);
|
mList.setLayoutManager(mLinearManager);
|
||||||
} else {
|
} else {
|
||||||
mList.setLayoutManager(mGridManager);
|
mList.setLayoutManager(mGridManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
mList.setAdapter(mAdapter);
|
mList.setAdapter(mAdapter);
|
||||||
|
|
||||||
if (mSwitch != null) {
|
if (mSwitch != null) {
|
||||||
mSwitch.setIcon(mLinear ? R.drawable.grid : R.drawable.list);
|
mSwitch.setIcon(mLinear ? R.drawable.grid : R.drawable.list);
|
||||||
}
|
}
|
||||||
|
|
||||||
mPrefs.edit().putBoolean("linear", mLinear).commit();
|
mPrefs.edit().putBoolean("linear", mLinear).commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder);
|
protected abstract DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,77 +9,76 @@ import java.io.File;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
//todo: replace this by using the internal crash handler of newpipe
|
//todo: replace this by using the internal crash handler of newpipe
|
||||||
public class CrashHandler implements Thread.UncaughtExceptionHandler
|
public class CrashHandler implements Thread.UncaughtExceptionHandler {
|
||||||
{
|
public static final String CRASH_DIR = Environment.getExternalStorageDirectory().getPath() + "/GigaCrash/";
|
||||||
public static final String CRASH_DIR = Environment.getExternalStorageDirectory().getPath() + "/GigaCrash/";
|
public static final String CRASH_LOG = CRASH_DIR + "last_crash.log";
|
||||||
public static final String CRASH_LOG = CRASH_DIR + "last_crash.log";
|
public static final String CRASH_TAG = CRASH_DIR + ".crashed";
|
||||||
public static final String CRASH_TAG = CRASH_DIR + ".crashed";
|
|
||||||
|
|
||||||
private static String ANDROID = Build.VERSION.RELEASE;
|
private static String ANDROID = Build.VERSION.RELEASE;
|
||||||
private static String MODEL = Build.MODEL;
|
private static String MODEL = Build.MODEL;
|
||||||
private static String MANUFACTURER = Build.MANUFACTURER;
|
private static String MANUFACTURER = Build.MANUFACTURER;
|
||||||
|
|
||||||
public static String VERSION = "Unknown";
|
public static String VERSION = "Unknown";
|
||||||
|
|
||||||
private Thread.UncaughtExceptionHandler mPrevious;
|
private Thread.UncaughtExceptionHandler mPrevious;
|
||||||
|
|
||||||
public static void init(Context context) {
|
public static void init(Context context) {
|
||||||
try {
|
try {
|
||||||
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||||
VERSION = info.versionName + info.versionCode;
|
VERSION = info.versionName + info.versionCode;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void register() {
|
public static void register() {
|
||||||
new CrashHandler();
|
new CrashHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
private CrashHandler() {
|
private CrashHandler() {
|
||||||
mPrevious = Thread.currentThread().getUncaughtExceptionHandler();
|
mPrevious = Thread.currentThread().getUncaughtExceptionHandler();
|
||||||
Thread.currentThread().setUncaughtExceptionHandler(this);
|
Thread.currentThread().setUncaughtExceptionHandler(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void uncaughtException(Thread thread, Throwable throwable) {
|
public void uncaughtException(Thread thread, Throwable throwable) {
|
||||||
File f = new File(CRASH_LOG);
|
File f = new File(CRASH_LOG);
|
||||||
if (f.exists()) {
|
if (f.exists()) {
|
||||||
f.delete();
|
f.delete();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
new File(CRASH_DIR).mkdirs();
|
new File(CRASH_DIR).mkdirs();
|
||||||
f.createNewFile();
|
f.createNewFile();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintWriter p;
|
PrintWriter p;
|
||||||
try {
|
try {
|
||||||
p = new PrintWriter(f);
|
p = new PrintWriter(f);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.write("Android Version: " + ANDROID + "\n");
|
p.write("Android Version: " + ANDROID + "\n");
|
||||||
p.write("Device Model: " + MODEL + "\n");
|
p.write("Device Model: " + MODEL + "\n");
|
||||||
p.write("Device Manufacturer: " + MANUFACTURER + "\n");
|
p.write("Device Manufacturer: " + MANUFACTURER + "\n");
|
||||||
p.write("App Version: " + VERSION + "\n");
|
p.write("App Version: " + VERSION + "\n");
|
||||||
p.write("*********************\n");
|
p.write("*********************\n");
|
||||||
throwable.printStackTrace(p);
|
throwable.printStackTrace(p);
|
||||||
|
|
||||||
p.close();
|
p.close();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
new File(CRASH_TAG).createNewFile();
|
new File(CRASH_TAG).createNewFile();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mPrevious != null) {
|
if (mPrevious != null) {
|
||||||
mPrevious.uncaughtException(thread, throwable);
|
mPrevious.uncaughtException(thread, throwable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,256 +1,221 @@
|
||||||
package us.shandian.giga.util;
|
package us.shandian.giga.util;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.ClipboardManager;
|
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.support.annotation.ColorRes;
|
||||||
import android.view.View;
|
import android.support.annotation.DrawableRes;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
public class Utility {
|
||||||
import org.schabi.newpipe.R;
|
|
||||||
|
|
||||||
import com.nononsenseapps.filepicker.FilePickerActivity;
|
public enum FileType {
|
||||||
import com.nononsenseapps.filepicker.AbstractFilePickerFragment;
|
VIDEO,
|
||||||
|
MUSIC,
|
||||||
|
UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
public class Utility
|
public static String formatBytes(long bytes) {
|
||||||
{
|
if (bytes < 1024) {
|
||||||
|
return String.format("%d B", bytes);
|
||||||
public static enum FileType {
|
} else if (bytes < 1024 * 1024) {
|
||||||
VIDEO,
|
return String.format("%.2f kB", (float) bytes / 1024);
|
||||||
MUSIC,
|
} else if (bytes < 1024 * 1024 * 1024) {
|
||||||
UNKNOWN
|
return String.format("%.2f MB", (float) bytes / 1024 / 1024);
|
||||||
}
|
} else {
|
||||||
|
return String.format("%.2f GB", (float) bytes / 1024 / 1024 / 1024);
|
||||||
public static String formatBytes(long bytes) {
|
}
|
||||||
if (bytes < 1024) {
|
}
|
||||||
return String.format("%d B", bytes);
|
|
||||||
} else if (bytes < 1024 * 1024) {
|
|
||||||
return String.format("%.2f kB", (float) bytes / 1024);
|
|
||||||
} else if (bytes < 1024 * 1024 * 1024) {
|
|
||||||
return String.format("%.2f MB", (float) bytes / 1024 / 1024);
|
|
||||||
} else {
|
|
||||||
return String.format("%.2f GB", (float) bytes / 1024 / 1024 / 1024);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String formatSpeed(float speed) {
|
|
||||||
if (speed < 1024) {
|
|
||||||
return String.format("%.2f B/s", speed);
|
|
||||||
} else if (speed < 1024 * 1024) {
|
|
||||||
return String.format("%.2f kB/s", speed / 1024);
|
|
||||||
} else if (speed < 1024 * 1024 * 1024) {
|
|
||||||
return String.format("%.2f MB/s", speed / 1024 / 1024);
|
|
||||||
} else {
|
|
||||||
return String.format("%.2f GB/s", speed / 1024 / 1024 / 1024);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void writeToFile(String fileName, String content) {
|
|
||||||
try {
|
|
||||||
writeToFile(fileName, content.getBytes("UTF-8"));
|
|
||||||
} catch (Exception e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void writeToFile(String fileName, byte[] content) {
|
|
||||||
File f = new File(fileName);
|
|
||||||
|
|
||||||
if (!f.exists()) {
|
|
||||||
try {
|
|
||||||
f.createNewFile();
|
|
||||||
} catch (Exception e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
FileOutputStream opt = new FileOutputStream(f, false);
|
|
||||||
opt.write(content, 0, content.length);
|
|
||||||
opt.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String readFromFile(String file) {
|
|
||||||
try {
|
|
||||||
File f = new File(file);
|
|
||||||
|
|
||||||
if (!f.exists() || !f.canRead()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferedInputStream ipt = new BufferedInputStream(new FileInputStream(f));
|
|
||||||
|
|
||||||
byte[] buf = new byte[512];
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
while (ipt.available() > 0) {
|
|
||||||
int len = ipt.read(buf, 0, 512);
|
|
||||||
sb.append(new String(buf, 0, len, "UTF-8"));
|
|
||||||
}
|
|
||||||
|
|
||||||
ipt.close();
|
|
||||||
return sb.toString();
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T findViewById(View v, int id) {
|
|
||||||
return (T) v.findViewById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T findViewById(Activity activity, int id) {
|
|
||||||
return (T) activity.findViewById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getFileExt(String url) {
|
|
||||||
if (url.indexOf("?")>-1) {
|
|
||||||
url = url.substring(0,url.indexOf("?"));
|
|
||||||
}
|
|
||||||
if (url.lastIndexOf(".") == -1) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
String ext = url.substring(url.lastIndexOf(".") );
|
|
||||||
if (ext.indexOf("%")>-1) {
|
|
||||||
ext = ext.substring(0,ext.indexOf("%"));
|
|
||||||
}
|
|
||||||
if (ext.indexOf("/")>-1) {
|
|
||||||
ext = ext.substring(0,ext.indexOf("/"));
|
|
||||||
}
|
|
||||||
return ext.toLowerCase();
|
|
||||||
|
|
||||||
}
|
public static String formatSpeed(float speed) {
|
||||||
}
|
if (speed < 1024) {
|
||||||
|
return String.format("%.2f B/s", speed);
|
||||||
|
} else if (speed < 1024 * 1024) {
|
||||||
|
return String.format("%.2f kB/s", speed / 1024);
|
||||||
|
} else if (speed < 1024 * 1024 * 1024) {
|
||||||
|
return String.format("%.2f MB/s", speed / 1024 / 1024);
|
||||||
|
} else {
|
||||||
|
return String.format("%.2f GB/s", speed / 1024 / 1024 / 1024);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static FileType getFileType(String file) {
|
public static void writeToFile(String fileName, String content) {
|
||||||
if (file.endsWith(".mp3") || file.endsWith(".wav") || file.endsWith(".flac") || file.endsWith(".m4a")) {
|
try {
|
||||||
return FileType.MUSIC;
|
writeToFile(fileName, content.getBytes("UTF-8"));
|
||||||
} else if (file.endsWith(".mp4") || file.endsWith(".mpeg") || file.endsWith(".rm") || file.endsWith(".rmvb")
|
} catch (Exception e) {
|
||||||
|| file.endsWith(".flv") || file.endsWith(".webp") || file.endsWith(".webm")) {
|
|
||||||
return FileType.VIDEO;
|
|
||||||
} else {
|
|
||||||
return FileType.UNKNOWN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Boolean isMusicFile(String file)
|
}
|
||||||
{
|
}
|
||||||
return Utility.getFileType(file) == FileType.MUSIC;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Boolean isVideoFile(String file)
|
public static void writeToFile(String fileName, byte[] content) {
|
||||||
{
|
File f = new File(fileName);
|
||||||
return Utility.getFileType(file) == FileType.VIDEO;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getBackgroundForFileType(FileType type) {
|
|
||||||
switch (type) {
|
|
||||||
case MUSIC:
|
|
||||||
return R.color.audio_left_to_load_color;
|
|
||||||
case VIDEO:
|
|
||||||
return R.color.video_left_to_load_color;
|
|
||||||
default:
|
|
||||||
return R.color.gray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getForegroundForFileType(FileType type) {
|
|
||||||
switch (type) {
|
|
||||||
case MUSIC:
|
|
||||||
return R.color.audio_already_load_color;
|
|
||||||
case VIDEO:
|
|
||||||
return R.color.video_already_load_color;
|
|
||||||
default:
|
|
||||||
return R.color.gray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getIconForFileType(FileType type) {
|
if (!f.exists()) {
|
||||||
switch(type) {
|
try {
|
||||||
case MUSIC:
|
f.createNewFile();
|
||||||
return R.drawable.music;
|
} catch (Exception e) {
|
||||||
case VIDEO:
|
|
||||||
return R.drawable.video;
|
|
||||||
default:
|
|
||||||
return R.drawable.video;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isDirectoryAvailble(String path) {
|
}
|
||||||
File dir = new File(path);
|
}
|
||||||
return dir.exists() && dir.isDirectory();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isDownloadDirectoryAvailble(Context context) {
|
try {
|
||||||
return isDirectoryAvailble(NewPipeSettings.getVideoDownloadPath(context));
|
FileOutputStream opt = new FileOutputStream(f, false);
|
||||||
}
|
opt.write(content, 0, content.length);
|
||||||
|
opt.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
public static void showDirectoryChooser(Activity activity) {
|
}
|
||||||
Intent i = new Intent(activity, FilePickerActivity.class);
|
}
|
||||||
i.setAction(Intent.ACTION_GET_CONTENT);
|
|
||||||
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
|
public static String readFromFile(String file) {
|
||||||
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true);
|
try {
|
||||||
i.putExtra(FilePickerActivity.EXTRA_MODE, AbstractFilePickerFragment.MODE_DIR);
|
File f = new File(file);
|
||||||
activity.startActivityForResult(i, 233);
|
|
||||||
}
|
if (!f.exists() || !f.canRead()) {
|
||||||
|
return null;
|
||||||
public static void copyToClipboard(Context context, String str) {
|
}
|
||||||
ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
|
||||||
cm.setPrimaryClip(ClipData.newPlainText("text", str));
|
BufferedInputStream ipt = new BufferedInputStream(new FileInputStream(f));
|
||||||
Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
byte[] buf = new byte[512];
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
public static String checksum(String path, String algorithm) {
|
|
||||||
MessageDigest md = null;
|
while (ipt.available() > 0) {
|
||||||
|
int len = ipt.read(buf, 0, 512);
|
||||||
try {
|
sb.append(new String(buf, 0, len, "UTF-8"));
|
||||||
md = MessageDigest.getInstance(algorithm);
|
}
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new RuntimeException(e);
|
ipt.close();
|
||||||
}
|
return sb.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
FileInputStream i = null;
|
return null;
|
||||||
|
}
|
||||||
try {
|
}
|
||||||
i = new FileInputStream(path);
|
|
||||||
} catch (FileNotFoundException e) {
|
@Nullable
|
||||||
throw new RuntimeException(e);
|
public static String getFileExt(String url) {
|
||||||
}
|
int index;
|
||||||
|
if ((index = url.indexOf("?")) > -1) {
|
||||||
byte[] buf = new byte[1024];
|
url = url.substring(0, index);
|
||||||
int len = 0;
|
}
|
||||||
|
|
||||||
try {
|
index = url.lastIndexOf(".");
|
||||||
while ((len = i.read(buf)) != -1) {
|
if (index == -1) {
|
||||||
md.update(buf, 0, len);
|
return null;
|
||||||
}
|
} else {
|
||||||
} catch (IOException e) {
|
String ext = url.substring(index);
|
||||||
|
if ((index = ext.indexOf("%")) > -1) {
|
||||||
}
|
ext = ext.substring(0, index);
|
||||||
|
}
|
||||||
byte[] digest = md.digest();
|
if ((index = ext.indexOf("/")) > -1) {
|
||||||
|
ext = ext.substring(0, index);
|
||||||
// HEX
|
}
|
||||||
StringBuilder sb = new StringBuilder();
|
return ext.toLowerCase();
|
||||||
for (byte b : digest) {
|
}
|
||||||
sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
|
}
|
||||||
}
|
|
||||||
|
public static FileType getFileType(String file) {
|
||||||
return sb.toString();
|
if (file.endsWith(".mp3") || file.endsWith(".wav") || file.endsWith(".flac") || file.endsWith(".m4a")) {
|
||||||
|
return FileType.MUSIC;
|
||||||
}
|
} else if (file.endsWith(".mp4") || file.endsWith(".mpeg") || file.endsWith(".rm") || file.endsWith(".rmvb")
|
||||||
|
|| file.endsWith(".flv") || file.endsWith(".webp") || file.endsWith(".webm")) {
|
||||||
|
return FileType.VIDEO;
|
||||||
|
} else {
|
||||||
|
return FileType.UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorRes
|
||||||
|
public static int getBackgroundForFileType(FileType type) {
|
||||||
|
switch (type) {
|
||||||
|
case MUSIC:
|
||||||
|
return R.color.audio_left_to_load_color;
|
||||||
|
case VIDEO:
|
||||||
|
return R.color.video_left_to_load_color;
|
||||||
|
default:
|
||||||
|
return R.color.gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorRes
|
||||||
|
public static int getForegroundForFileType(FileType type) {
|
||||||
|
switch (type) {
|
||||||
|
case MUSIC:
|
||||||
|
return R.color.audio_already_load_color;
|
||||||
|
case VIDEO:
|
||||||
|
return R.color.video_already_load_color;
|
||||||
|
default:
|
||||||
|
return R.color.gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
public static int getIconForFileType(FileType type) {
|
||||||
|
switch (type) {
|
||||||
|
case MUSIC:
|
||||||
|
return R.drawable.music;
|
||||||
|
case VIDEO:
|
||||||
|
return R.drawable.video;
|
||||||
|
default:
|
||||||
|
return R.drawable.video;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void copyToClipboard(Context context, String str) {
|
||||||
|
ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
cm.setPrimaryClip(ClipData.newPlainText("text", str));
|
||||||
|
Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String checksum(String path, String algorithm) {
|
||||||
|
MessageDigest md = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
md = MessageDigest.getInstance(algorithm);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInputStream i = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
i = new FileInputStream(path);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] buf = new byte[1024];
|
||||||
|
int len = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
while ((len = i.read(buf)) != -1) {
|
||||||
|
md.update(buf, 0, len);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] digest = md.digest();
|
||||||
|
|
||||||
|
// HEX
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : digest) {
|
||||||
|
sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:duration="200"
|
||||||
|
android:fromAlpha="0.0"
|
||||||
|
android:interpolator="@android:interpolator/accelerate_decelerate"
|
||||||
|
android:toAlpha="1.0"/>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:duration="200"
|
||||||
|
android:fromAlpha="1.0"
|
||||||
|
android:interpolator="@android:interpolator/accelerate_decelerate"
|
||||||
|
android:toAlpha="0.0"/>
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout
|
<FrameLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -15,4 +15,4 @@
|
||||||
|
|
||||||
<include layout="@layout/toolbar_layout"/>
|
<include layout="@layout/toolbar_layout"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</FrameLayout>
|
||||||
|
|
|
@ -4,11 +4,10 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/itemRoot"
|
android:id="@+id/itemRoot"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="@dimen/video_item_search_height"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:orientation="vertical"
|
android:padding="@dimen/video_item_search_padding">
|
||||||
android:padding="12dp">
|
|
||||||
|
|
||||||
<de.hdodenhof.circleimageview.CircleImageView
|
<de.hdodenhof.circleimageview.CircleImageView
|
||||||
android:id="@+id/itemThumbnailView"
|
android:id="@+id/itemThumbnailView"
|
||||||
|
@ -22,55 +21,43 @@
|
||||||
android:src="@drawable/buddy_channel_item"
|
android:src="@drawable/buddy_channel_item"
|
||||||
tools:ignore="RtlHardcoded"/>
|
tools:ignore="RtlHardcoded"/>
|
||||||
|
|
||||||
<RelativeLayout
|
<TextView
|
||||||
|
android:id="@+id/itemChannelTitleView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/video_item_search_thumbnail_image_height"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
android:layout_toRightOf="@id/itemThumbnailView"
|
android:layout_toRightOf="@id/itemThumbnailView"
|
||||||
android:orientation="vertical"
|
android:layout_marginBottom="@dimen/video_item_search_image_right_margin"
|
||||||
tools:ignore="RtlHardcoded">
|
android:ellipsize="end"
|
||||||
|
android:lines="1"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:textSize="@dimen/video_item_search_title_text_size"
|
||||||
|
tools:text="Channel Title, Lorem ipsum"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/itemChannelTitleView"
|
android:id="@+id/itemAdditionalDetails"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:ellipsize="end"
|
android:layout_toRightOf="@id/itemThumbnailView"
|
||||||
android:maxLines="1"
|
android:lines="1"
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:textSize="@dimen/channel_item_detail_title_text_size"
|
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
||||||
tools:text="Channel Title, Lorem ipsum"/>
|
tools:text="10M subscribers • 1000 videos"/>
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/itemChannelDescriptionView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_above="@+id/itemSubscriberCountView"
|
|
||||||
android:layout_below="@+id/itemChannelTitleView"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:maxLines="2"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
|
||||||
android:textSize="@dimen/video_item_search_uploader_text_size"
|
|
||||||
tools:text="Channel description, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/itemSubscriberCountView"
|
android:id="@+id/itemChannelDescriptionView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_above="@id/itemAdditionalDetails"
|
||||||
android:maxLines="1"
|
android:layout_marginBottom="@dimen/video_item_detail_description_to_details_margin"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:layout_toRightOf="@id/itemThumbnailView"
|
||||||
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
android:ellipsize="end"
|
||||||
tools:text="10M subscribers • "/>
|
android:lines="2"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textSize="@dimen/video_item_search_uploader_text_size"
|
||||||
|
tools:text="Channel description, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit"/>
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/itemVideoCountView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:layout_toRightOf="@+id/itemSubscriberCountView"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
|
||||||
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
|
||||||
tools:text="1000 videos"/>
|
|
||||||
</RelativeLayout>
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout
|
<LinearLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="16dp"
|
android:padding="16dp"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/error_message_view"
|
android:id="@+id/error_message_view"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:text="@string/general_error"
|
android:text="@string/general_error"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
|
@ -22,8 +22,6 @@
|
||||||
android:id="@+id/error_button_retry"
|
android:id="@+id/error_button_retry"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/error_message_view"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
android:text="@string/retry"
|
android:text="@string/retry"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
|
@ -32,4 +30,4 @@
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
android:theme="@style/RedButton"/>
|
android:theme="@style/RedButton"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</LinearLayout>
|
|
@ -1,9 +1,9 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".fragments.channel.ChannelFragment">
|
||||||
|
|
||||||
<android.support.v7.widget.RecyclerView
|
<android.support.v7.widget.RecyclerView
|
||||||
android:id="@+id/channel_streams_view"
|
android:id="@+id/channel_streams_view"
|
||||||
|
@ -11,26 +11,11 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?android:windowBackground"
|
android:background="?android:windowBackground"
|
||||||
android:scrollbars="vertical"
|
android:scrollbars="vertical"
|
||||||
tools:listitem="@layout/stream_item"/>
|
tools:listitem="@layout/stream_item" />
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/loading_progress_bar"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerInParent="true"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible"/>
|
|
||||||
|
|
||||||
<!--ERROR PANEL-->
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
layout="@layout/loading_error_retry"
|
||||||
layout="@layout/error_retry"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_gravity="center" />
|
||||||
android:layout_marginTop="50dp"
|
</FrameLayout>
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible"/>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -14,26 +13,11 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:scrollbars="vertical"
|
android:scrollbars="vertical"
|
||||||
app:layoutManager="LinearLayoutManager"
|
app:layoutManager="LinearLayoutManager"
|
||||||
tools:listitem="@layout/stream_item"/>
|
tools:listitem="@layout/stream_item" />
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/loading_progress_bar"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerInParent="true"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible"/>
|
|
||||||
|
|
||||||
<!--ERROR PANEL-->
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
layout="@layout/loading_error_retry"
|
||||||
layout="@layout/error_retry"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_gravity="center_vertical" />
|
||||||
android:layout_marginTop="50dp"
|
</FrameLayout>
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible"/>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/video_item_detail"
|
android:id="@+id/video_item_detail"
|
||||||
|
@ -16,105 +15,92 @@
|
||||||
app:parallax_factor="1.9">
|
app:parallax_factor="1.9">
|
||||||
|
|
||||||
<!--WRAPPER-->
|
<!--WRAPPER-->
|
||||||
<RelativeLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<!-- THUMBNAIL -->
|
<!-- THUMBNAIL -->
|
||||||
<RelativeLayout
|
<FrameLayout
|
||||||
android:id="@+id/detail_thumbnail_root_layout"
|
android:id="@+id/detail_thumbnail_root_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@android:color/black">
|
android:background="@android:color/black"
|
||||||
|
android:clickable="true"
|
||||||
|
android:foreground="?attr/selectableItemBackground">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/detail_thumbnail_image_view"
|
android:id="@+id/detail_thumbnail_image_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:contentDescription="@string/detail_thumbnail_view_description"
|
android:contentDescription="@string/detail_thumbnail_view_description"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
tools:ignore="RtlHardcoded"
|
tools:ignore="RtlHardcoded"
|
||||||
tools:layout_height="200dp"
|
tools:layout_height="200dp"
|
||||||
tools:src="@drawable/dummy_thumbnail"/>
|
tools:src="@drawable/dummy_thumbnail" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/detail_thumbnail_play_button"
|
android:id="@+id/detail_thumbnail_play_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_gravity="center"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:src="@drawable/new_play_arrow"
|
android:src="@drawable/new_play_arrow"
|
||||||
android:visibility="invisible"
|
android:visibility="invisible"
|
||||||
tools:ignore="ContentDescription"
|
tools:ignore="ContentDescription"
|
||||||
tools:visibility="visible"/>
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<Button
|
</FrameLayout>
|
||||||
android:id="@+id/detail_thumbnail_background_button"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="?attr/selectableItemBackground"/>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
<!-- CONTENT -->
|
<!-- CONTENT -->
|
||||||
<RelativeLayout
|
<LinearLayout
|
||||||
android:id="@+id/detail_content_root_layout"
|
android:id="@+id/detail_content_root_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_below="@+id/detail_thumbnail_root_layout"
|
android:background="?android:windowBackground"
|
||||||
android:background="?android:windowBackground">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<!-- TITLE -->
|
<!-- TITLE -->
|
||||||
<RelativeLayout
|
<FrameLayout
|
||||||
android:id="@+id/detail_title_root_layout"
|
android:id="@+id/detail_title_root_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:paddingLeft="12dp"
|
android:paddingLeft="12dp"
|
||||||
android:paddingRight="12dp"
|
android:paddingRight="12dp">
|
||||||
android:paddingTop="12dp">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/detail_video_title_view"
|
android:id="@+id/detail_video_title_view"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_alignParentLeft="true"
|
android:layout_marginRight="20dp"
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_toLeftOf="@+id/detail_toggle_description_view"
|
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:paddingBottom="2dp"
|
android:paddingBottom="8dp"
|
||||||
android:paddingTop="6dp"
|
android:paddingTop="12dp"
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
android:textSize="@dimen/video_item_detail_title_text_size"
|
android:textSize="@dimen/video_item_detail_title_text_size"
|
||||||
tools:ignore="RtlHardcoded"
|
tools:ignore="RtlHardcoded"
|
||||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum. Nunc eleifend est quis ipsum porttitor egestas. Sed facilisis, nisl quis eleifend pellentesque, orci metus egestas dolor, at accumsan eros metus quis libero."/>
|
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum. Nunc eleifend est quis ipsum porttitor egestas. Sed facilisis, nisl quis eleifend pellentesque, orci metus egestas dolor, at accumsan eros metus quis libero." />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/detail_toggle_description_view"
|
android:id="@+id/detail_toggle_description_view"
|
||||||
android:layout_width="15dp"
|
android:layout_width="15dp"
|
||||||
android:layout_height="15dp"
|
android:layout_height="15dp"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_gravity="center_vertical|right"
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_marginLeft="5dp"
|
android:layout_marginLeft="5dp"
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:src="@drawable/arrow_down"
|
android:src="@drawable/arrow_down"
|
||||||
tools:ignore="ContentDescription,RtlHardcoded"/>
|
tools:ignore="ContentDescription,RtlHardcoded" />
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
<!--HIDING ROOT-->
|
<!--HIDING ROOT-->
|
||||||
<RelativeLayout
|
<LinearLayout
|
||||||
android:id="@+id/detail_content_root_hiding"
|
android:id="@+id/detail_content_root_hiding"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_below="@+id/detail_title_root_layout"
|
android:orientation="vertical"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
@ -122,197 +108,156 @@
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:id="@+id/detail_root"
|
android:id="@+id/detail_root"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="55dp"
|
||||||
android:layout_marginLeft="12dp"
|
android:layout_marginLeft="12dp"
|
||||||
android:layout_marginRight="12dp"
|
android:layout_marginRight="12dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
android:baselineAligned="false"
|
android:baselineAligned="false"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal">
|
||||||
android:paddingTop="6dp">
|
|
||||||
|
|
||||||
<!-- VIEW & THUMBS -->
|
<!-- VIEW & THUMBS -->
|
||||||
<LinearLayout
|
<TextView
|
||||||
android:id="@+id/detail_views_thumbs_root"
|
android:id="@+id/detail_view_count_view"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_marginBottom="6dp"
|
||||||
android:orientation="vertical"
|
android:layout_marginTop="6dp"
|
||||||
android:paddingTop="6dp"
|
android:lines="1"
|
||||||
tools:ignore="RtlHardcoded">
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:textSize="@dimen/video_item_detail_views_text_size"
|
||||||
|
tools:ignore="RtlHardcoded"
|
||||||
|
tools:text="2,816,821,505 views" />
|
||||||
|
|
||||||
<TextView
|
<ImageView
|
||||||
android:id="@+id/detail_view_count_view"
|
android:id="@+id/detail_thumbs_up_img_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="@dimen/video_item_detail_like_image_width"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||||
android:layout_gravity="left"
|
android:layout_below="@id/detail_view_count_view"
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
android:contentDescription="@string/detail_likes_img_view_description"
|
||||||
android:textSize="@dimen/video_item_detail_views_text_size"
|
android:src="?attr/thumbs_up" />
|
||||||
tools:ignore="RtlHardcoded"
|
|
||||||
tools:text="2,816,821,505 views"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
<TextView
|
||||||
android:id="@+id/detail_thumbs_root_layout"
|
android:id="@+id/detail_thumbs_up_count_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||||
android:layout_gravity="left"
|
android:layout_below="@id/detail_view_count_view"
|
||||||
android:gravity="center_vertical"
|
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
|
||||||
android:minHeight="30dp"
|
android:layout_toRightOf="@id/detail_thumbs_up_img_view"
|
||||||
android:orientation="horizontal">
|
android:gravity="left|center_vertical"
|
||||||
|
android:lines="1"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textSize="@dimen/video_item_detail_likes_text_size"
|
||||||
|
tools:ignore="RtlHardcoded"
|
||||||
|
tools:text="12M" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/detail_thumbs_up_img_view"
|
android:id="@+id/detail_thumbs_down_img_view"
|
||||||
android:layout_width="@dimen/video_item_detail_like_image_width"
|
android:layout_width="@dimen/video_item_detail_like_image_width"
|
||||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||||
android:contentDescription="@string/detail_likes_img_view_description"
|
android:layout_below="@id/detail_view_count_view"
|
||||||
android:src="?attr/thumbs_up"/>
|
android:layout_marginLeft="15dp"
|
||||||
|
android:layout_toRightOf="@id/detail_thumbs_up_count_view"
|
||||||
|
android:contentDescription="@string/detail_dislikes_img_view_description"
|
||||||
|
android:src="?attr/thumbs_down"
|
||||||
|
tools:ignore="RtlHardcoded" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/detail_thumbs_up_count_view"
|
android:id="@+id/detail_thumbs_down_count_view"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||||
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
|
android:layout_below="@id/detail_view_count_view"
|
||||||
android:gravity="left|center_vertical"
|
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:layout_toRightOf="@id/detail_thumbs_down_img_view"
|
||||||
android:textSize="@dimen/video_item_detail_likes_text_size"
|
android:gravity="left|center_vertical"
|
||||||
tools:ignore="RtlHardcoded"
|
android:lines="1"
|
||||||
tools:text="12M"/>
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textSize="@dimen/video_item_detail_likes_text_size"
|
||||||
|
tools:ignore="RtlHardcoded"
|
||||||
|
tools:text="10K" />
|
||||||
|
|
||||||
<ImageView
|
<TextView
|
||||||
android:id="@+id/detail_thumbs_down_img_view"
|
android:id="@+id/detail_thumbs_disabled_view"
|
||||||
android:layout_width="@dimen/video_item_detail_like_image_width"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginLeft="15dp"
|
android:layout_below="@id/detail_view_count_view"
|
||||||
android:contentDescription="@string/detail_dislikes_img_view_description"
|
android:layout_marginLeft="15dp"
|
||||||
android:src="?attr/thumbs_down"
|
android:gravity="left|center_vertical"
|
||||||
tools:ignore="RtlHardcoded"/>
|
android:text="@string/disabled"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
<TextView
|
android:textSize="@dimen/video_item_detail_likes_text_size"
|
||||||
android:id="@+id/detail_thumbs_down_count_view"
|
android:textStyle="bold"
|
||||||
android:layout_width="wrap_content"
|
android:visibility="gone"
|
||||||
android:layout_height="match_parent"
|
tools:ignore="RtlHardcoded" />
|
||||||
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
|
|
||||||
android:gravity="left|center_vertical"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
|
||||||
android:textSize="@dimen/video_item_detail_likes_text_size"
|
|
||||||
tools:ignore="RtlHardcoded"
|
|
||||||
tools:text="10K"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/detail_thumbs_disabled_view"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginLeft="15dp"
|
|
||||||
android:gravity="left|center_vertical"
|
|
||||||
android:text="@string/disabled"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
|
||||||
android:textSize="@dimen/video_item_detail_likes_text_size"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:ignore="RtlHardcoded"/>
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- CONTROLS -->
|
<!-- CONTROLS -->
|
||||||
<LinearLayout
|
<TextView
|
||||||
android:id="@+id/detail_controls_root"
|
android:id="@+id/detail_controls_popup"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="80dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="55dp"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_alignParentTop="true"
|
||||||
android:orientation="horizontal"
|
android:layout_gravity="center_vertical"
|
||||||
tools:ignore="RtlHardcoded">
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:clickable="true"
|
||||||
<TextView
|
android:contentDescription="@string/open_in_popup_mode"
|
||||||
android:id="@+id/detail_controls_background"
|
android:drawableTop="?attr/popup"
|
||||||
android:layout_width="80dp"
|
android:gravity="center"
|
||||||
android:layout_height="55dp"
|
android:paddingBottom="6dp"
|
||||||
android:layout_gravity="center_vertical"
|
android:paddingTop="6dp"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
android:text="@string/controls_popup_title"
|
||||||
android:clickable="true"
|
android:textSize="12sp" />
|
||||||
android:contentDescription="@string/play_audio"
|
|
||||||
android:drawableTop="?attr/audio"
|
|
||||||
android:gravity="center"
|
|
||||||
android:paddingBottom="6dp"
|
|
||||||
android:paddingTop="6dp"
|
|
||||||
android:text="@string/controls_background_title"
|
|
||||||
android:textSize="12sp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/detail_controls_popup"
|
|
||||||
android:layout_width="80dp"
|
|
||||||
android:layout_height="55dp"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:clickable="true"
|
|
||||||
android:contentDescription="@string/open_in_popup_mode"
|
|
||||||
android:drawableTop="?attr/popup"
|
|
||||||
android:gravity="center"
|
|
||||||
android:paddingBottom="6dp"
|
|
||||||
android:paddingTop="6dp"
|
|
||||||
android:text="@string/controls_popup_title"
|
|
||||||
android:textSize="12sp"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/detail_controls_background"
|
||||||
|
android:layout_width="80dp"
|
||||||
|
android:layout_height="55dp"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_toLeftOf="@id/detail_controls_popup"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:clickable="true"
|
||||||
|
android:contentDescription="@string/play_audio"
|
||||||
|
android:drawableTop="?attr/audio"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingBottom="6dp"
|
||||||
|
android:paddingTop="6dp"
|
||||||
|
android:text="@string/controls_background_title"
|
||||||
|
android:textSize="12sp" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<!--UPLOADER-->
|
<!--UPLOADER-->
|
||||||
<FrameLayout
|
<LinearLayout
|
||||||
android:id="@+id/detail_uploader_root_layout"
|
android:id="@+id/detail_uploader_root_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/detail_root">
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:paddingLeft="12dp"
|
||||||
|
android:paddingRight="12dp"
|
||||||
|
android:paddingTop="8dp">
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/detail_uploader_layout"
|
<de.hdodenhof.circleimageview.CircleImageView
|
||||||
|
android:id="@+id/detail_uploader_thumbnail_view"
|
||||||
|
android:layout_width="@dimen/video_item_detail_uploader_image_size"
|
||||||
|
android:layout_height="@dimen/video_item_detail_uploader_image_size"
|
||||||
|
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
|
||||||
|
android:src="@drawable/buddy"
|
||||||
|
tools:ignore="RtlHardcoded" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/detail_uploader_text_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginLeft="15dp"
|
||||||
android:layout_marginLeft="12dp"
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
android:layout_marginRight="12dp"
|
android:textSize="@dimen/video_item_detail_uploader_text_size"
|
||||||
android:layout_marginTop="8dp">
|
android:textStyle="bold"
|
||||||
|
tools:ignore="RtlHardcoded"
|
||||||
|
tools:text="Uploader" />
|
||||||
<de.hdodenhof.circleimageview.CircleImageView
|
|
||||||
android:id="@+id/detail_uploader_thumbnail_view"
|
|
||||||
android:layout_width="@dimen/video_item_detail_uploader_image_size"
|
|
||||||
android:layout_height="@dimen/video_item_detail_uploader_image_size"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
|
|
||||||
android:src="@drawable/buddy"
|
|
||||||
tools:ignore="RtlHardcoded"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/detail_uploader_text_view"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_marginLeft="15dp"
|
|
||||||
android:layout_toEndOf="@+id/detail_uploader_thumbnail_view"
|
|
||||||
android:layout_toRightOf="@+id/detail_uploader_thumbnail_view"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
|
||||||
android:textSize="@dimen/video_item_detail_uploader_text_size"
|
|
||||||
android:textStyle="bold"
|
|
||||||
tools:ignore="RtlHardcoded"
|
|
||||||
tools:text="Uploader"/>
|
|
||||||
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1px"
|
|
||||||
android:layout_gravity="bottom"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginRight="8dp"
|
|
||||||
android:background="?attr/separatorColor"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/detail_uploader_button"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
tools:visibility="gone"/>
|
|
||||||
|
|
||||||
<!--<Button
|
<!--<Button
|
||||||
android:id="@+id/detail_uploader_subscribe"
|
android:id="@+id/detail_uploader_subscribe"
|
||||||
|
@ -326,15 +271,22 @@
|
||||||
android:drawableLeft="@drawable/ic_rss_feed_white_24dp"
|
android:drawableLeft="@drawable/ic_rss_feed_white_24dp"
|
||||||
tools:ignore="RtlHardcoded"
|
tools:ignore="RtlHardcoded"
|
||||||
android:visibility="gone"/>-->
|
android:visibility="gone"/>-->
|
||||||
</FrameLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:background="?attr/separatorColor" />
|
||||||
|
|
||||||
<!--DESCRIPTIONS-->
|
<!--DESCRIPTIONS-->
|
||||||
<RelativeLayout
|
<LinearLayout
|
||||||
android:id="@+id/detail_description_root_layout"
|
android:id="@+id/detail_description_root_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/detail_uploader_root_layout"
|
|
||||||
android:layout_marginTop="5dp"
|
android:layout_marginTop="5dp"
|
||||||
|
android:orientation="vertical"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
@ -347,106 +299,74 @@
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
android:textSize="@dimen/video_item_detail_upload_date_text_size"
|
android:textSize="@dimen/video_item_detail_upload_date_text_size"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
tools:text="Upload date"/>
|
tools:text="Published on Oct 2, 2009" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/detail_description_view"
|
android:id="@+id/detail_description_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/detail_upload_date_view"
|
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:layout_marginLeft="12dp"
|
android:layout_marginLeft="12dp"
|
||||||
android:layout_marginRight="12dp"
|
android:layout_marginRight="12dp"
|
||||||
android:layout_marginTop="3dp"
|
android:layout_marginTop="3dp"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
android:textSize="@dimen/video_item_detail_description_text_size"
|
android:textSize="@dimen/video_item_detail_description_text_size"
|
||||||
tools:text="Description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum."/>
|
tools:text="Description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum." />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1px"
|
android:layout_height="1px"
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
android:layout_marginRight="8dp"
|
android:layout_marginRight="8dp"
|
||||||
android:background="?attr/separatorColor"/>
|
android:background="?attr/separatorColor" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!--NEXT AND RELATED VIDEOS-->
|
<!--NEXT AND RELATED VIDEOS-->
|
||||||
<RelativeLayout
|
<LinearLayout
|
||||||
android:id="@+id/detail_related_streams_root_layout"
|
android:id="@+id/detail_related_streams_root_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/detail_description_root_layout"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_gravity="center_horizontal|bottom"
|
android:layout_gravity="center_horizontal|bottom"
|
||||||
android:layout_marginTop="14dp">
|
android:layout_marginTop="14dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/detail_next_stream_title"
|
android:id="@+id/detail_next_stream_title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_marginLeft="12dp"
|
android:layout_marginLeft="12dp"
|
||||||
android:text="@string/next_video_title"
|
android:text="@string/next_video_title"
|
||||||
android:textAllCaps="true"
|
android:textAllCaps="true"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
android:textSize="@dimen/video_item_detail_next_text_size"
|
android:textSize="@dimen/video_item_detail_next_text_size"
|
||||||
tools:ignore="RtlHardcoded"/>
|
tools:ignore="RtlHardcoded" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/detail_related_streams_view"
|
android:id="@+id/detail_related_streams_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_below="@id/detail_next_stream_title"
|
|
||||||
android:layout_marginTop="2dp"
|
android:layout_marginTop="2dp"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
tools:minHeight="50dp"/>
|
tools:minHeight="50dp" />
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/detail_related_streams_expand"
|
android:id="@+id/detail_related_streams_expand"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/detail_related_streams_view"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:paddingBottom="10dp"
|
android:paddingBottom="10dp"
|
||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
android:src="?attr/expand"
|
android:src="?attr/expand"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:textAllCaps="true"
|
android:textAllCaps="true"
|
||||||
tools:ignore="ContentDescription"/>
|
tools:ignore="ContentDescription" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- LOADING BAR, ERROR & RETRY -->
|
||||||
</RelativeLayout>
|
<include layout="@layout/loading_error_retry" />
|
||||||
|
</LinearLayout>
|
||||||
</RelativeLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- LOADING BAR -->
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/loading_progress_bar"
|
|
||||||
style="@style/Widget.AppCompat.ProgressBar"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@+id/detail_title_root_layout"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_marginTop="15dp"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible"/>
|
|
||||||
|
|
||||||
<!--ERROR PANEL-->
|
|
||||||
<include
|
|
||||||
android:id="@+id/error_panel"
|
|
||||||
layout="@layout/error_retry"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@+id/detail_title_root_layout"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_marginTop="@dimen/video_item_detail_error_panel_margin"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible"/>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
</RelativeLayout>
|
|
||||||
</com.nirhart.parallaxscroll.views.ParallaxScrollView>
|
</com.nirhart.parallaxscroll.views.ParallaxScrollView>
|
||||||
</RelativeLayout>
|
</FrameLayout>
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="15dp">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/loading_progress_bar"
|
||||||
|
style="@style/Widget.AppCompat.ProgressBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<!--ERROR PANEL-->
|
||||||
|
<include
|
||||||
|
android:id="@+id/error_panel"
|
||||||
|
layout="@layout/error_retry"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/video_item_detail_error_panel_margin"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
</FrameLayout>
|
|
@ -4,11 +4,10 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/itemRoot"
|
android:id="@+id/itemRoot"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="@dimen/video_item_search_height"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:orientation="vertical"
|
android:padding="@dimen/video_item_search_padding">
|
||||||
android:padding="12dp">
|
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/itemThumbnailView"
|
android:id="@+id/itemThumbnailView"
|
||||||
|
@ -42,55 +41,40 @@
|
||||||
tools:ignore="RtlHardcoded"
|
tools:ignore="RtlHardcoded"
|
||||||
tools:text="1:09:10"/>
|
tools:text="1:09:10"/>
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/itemVideoTitleView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/video_item_search_thumbnail_image_height"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
android:layout_toRightOf="@id/itemThumbnailView"
|
android:layout_toRightOf="@id/itemThumbnailView"
|
||||||
android:orientation="vertical"
|
android:ellipsize="end"
|
||||||
tools:ignore="RtlHardcoded">
|
android:lines="2"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:textSize="@dimen/video_item_search_title_text_size"
|
||||||
|
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/itemVideoTitleView"
|
android:id="@+id/itemUploaderView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_above="@+id/itemUploaderView"
|
android:layout_above="@id/itemAdditionalDetails"
|
||||||
android:layout_alignParentTop="true"
|
android:layout_toRightOf="@id/itemThumbnailView"
|
||||||
android:ellipsize="end"
|
android:lines="1"
|
||||||
android:maxLines="2"
|
android:layout_marginBottom="@dimen/video_item_detail_description_to_details_margin"
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:textSize="@dimen/video_item_search_title_text_size"
|
android:textSize="@dimen/video_item_search_uploader_text_size"
|
||||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum"/>
|
tools:text="Uploader"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/itemUploaderView"
|
android:id="@+id/itemAdditionalDetails"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_above="@+id/itemUploadDateView"
|
android:layout_alignParentBottom="true"
|
||||||
android:maxLines="1"
|
android:layout_toRightOf="@id/itemThumbnailView"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:lines="1"
|
||||||
android:textSize="@dimen/video_item_search_uploader_text_size"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
tools:text="Uploader"/>
|
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
||||||
|
tools:text="2 years ago • 10M views"/>
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/itemUploadDateView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
|
||||||
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
|
||||||
tools:text="2 years ago • "/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/itemViewCountView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:layout_toRightOf="@+id/itemUploadDateView"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
|
||||||
android:textSize="@dimen/video_item_search_upload_date_text_size"
|
|
||||||
tools:text="10M views"/>
|
|
||||||
</RelativeLayout>
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
|
@ -37,7 +37,7 @@
|
||||||
android:layout_gravity="right|center_vertical"
|
android:layout_gravity="right|center_vertical"
|
||||||
tools:ignore="RtlHardcoded">
|
tools:ignore="RtlHardcoded">
|
||||||
|
|
||||||
<FrameLayout
|
<View
|
||||||
android:layout_width="28dp"
|
android:layout_width="28dp"
|
||||||
android:layout_height="28dp"
|
android:layout_height="28dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
<!-- 16 / 9 ratio-->
|
<!-- 16 / 9 ratio-->
|
||||||
<dimen name="video_item_search_thumbnail_image_width">142dp</dimen>
|
<dimen name="video_item_search_thumbnail_image_width">142dp</dimen>
|
||||||
<dimen name="video_item_search_thumbnail_image_height">80dp</dimen>
|
<dimen name="video_item_search_thumbnail_image_height">80dp</dimen>
|
||||||
|
<!-- Calculated: 2*video_item_search_padding + video_item_search_thumbnail_image_height -->
|
||||||
|
<dimen name="video_item_search_height">104dp</dimen>
|
||||||
<!-- Paddings & Margins -->
|
<!-- Paddings & Margins -->
|
||||||
<dimen name="video_item_search_image_right_margin">10dp</dimen>
|
<dimen name="video_item_search_image_right_margin">10dp</dimen>
|
||||||
<dimen name="video_item_search_duration_vertical_padding">1sp</dimen>
|
<dimen name="video_item_search_duration_vertical_padding">1sp</dimen>
|
||||||
|
|
|
@ -11,11 +11,15 @@
|
||||||
<!-- 16 / 9 ratio-->
|
<!-- 16 / 9 ratio-->
|
||||||
<dimen name="video_item_search_thumbnail_image_width">124dp</dimen>
|
<dimen name="video_item_search_thumbnail_image_width">124dp</dimen>
|
||||||
<dimen name="video_item_search_thumbnail_image_height">70dp</dimen>
|
<dimen name="video_item_search_thumbnail_image_height">70dp</dimen>
|
||||||
|
<!-- Calculated: 2*video_item_search_padding + video_item_search_thumbnail_image_height -->
|
||||||
|
<dimen name="video_item_search_height">94dp</dimen>
|
||||||
<!-- Paddings & Margins -->
|
<!-- Paddings & Margins -->
|
||||||
|
<dimen name="video_item_search_padding">12dp</dimen>
|
||||||
<dimen name="video_item_search_image_right_margin">6dp</dimen>
|
<dimen name="video_item_search_image_right_margin">6dp</dimen>
|
||||||
<dimen name="video_item_search_duration_vertical_padding">1sp</dimen>
|
<dimen name="video_item_search_duration_vertical_padding">1sp</dimen>
|
||||||
<dimen name="video_item_search_duration_horizontal_padding">5sp</dimen>
|
<dimen name="video_item_search_duration_horizontal_padding">5sp</dimen>
|
||||||
<dimen name="video_item_search_duration_margin">2sp</dimen>
|
<dimen name="video_item_search_duration_margin">2sp</dimen>
|
||||||
|
<dimen name="video_item_detail_description_to_details_margin">4dp</dimen>
|
||||||
<!-- Miscellaneous -->
|
<!-- Miscellaneous -->
|
||||||
<dimen name="popup_default_width">180dp</dimen>
|
<dimen name="popup_default_width">180dp</dimen>
|
||||||
<dimen name="popup_minimum_width">120dp</dimen>
|
<dimen name="popup_minimum_width">120dp</dimen>
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package org.schabi.newpipe.report;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.schabi.newpipe.MainActivity;
|
||||||
|
import org.schabi.newpipe.RouterActivity;
|
||||||
|
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link ErrorActivity}
|
||||||
|
*/
|
||||||
|
public class ErrorActivityTest {
|
||||||
|
@Test
|
||||||
|
public void getReturnActivity() throws Exception {
|
||||||
|
Class<? extends Activity> returnActivity;
|
||||||
|
returnActivity = ErrorActivity.getReturnActivity(MainActivity.class);
|
||||||
|
assertEquals(MainActivity.class, returnActivity);
|
||||||
|
|
||||||
|
returnActivity = ErrorActivity.getReturnActivity(RouterActivity.class);
|
||||||
|
assertEquals(RouterActivity.class, returnActivity);
|
||||||
|
|
||||||
|
returnActivity = ErrorActivity.getReturnActivity(null);
|
||||||
|
assertNull(returnActivity);
|
||||||
|
|
||||||
|
returnActivity = ErrorActivity.getReturnActivity(Integer.class);
|
||||||
|
assertEquals(MainActivity.class, returnActivity);
|
||||||
|
|
||||||
|
returnActivity = ErrorActivity.getReturnActivity(VideoDetailFragment.class);
|
||||||
|
assertEquals(MainActivity.class, returnActivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -31,18 +31,18 @@ import static org.mockito.Mockito.when;
|
||||||
public class DownloadManagerImplTest {
|
public class DownloadManagerImplTest {
|
||||||
|
|
||||||
private DownloadManagerImpl downloadManager;
|
private DownloadManagerImpl downloadManager;
|
||||||
private DownloadDataSource dowloadDataSource;
|
private DownloadDataSource downloadDataSource;
|
||||||
private ArrayList<DownloadMission> missions;
|
private ArrayList<DownloadMission> missions;
|
||||||
|
|
||||||
@org.junit.Before
|
@org.junit.Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
dowloadDataSource = mock(DownloadDataSource.class);
|
downloadDataSource = mock(DownloadDataSource.class);
|
||||||
missions = new ArrayList<>();
|
missions = new ArrayList<>();
|
||||||
for(int i = 0; i < 50; ++i){
|
for(int i = 0; i < 50; ++i){
|
||||||
missions.add(generateFinishedDownloadMission());
|
missions.add(generateFinishedDownloadMission());
|
||||||
}
|
}
|
||||||
when(dowloadDataSource.loadMissions()).thenReturn(new ArrayList<>(missions));
|
when(downloadDataSource.loadMissions()).thenReturn(new ArrayList<>(missions));
|
||||||
downloadManager = new DownloadManagerImpl(new ArrayList<String>(), dowloadDataSource);
|
downloadManager = new DownloadManagerImpl(new ArrayList<String>(), downloadDataSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = NullPointerException.class)
|
@Test(expected = NullPointerException.class)
|
||||||
|
@ -82,10 +82,10 @@ public class DownloadManagerImplTest {
|
||||||
missions.add(mission);
|
missions.add(mission);
|
||||||
}
|
}
|
||||||
|
|
||||||
dowloadDataSource = mock(DownloadDataSource.class);
|
downloadDataSource = mock(DownloadDataSource.class);
|
||||||
when(dowloadDataSource.loadMissions()).thenReturn(new ArrayList<>(missions));
|
when(downloadDataSource.loadMissions()).thenReturn(new ArrayList<>(missions));
|
||||||
downloadManager = new DownloadManagerImpl(new ArrayList<String>(), dowloadDataSource);
|
downloadManager = new DownloadManagerImpl(new ArrayList<String>(), downloadDataSource);
|
||||||
verify(dowloadDataSource, times(1)).loadMissions();
|
verify(downloadDataSource, times(1)).loadMissions();
|
||||||
|
|
||||||
assertEquals(50, downloadManager.getCount());
|
assertEquals(50, downloadManager.getCount());
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue