diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 665a327a3..6126197a0 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -5,7 +5,7 @@ READ THIS GUIDELINES CAREFULLY BEFORE CONTRIBUTING. ## 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 diff --git a/app/build.gradle b/app/build.gradle index 1101abe32..e2fa1449e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,6 +10,8 @@ android { targetSdkVersion 25 versionCode 35 versionName "0.9.8" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { @@ -31,6 +33,11 @@ android { } dependencies { + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') { + exclude module: 'support-annotations' + } + + testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' testCompile 'org.json:json:20160810' diff --git a/app/src/androidTest/java/org/schabi/newpipe/report/ErrorInfoTest.java b/app/src/androidTest/java/org/schabi/newpipe/report/ErrorInfoTest.java new file mode 100644 index 000000000..cdeb47483 --- /dev/null +++ b/app/src/androidTest/java/org/schabi/newpipe/report/ErrorInfoTest.java @@ -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(); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index e2bdc9f68..ee1de0196 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -14,6 +14,7 @@ import org.acra.sender.ReportSenderFactory; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.report.AcraReportSenderFactory; import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.settings.SettingsActivity; import org.schabi.newpipe.util.ThemeHelper; @@ -59,7 +60,7 @@ public class App extends Application { } catch(ACRAConfigurationException ace) { ace.printStackTrace(); 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)); } diff --git a/app/src/main/java/org/schabi/newpipe/ImageErrorLoadingListener.java b/app/src/main/java/org/schabi/newpipe/ImageErrorLoadingListener.java index 8f1940fda..eb2202fca 100644 --- a/app/src/main/java/org/schabi/newpipe/ImageErrorLoadingListener.java +++ b/app/src/main/java/org/schabi/newpipe/ImageErrorLoadingListener.java @@ -9,6 +9,7 @@ import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.report.UserAction; /** * 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) { ErrorActivity.reportError(context, failReason.getCause(), null, rootView, - ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE, + ErrorActivity.ErrorInfo.make(UserAction.LOAD_IMAGE, NewPipe.getNameOfService(serviceId), imageUri, R.string.could_not_load_image)); } diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java index aef3d3bce..382f2ffa5 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java @@ -35,7 +35,6 @@ import us.shandian.giga.service.DownloadManagerService; import us.shandian.giga.ui.fragment.AllMissionsFragment; import us.shandian.giga.ui.fragment.MissionsFragment; import us.shandian.giga.util.CrashHandler; -import us.shandian.giga.util.Utility; public class DownloadActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { @@ -125,11 +124,11 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O // Create the view LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); View v = inflater.inflate(R.layout.dialog_url, null); - final EditText name = Utility.findViewById(v, R.id.file_name); - final TextView tCount = Utility.findViewById(v, R.id.threads_count); - final SeekBar threads = Utility.findViewById(v, R.id.threads); - final Toolbar toolbar = Utility.findViewById(v, R.id.toolbar); - final RadioButton audioButton = (RadioButton) Utility.findViewById(v, R.id.audio_button); + final EditText name = (EditText) v.findViewById(R.id.file_name); + final TextView tCount = (TextView) v.findViewById(R.id.threads_count); + final SeekBar threads = (SeekBar) v.findViewById(R.id.threads); + final Toolbar toolbar = (Toolbar) v.findViewById(R.id.toolbar); + final RadioButton audioButton = (RadioButton) v.findViewById(R.id.audio_button); threads.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index 84770ac68..08a3528d6 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -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. */ private String createFileName(String fileName) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/channel/ChannelFragment.java index 9a1cfe1a7..1bb7ddee7 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/channel/ChannelFragment.java @@ -26,6 +26,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfo; 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.InfoListAdapter; import org.schabi.newpipe.util.Constants; @@ -213,7 +214,7 @@ public class ChannelFragment extends BaseFragment implements ChannelExtractorWor channelVideosList.setLayoutManager(new LinearLayoutManager(activity)); if (infoListAdapter == null) { - infoListAdapter = new InfoListAdapter(activity, rootView); + infoListAdapter = new InfoListAdapter(activity); if (savedInstanceState != null) { //noinspection unchecked ArrayList serializable = (ArrayList) savedInstanceState.getSerializable(INFO_LIST_KEY); @@ -245,23 +246,12 @@ public class ChannelFragment extends BaseFragment implements ChannelExtractorWor }); channelVideosList.clearOnScrollListeners(); - channelVideosList.addOnScrollListener(new RecyclerView.OnScrollListener() { + channelVideosList.addOnScrollListener(new OnScrollBelowItemsListener() { @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - int pastVisiblesItems, visibleItemCount, totalItemCount; - super.onScrolled(recyclerView, dx, dy); - //check for scroll down - 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(); - } + public void onScrolledDown(RecyclerView recyclerView) { + if ((currentChannelWorker == null || !currentChannelWorker.isRunning()) && hasNextPage && !isLoading.get()) { + pageNumber++; + loadMoreVideos(); } } }); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/ActionBarHandler.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/ActionBarHandler.java index 73f2551b0..77896b475 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/ActionBarHandler.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/ActionBarHandler.java @@ -67,12 +67,12 @@ class ActionBarHandler { public void setupStreamList(final List videoStreams, Spinner toolbarSpinner) { 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); toolbarSpinner.setAdapter(new SpinnerToolbarAdapter(activity, videoStreams, isExternalPlayerEnabled)); - toolbarSpinner.setSelection(defaultResolutionIndex); + toolbarSpinner.setSelection(selectedVideoStream); toolbarSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 2721eefc9..5588773ec 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -1,12 +1,17 @@ package org.schabi.newpipe.fragments.detail; import android.app.Activity; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.Build; 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.support.annotation.FloatRange; 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.AlertDialog; import android.text.Html; +import android.text.Spanned; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.util.Log; @@ -26,7 +32,7 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; +import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; 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.PopupVideoPlayer; import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; @@ -105,6 +112,10 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor private boolean showRelatedStreams; private boolean wasRelatedStreamsExpanded = false; + private Handler uiHandler; + private Handler backgroundHandler; + private HandlerThread backgroundHandlerThread; + /*////////////////////////////////////////////////////////////////////////// // Views //////////////////////////////////////////////////////////////////////////*/ @@ -112,9 +123,9 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor private Spinner spinnerToolbar; private ParallaxScrollView parallaxScrollRootView; - private RelativeLayout contentRootLayoutHiding; + private LinearLayout contentRootLayoutHiding; - private Button thumbnailBackgroundButton; + private View thumbnailBackgroundButton; private ImageView thumbnailImageView; private ImageView thumbnailPlayButton; @@ -126,12 +137,11 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor private TextView detailControlsBackground; private TextView detailControlsPopup; - private RelativeLayout videoDescriptionRootLayout; + private LinearLayout videoDescriptionRootLayout; private TextView videoUploadDateView; private TextView videoDescriptionView; private View uploaderRootLayout; - private Button uploaderButton; private TextView uploaderTextView; private ImageView uploaderThumb; @@ -142,7 +152,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor private TextView thumbsDisabledTextView; private TextView nextStreamTitle; - private RelativeLayout relatedStreamRootLayout; + private LinearLayout relatedStreamRootLayout; private LinearLayout relatedStreamsView; private ImageButton relatedStreamExpandButton; @@ -194,6 +204,16 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor thousand = getString(R.string.short_thousand); million = getString(R.string.short_million); 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 @@ -241,6 +261,11 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor @Override public void onDestroy() { super.onDestroy(); + if (backgroundHandlerThread != null) { + backgroundHandlerThread.quit(); + } + backgroundHandlerThread = null; + backgroundHandler = null; PreferenceManager.getDefaultSharedPreferences(activity).unregisterOnSharedPreferenceChangeListener(this); } @@ -272,7 +297,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor videoUploadDateView = null; videoDescriptionView = null; - uploaderButton = null; + uploaderRootLayout = null; uploaderTextView = null; uploaderThumb = null; @@ -353,10 +378,14 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor case R.id.detail_controls_popup: openInPopup(); break; - case R.id.detail_uploader_button: - NavigationHelper.openChannelFragment(getFragmentManager(), currentStreamInfo.service_id, currentStreamInfo.channel_url, currentStreamInfo.uploader); + case R.id.detail_uploader_root_layout: + 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; - case R.id.detail_thumbnail_background_button: + case R.id.detail_thumbnail_root_layout: playVideo(currentStreamInfo); break; 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); - //thumbnailRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_thumbnail_root_layout); - thumbnailBackgroundButton = (Button) rootView.findViewById(R.id.detail_thumbnail_background_button); + thumbnailBackgroundButton = rootView.findViewById(R.id.detail_thumbnail_root_layout); thumbnailImageView = (ImageView) rootView.findViewById(R.id.detail_thumbnail_image_view); 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); 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); 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); 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); 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); 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); relatedStreamsView = (LinearLayout) rootView.findViewById(R.id.detail_related_streams_view); + relatedStreamExpandButton = ((ImageButton) rootView.findViewById(R.id.detail_related_streams_expand)); actionBarHandler = new ActionBarHandler(activity); videoDescriptionView.setMovementMethod(LinkMovementMethod.getInstance()); - infoItemBuilder = new InfoItemBuilder(activity, rootView.findViewById(android.R.id.content)); + infoItemBuilder = new InfoItemBuilder(activity); setHeightThumbnail(); } @@ -539,7 +567,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor }); videoTitleRoot.setOnClickListener(this); - uploaderButton.setOnClickListener(this); + uploaderRootLayout.setOnClickListener(this); thumbnailBackgroundButton.setOnClickListener(this); detailControlsBackground.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() { @Override 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); @@ -739,8 +767,17 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor // Get url from the new top StackItem peek = stack.peek(); - if (peek.getInfo() != null) selectAndHandleInfo(peek.getInfo()); - else selectAndLoadVideo(0, peek.getUrl(), !TextUtils.isEmpty(peek.getTitle()) ? peek.getTitle() : ""); + if (peek.getInfo() != null) { + 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; } @@ -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 + "]"); currentStreamInfo = info; 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); 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)); 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); } - 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); videoTitleToggleArrow.setImageResource(R.drawable.arrow_down); videoTitleToggleArrow.setVisibility(View.VISIBLE); @@ -908,9 +938,32 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor toggleExpandRelatedVideos(currentStreamInfo); wasRelatedStreamsExpanded = false; } - setTitleToUrl(info.webpage_url, info.title); 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) { @@ -987,10 +1040,8 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor int height = isPortrait ? (int) (getResources().getDisplayMetrics().widthPixels / (16.0f / 9.0f)) : (int) (getResources().getDisplayMetrics().heightPixels / 2f); 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); - thumbnailBackgroundButton.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, height)); - thumbnailBackgroundButton.setMinimumHeight(height); } 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(); } } - /*////////////////////////////////////////////////////////////////////////// - // OnStreamInfoReceivedListener callbacks - //////////////////////////////////////////////////////////////////////////*/ private void setErrorImage(final int imageResource) { if (thumbnailImageView == null || activity == null) return; @@ -1055,6 +1103,10 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor currentStreamInfo = null; } + /*////////////////////////////////////////////////////////////////////////// + // OnStreamInfoReceivedListener callbacks + //////////////////////////////////////////////////////////////////////////*/ + @Override public void onReceive(StreamInfo 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); showContentWithAnimation(300, 0, 0); - animateView(loadingProgressBar, false, 200); - if (autoPlayEnabled) { - playVideo(info); - // Only auto play in the first open - autoPlayEnabled = false; - } - StreamInfoCache.getInstance().putInfo(info); isLoading.set(false); @@ -1126,4 +1171,69 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor public void onUnrecoverableError(Exception exception) { 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; + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/search/OnScrollBelowItemsListener.java b/app/src/main/java/org/schabi/newpipe/fragments/search/OnScrollBelowItemsListener.java new file mode 100644 index 000000000..51ef88706 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/search/OnScrollBelowItemsListener.java @@ -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); +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java index 3e04b94fd..375591a57 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java @@ -212,7 +212,7 @@ public class SearchFragment extends BaseFragment implements SuggestionWorker.OnS resultRecyclerView.setLayoutManager(new LinearLayoutManager(activity)); if (infoListAdapter == null) { - infoListAdapter = new InfoListAdapter(getActivity(), getActivity().findViewById(android.R.id.content)); + infoListAdapter = new InfoListAdapter(getActivity()); if (savedInstanceState != null) { //noinspection unchecked ArrayList serializable = (ArrayList) savedInstanceState.getSerializable(INFO_LIST_KEY); @@ -242,33 +242,24 @@ public class SearchFragment extends BaseFragment implements SuggestionWorker.OnS protected void initListeners() { super.initListeners(); resultRecyclerView.clearOnScrollListeners(); - resultRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + resultRecyclerView.addOnScrollListener(new OnScrollBelowItemsListener() { @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - int pastVisiblesItems, visibleItemCount, totalItemCount; - super.onScrolled(recyclerView, dx, dy); - //check for scroll down - if (dy > 0) { - LinearLayoutManager layoutManager = (LinearLayoutManager) resultRecyclerView.getLayoutManager(); - visibleItemCount = resultRecyclerView.getLayoutManager().getChildCount(); - totalItemCount = resultRecyclerView.getLayoutManager().getItemCount(); - pastVisiblesItems = layoutManager.findFirstVisibleItemPosition(); - - if ((visibleItemCount + pastVisiblesItems) >= totalItemCount && !isLoading.get()) { - pageNumber++; - recyclerView.post(new Runnable() { - @Override - public void run() { - infoListAdapter.showFooter(true); - } - }); - search(searchQuery, pageNumber); - } + public void onScrolledDown(RecyclerView recyclerView) { + if(!isLoading.get()) { + pageNumber++; + recyclerView.post(new Runnable() { + @Override + public void run() { + infoListAdapter.showFooter(true); + } + }); + search(searchQuery, pageNumber); } } }); } + @Override protected void reloadContent() { if (DEBUG) Log.d(TAG, "reloadContent() called"); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/ChannelInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/ChannelInfoItemHolder.java index 29eaafcd9..5543907e5 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/ChannelInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/ChannelInfoItemHolder.java @@ -31,8 +31,7 @@ import de.hdodenhof.circleimageview.CircleImageView; public class ChannelInfoItemHolder extends InfoItemHolder { public final CircleImageView itemThumbnailView; public final TextView itemChannelTitleView; - public final TextView itemSubscriberCountView; - public final TextView itemVideoCountView; + public final TextView itemAdditionalDetailView; public final TextView itemChannelDescriptionView; public final View itemRoot; @@ -42,8 +41,7 @@ public class ChannelInfoItemHolder extends InfoItemHolder { itemRoot = v.findViewById(R.id.itemRoot); itemThumbnailView = (CircleImageView) v.findViewById(R.id.itemThumbnailView); itemChannelTitleView = (TextView) v.findViewById(R.id.itemChannelTitleView); - itemSubscriberCountView = (TextView) v.findViewById(R.id.itemSubscriberCountView); - itemVideoCountView = (TextView) v.findViewById(R.id.itemVideoCountView); + itemAdditionalDetailView = (TextView) v.findViewById(R.id.itemAdditionalDetails); itemChannelDescriptionView = (TextView) v.findViewById(R.id.itemChannelDescriptionView); } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java index f83d954a2..1bed5f22d 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java @@ -17,6 +17,8 @@ import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; import org.schabi.newpipe.extractor.stream_info.StreamInfoItem; +import java.util.Locale; + /** * Created by Christian Schabesberger on 26.09.16. *

@@ -54,18 +56,35 @@ public class InfoItemBuilder { 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 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 onChannelInfoItemSelectedListener; - public InfoItemBuilder(Context context, View rootView) { - mContext = context; - this.rootView = rootView; + public InfoItemBuilder(Context context) { viewsS = context.getString(R.string.views); videosS = context.getString(R.string.videos); subsS = context.getString(R.string.subscriber); @@ -73,7 +92,6 @@ public class InfoItemBuilder { thousand = context.getString(R.string.short_thousand); million = context.getString(R.string.short_million); billion = context.getString(R.string.short_billion); - inflater = LayoutInflater.from(context); } public void setOnStreamInfoItemSelectedListener( @@ -107,6 +125,7 @@ public class InfoItemBuilder { public View buildView(ViewGroup parent, final InfoItem info) { View itemView = null; InfoItemHolder holder = null; + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); switch (info.infoType()) { case STREAM: //long start = System.nanoTime(); @@ -127,6 +146,22 @@ public class InfoItemBuilder { 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) { if (info.infoType() != InfoItem.InfoType.STREAM) { Log.e("InfoItemBuilder", "Info type not yet supported"); @@ -146,46 +181,59 @@ public class InfoItemBuilder { 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); - if (!TextUtils.isEmpty(info.thumbnail_url)) { - imageLoader.displayImage(info.thumbnail_url, - holder.itemThumbnailView, displayImageOptions, - new ImageErrorLoadingListener(mContext, rootView, info.service_id)); - } + holder.itemAdditionalDetails.setText(getStreamInfoDetailLine(info)); + + // Default thumbnail is shown on error, while loading and if the url is empty + imageLoader.displayImage(info.thumbnail_url, + holder.itemThumbnailView, + DISPLAY_STREAM_THUMBNAIL_OPTIONS, + new ImageErrorLoadingListener(holder.itemRoot.getContext(), holder.itemRoot.getRootView(), info.service_id)); + holder.itemRoot.setOnClickListener(new View.OnClickListener() { @Override 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) { if (!TextUtils.isEmpty(info.getTitle())) holder.itemChannelTitleView.setText(info.getTitle()); - holder.itemSubscriberCountView.setText(shortSubscriber(info.subscriberCount) + " • "); - holder.itemVideoCountView.setText(info.videoAmount + " " + videosS); + holder.itemAdditionalDetailView.setText(getChannelInfoDetailLine(info)); if (!TextUtils.isEmpty(info.description)) holder.itemChannelDescriptionView.setText(info.description); - holder.itemThumbnailView.setImageResource(R.drawable.buddy_channel_item); - if (!TextUtils.isEmpty(info.thumbnailUrl)) { - imageLoader.displayImage(info.thumbnailUrl, - holder.itemThumbnailView, - displayImageOptions, - new ImageErrorLoadingListener(mContext, rootView, info.serviceId)); - } + imageLoader.displayImage(info.thumbnailUrl, + holder.itemThumbnailView, + DISPLAY_CHANNEL_THUMBNAIL_OPTIONS, + new ImageErrorLoadingListener(holder.itemRoot.getContext(), holder.itemRoot.getRootView(), info.serviceId)); + holder.itemRoot.setOnClickListener(new View.OnClickListener() { @Override 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) { - String output = ""; + if(duration < 0) { + duration = 0; + } + String output; int days = duration / (24 * 60 * 60); /* greater than a day */ duration %= (24 * 60 * 60); int hours = duration / (60 * 60); /* greater than an hour */ @@ -228,46 +279,12 @@ public class InfoItemBuilder { //handle days if (days > 0) { - output = Integer.toString(days) + ":"; - } - // handle hours - 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); + output = String.format(Locale.US, "%d:%02d:%02d:%02d", days, hours, minutes, seconds); + } else if(hours > 0) { + output = String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds); } else { - output += "0" + Integer.toString(seconds); + output = String.format(Locale.US, "%d:%02d", minutes, seconds); } - return output; } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java index e17e2aaac..df0d9ac9f 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java @@ -55,8 +55,8 @@ public class InfoListAdapter extends RecyclerView.Adapter(); } @@ -78,6 +78,9 @@ public class InfoListAdapter extends RecyclerView.Adapter el, - final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) { + final Class returnActivity, View rootView, final ErrorInfo errorInfo) { if (rootView != null) { Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG) @@ -118,7 +103,7 @@ public class ErrorActivity extends AppCompatActivity { @Override public void onClick(View v) { ActivityCommunicator ac = ActivityCommunicator.getCommunicator(); - ac.returnActivity = returnAcitivty; + ac.returnActivity = returnActivity; Intent intent = new Intent(context, ErrorActivity.class); intent.putExtra(ERROR_INFO, errorInfo); intent.putExtra(ERROR_LIST, elToSl(el)); @@ -128,7 +113,7 @@ public class ErrorActivity extends AppCompatActivity { }).show(); } else { ActivityCommunicator ac = ActivityCommunicator.getCommunicator(); - ac.returnActivity = returnAcitivty; + ac.returnActivity = returnActivity; Intent intent = new Intent(context, ErrorActivity.class); intent.putExtra(ERROR_INFO, errorInfo); 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, - final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) { + final Class returnActivity, View rootView, final ErrorInfo errorInfo) { List el = null; if(e != null) { el = new Vector<>(); el.add(e); } - reportError(context, el, returnAcitivty, rootView, errorInfo); + reportError(context, el, returnActivity, rootView, errorInfo); } // async call 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 el = null; if(e != null) { el = new Vector<>(); el.add(e); } - reportError(handler, context, el, returnAcitivty, rootView, errorInfo); + reportError(handler, context, el, returnActivity, rootView, errorInfo); } // async call public static void reportError(Handler handler, final Context context, final List el, - final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) { + final Class returnActivity, final View rootView, final ErrorInfo errorInfo) { handler.post(new Runnable() { @Override 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); errorList = intent.getStringArrayExtra(ERROR_LIST); - //importand add gurumeditaion + // important add guru meditation addGuruMeditaion(); currentTimeStamp = getCurrentTimeStamp(); @@ -250,7 +235,7 @@ public class ErrorActivity extends AppCompatActivity { }); reportButton.setEnabled(false); - globIpRangeThread = new Thread(new IpRagneRequester()); + globIpRangeThread = new Thread(new IpRangeRequester()); globIpRangeThread.start(); // normal bugreport @@ -308,17 +293,30 @@ public class ErrorActivity extends AppCompatActivity { return text; } + /** + * Get the checked activity. + * @param returnActivity the activity to return to + * @return the casted return activity or null + */ + @Nullable + static Class getReturnActivity(Class returnActivity) { + Class checkedReturnActivity = null; + if (returnActivity != null){ + if (Activity.class.isAssignableFrom(returnActivity)) { + checkedReturnActivity = returnActivity.asSubclass(Activity.class); + } else { + checkedReturnActivity = MainActivity.class; + } + } + return checkedReturnActivity; + } + private void goToReturnActivity() { - if (returnActivity == null) { + Class checkedReturnActivity = getReturnActivity(returnActivity); + if (checkedReturnActivity == null) { super.onBackPressed(); } else { - Intent intent; - if (returnActivity != null && - returnActivity.isAssignableFrom(Activity.class)) { - intent = new Intent(this, returnActivity); - } else { - intent = new Intent(this, MainActivity.class); - } + Intent intent = new Intent(this, checkedReturnActivity); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); NavUtils.navigateUpTo(this, intent); } @@ -376,26 +374,11 @@ public class ErrorActivity extends AppCompatActivity { return ""; } - private String getUserActionString(int userAction) { - switch (userAction) { - case REQUESTED_STREAM: - return REQUESTED_STREAM_STRING; - case SEARCHED: - 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."; + private String getUserActionString(UserAction userAction) { + if(userAction == null) { + return "Your description is in another castle."; + } else { + return userAction.getMessage(); } } @@ -444,28 +427,28 @@ public class ErrorActivity extends AppCompatActivity { return new ErrorInfo[size]; } }; - public int userAction; - public String request; - public String serviceName; - public int message; + final public UserAction userAction; + final public String request; + final public String serviceName; + @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) { - this.userAction = in.readInt(); + this.userAction = UserAction.valueOf(in.readString()); this.request = in.readString(); this.serviceName = in.readString(); this.message = in.readInt(); } - public static ErrorInfo make(int userAction, String serviceName, String request, int message) { - ErrorInfo info = new ErrorInfo(); - info.userAction = userAction; - info.serviceName = serviceName; - info.request = request; - info.message = message; - return info; + public static ErrorInfo make(UserAction userAction, String serviceName, String request, @StringRes int message) { + return new ErrorInfo(userAction, serviceName, request, message); } @Override @@ -475,14 +458,14 @@ public class ErrorActivity extends AppCompatActivity { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(this.userAction); + dest.writeString(this.userAction.name()); dest.writeString(this.request); dest.writeString(this.serviceName); dest.writeInt(this.message); } } - private class IpRagneRequester implements Runnable { + private class IpRangeRequester implements Runnable { Handler h = new Handler(); public void run() { String ipRange = "none"; @@ -493,17 +476,16 @@ public class ErrorActivity extends AppCompatActivity { ipRange = Parser.matchGroup1("([0-9]*\\.[0-9]*\\.)[0-9]*\\.[0-9]*", ip) + "0.0"; } catch(Throwable e) { - Log.d(TAG, "Error while error: could not get iprange"); - e.printStackTrace(); + Log.w(TAG, "Error while error: could not get iprange", e); } finally { - h.post(new IpRageReturnRunnable(ipRange)); + h.post(new IpRangeReturnRunnable(ipRange)); } } } - private class IpRageReturnRunnable implements Runnable { + private class IpRangeReturnRunnable implements Runnable { String ipRange; - public IpRageReturnRunnable(String ipRange) { + public IpRangeReturnRunnable(String ipRange) { this.ipRange = ipRange; } public void run() { diff --git a/app/src/main/java/org/schabi/newpipe/report/UserAction.java b/app/src/main/java/org/schabi/newpipe/report/UserAction.java new file mode 100644 index 000000000..48dcb5752 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/report/UserAction.java @@ -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; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java index e4d6b6ea6..105d65a34 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java +++ b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java @@ -72,9 +72,7 @@ public class NewPipeSettings { public static String getVideoDownloadPath(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); final String key = context.getString(R.string.download_path_key); - String downloadPath = prefs.getString(key, Environment.DIRECTORY_MOVIES); - - return downloadPath; + return prefs.getString(key, Environment.DIRECTORY_MOVIES); } public static File getAudioDownloadFolder(Context context) { @@ -84,9 +82,7 @@ public class NewPipeSettings { public static String getAudioDownloadPath(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); final String key = context.getString(R.string.download_path_audio_key); - String downloadPath = prefs.getString(key, Environment.DIRECTORY_MUSIC); - - return downloadPath; + return prefs.getString(key, Environment.DIRECTORY_MUSIC); } private static File getFolder(Context context, int keyID, String defaultDirectoryName) { diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index 56a44cf55..8c47879a3 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -77,14 +77,14 @@ public class NavigationHelper { public static void openMainFragment(FragmentManager fragmentManager) { ImageLoader.getInstance().clearMemoryCache(); 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()) .commit(); } public static void openSearchFragment(FragmentManager fragmentManager, int serviceId, String query) { 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)) .addToBackStack(null) .commit(); @@ -109,7 +109,7 @@ public class NavigationHelper { instance.setAutoplay(autoPlay); 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) .addToBackStack(null) .commit(); @@ -118,7 +118,7 @@ public class NavigationHelper { public static void openChannelFragment(FragmentManager fragmentManager, int serviceId, String url, String name) { if (name == null) name = ""; 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)) .addToBackStack(null) .commit(); diff --git a/app/src/main/java/org/schabi/newpipe/workers/ChannelExtractorWorker.java b/app/src/main/java/org/schabi/newpipe/workers/ChannelExtractorWorker.java index bd4cf1139..dec2f3fcd 100644 --- a/app/src/main/java/org/schabi/newpipe/workers/ChannelExtractorWorker.java +++ b/app/src/main/java/org/schabi/newpipe/workers/ChannelExtractorWorker.java @@ -12,6 +12,8 @@ import org.schabi.newpipe.report.ErrorActivity; 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 * @@ -67,7 +69,7 @@ public class ChannelExtractorWorker extends ExtractorWorker { ChannelExtractor extractor = getService().getChannelExtractorInstance(url, pageNumber); 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() { @Override @@ -93,7 +95,7 @@ public class ChannelExtractorWorker extends ExtractorWorker { } }); } 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() { @Override public void run() { @@ -101,7 +103,7 @@ public class ChannelExtractorWorker extends ExtractorWorker { } }); } 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() { @Override public void run() { diff --git a/app/src/main/java/org/schabi/newpipe/workers/ExtractorWorker.java b/app/src/main/java/org/schabi/newpipe/workers/ExtractorWorker.java index 57872b613..40ce249a4 100644 --- a/app/src/main/java/org/schabi/newpipe/workers/ExtractorWorker.java +++ b/app/src/main/java/org/schabi/newpipe/workers/ExtractorWorker.java @@ -6,6 +6,7 @@ import android.util.Log; import android.view.View; import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.report.UserAction; 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. * (One of the {@link ErrorActivity}.REQUEST_* error (message) ids) */ - protected void handleErrorsDuringExtraction(List errorsList, int errorUserAction){ + protected void handleErrorsDuringExtraction(List errorsList, UserAction errorUserAction){ String errorString = ""; switch (errorUserAction) { - case ErrorActivity.REQUESTED_STREAM: - errorString= ErrorActivity.REQUESTED_STREAM_STRING; + case REQUESTED_STREAM: + errorString= errorUserAction.getMessage(); break; - case ErrorActivity.REQUESTED_CHANNEL: - errorString= ErrorActivity.REQUESTED_CHANNEL_STRING; + case REQUESTED_CHANNEL: + errorString= errorUserAction.getMessage(); break; } diff --git a/app/src/main/java/org/schabi/newpipe/workers/SearchWorker.java b/app/src/main/java/org/schabi/newpipe/workers/SearchWorker.java index 7d1e28441..dc62698ea 100644 --- a/app/src/main/java/org/schabi/newpipe/workers/SearchWorker.java +++ b/app/src/main/java/org/schabi/newpipe/workers/SearchWorker.java @@ -13,10 +13,13 @@ import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; import org.schabi.newpipe.extractor.search.SearchEngine; import org.schabi.newpipe.extractor.search.SearchResult; import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.report.UserAction; import java.io.IOException; import java.util.EnumSet; +import static org.schabi.newpipe.report.UserAction.*; + /** * Return list of results based on a query * @@ -106,7 +109,7 @@ public class SearchWorker extends AbstractWorker { }); } else if (exception instanceof ExtractionException) { 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() { @Override public void run() { @@ -115,7 +118,7 @@ public class SearchWorker extends AbstractWorker { }); } else { 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() { @Override public void run() { diff --git a/app/src/main/java/org/schabi/newpipe/workers/StreamExtractorWorker.java b/app/src/main/java/org/schabi/newpipe/workers/StreamExtractorWorker.java index 7ff04adf2..af8e156b0 100644 --- a/app/src/main/java/org/schabi/newpipe/workers/StreamExtractorWorker.java +++ b/app/src/main/java/org/schabi/newpipe/workers/StreamExtractorWorker.java @@ -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.StreamInfo; import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.report.UserAction; 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 * @@ -66,7 +69,7 @@ public class StreamExtractorWorker extends ExtractorWorker { StreamExtractor streamExtractor = getService().getExtractorInstance(url); 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() { @Override @@ -121,7 +124,7 @@ public class StreamExtractorWorker extends ExtractorWorker { }); } else if (exception instanceof YoutubeStreamExtractor.DecryptException) { // 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() { @Override public void run() { @@ -131,9 +134,9 @@ public class StreamExtractorWorker extends ExtractorWorker { } else if (exception instanceof StreamInfo.StreamExctractException) { if (!streamInfo.errors.isEmpty()) { // !!! 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 { - 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() { @@ -143,7 +146,7 @@ public class StreamExtractorWorker extends ExtractorWorker { } }); } 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() { @Override public void run() { @@ -151,7 +154,7 @@ public class StreamExtractorWorker extends ExtractorWorker { } }); } 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() { @Override public void run() { diff --git a/app/src/main/java/org/schabi/newpipe/workers/SuggestionWorker.java b/app/src/main/java/org/schabi/newpipe/workers/SuggestionWorker.java index 6bad06fce..66caad26c 100644 --- a/app/src/main/java/org/schabi/newpipe/workers/SuggestionWorker.java +++ b/app/src/main/java/org/schabi/newpipe/workers/SuggestionWorker.java @@ -11,10 +11,13 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.SuggestionExtractor; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.report.UserAction; import java.io.IOException; import java.util.List; +import static org.schabi.newpipe.report.UserAction.*; + /** * Worker that get suggestions based on the query * @@ -79,7 +82,7 @@ public class SuggestionWorker extends AbstractWorker { if (exception instanceof ExtractionException) { 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() { @Override public void run() { @@ -95,7 +98,7 @@ public class SuggestionWorker extends AbstractWorker { }); } else { 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() { @Override public void run() { diff --git a/app/src/main/java/us/shandian/giga/get/DownloadDataSource.java b/app/src/main/java/us/shandian/giga/get/DownloadDataSource.java index 87f550cc4..2a8a9e129 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadDataSource.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadDataSource.java @@ -9,12 +9,14 @@ public interface DownloadDataSource { /** * Load all missions + * * @return a list of download missions */ List loadMissions(); /** - * Add a downlaod mission to the storage + * Add a download mission to the storage + * * @param downloadMission the download mission to add * @return the identifier of the mission */ @@ -22,6 +24,7 @@ public interface DownloadDataSource { /** * Update a download mission which exists in the storage + * * @param downloadMission the download mission to update * @throws IllegalArgumentException if the mission was not added to storage */ @@ -30,6 +33,7 @@ public interface DownloadDataSource { /** * Delete a download mission + * * @param downloadMission the mission to delete */ void deleteMission(DownloadMission downloadMission); diff --git a/app/src/main/java/us/shandian/giga/get/DownloadManager.java b/app/src/main/java/us/shandian/giga/get/DownloadManager.java index b6579c86d..45beb5563 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadManager.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadManager.java @@ -1,48 +1,53 @@ package us.shandian.giga.get; -public interface DownloadManager -{ - int BLOCK_SIZE = 512 * 1024; +public interface DownloadManager { + int BLOCK_SIZE = 512 * 1024; - /** - * Start a new download mission - * @param url the url to download - * @param location the location - * @param name the name of the file to create - * @param isAudio true if the download is an audio file - * @param threads the number of threads maximal used to download chunks of the file. @return the identifier of the mission. + /** + * Start a new download mission + * + * @param url the url to download + * @param location the location + * @param name the name of the file to create + * @param isAudio true if the download is an audio file + * @param threads the number of threads maximal used to download chunks of the file. @return the identifier of the mission. */ - int startMission(String url, String location, String name, boolean isAudio, int threads); + int startMission(String url, String location, String name, boolean isAudio, int threads); - /** - * Resume the execution of a download mission. - * @param id the identifier of the mission to resume. - */ - void resumeMission(int id); - - /** - * Pause the execution of a download mission. - * @param id the identifier of the mission to pause. + /** + * Resume the execution of a download mission. + * + * @param id the identifier of the mission to resume. */ - void pauseMission(int id); + void resumeMission(int id); - /** - * Deletes the mission from the downloaded list but keeps the downloaded file. - * @param id The mission identifier + /** + * Pause the execution of a download mission. + * + * @param id the identifier of the mission to pause. */ - void deleteMission(int id); + void pauseMission(int id); - /** - * Get the download mission by its identifier - * @param id the identifier of the download mission - * @return the download mission or null if the mission doesn't exist + /** + * Deletes the mission from the downloaded list but keeps the downloaded file. + * + * @param id The mission identifier */ - DownloadMission getMission(int id); + void deleteMission(int id); - /** - * Get the number of download missions. - * @return the number of download missions. + /** + * Get the download mission by its identifier + * + * @param id the identifier of the download mission + * @return the download mission or null if the mission doesn't exist */ - int getCount(); + DownloadMission getMission(int id); + + /** + * Get the number of download missions. + * + * @return the number of download missions. + */ + int getCount(); } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java b/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java index 3c37ac7d4..479a5cee3 100755 --- a/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java @@ -18,95 +18,96 @@ import java.util.Comparator; import java.util.List; import us.shandian.giga.util.Utility; + import static org.schabi.newpipe.BuildConfig.DEBUG; -public class DownloadManagerImpl implements DownloadManager -{ - private static final String TAG = DownloadManagerImpl.class.getSimpleName(); - private final DownloadDataSource mDownloadDataSource; +public class DownloadManagerImpl implements DownloadManager { + private static final String TAG = DownloadManagerImpl.class.getSimpleName(); + private final DownloadDataSource mDownloadDataSource; - private final ArrayList mMissions = new ArrayList(); + private final ArrayList mMissions = new ArrayList(); /** * 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 */ - public DownloadManagerImpl(Collection searchLocations, DownloadDataSource downloadDataSource) { - mDownloadDataSource = downloadDataSource; - loadMissions(searchLocations); - } + public DownloadManagerImpl(Collection searchLocations, DownloadDataSource downloadDataSource) { + mDownloadDataSource = downloadDataSource; + loadMissions(searchLocations); + } - @Override - public int startMission(String url, String location, String name, boolean isAudio, int threads) { - DownloadMission existingMission = getMissionByLocation(location, name); - if(existingMission != null) { - // Already downloaded or downloading - if(existingMission.finished) { - // Overwrite mission + @Override + public int startMission(String url, String location, String name, boolean isAudio, int threads) { + DownloadMission existingMission = getMissionByLocation(location, name); + if (existingMission != null) { + // Already downloaded or downloading + if (existingMission.finished) { + // Overwrite mission deleteMission(mMissions.indexOf(existingMission)); - } else { - // Rename file (?) + } else { + // Rename file (?) try { name = generateUniqueName(location, name); - }catch (Exception e) { + } catch (Exception e) { Log.e(TAG, "Unable to generate unique name", e); - name = System.currentTimeMillis() + name ; + name = System.currentTimeMillis() + name; Log.i(TAG, "Using " + name); } - } - } + } + } - DownloadMission mission = new DownloadMission(name, url, location); - mission.timestamp = System.currentTimeMillis(); - mission.threadCount = threads; - mission.addListener(new MissionListener(mission)); - new Initializer(mission).start(); - return insertMission(mission); - } + DownloadMission mission = new DownloadMission(name, url, location); + mission.timestamp = System.currentTimeMillis(); + mission.threadCount = threads; + mission.addListener(new MissionListener(mission)); + new Initializer(mission).start(); + return insertMission(mission); + } - @Override - public void resumeMission(int i) { - DownloadMission d = getMission(i); - if (!d.running && d.errCode == -1) { - d.start(); - } - } + @Override + public void resumeMission(int i) { + DownloadMission d = getMission(i); + if (!d.running && d.errCode == -1) { + d.start(); + } + } - @Override - public void pauseMission(int i) { - DownloadMission d = getMission(i); - if (d.running) { - d.pause(); - } - } + @Override + public void pauseMission(int i) { + DownloadMission d = getMission(i); + if (d.running) { + d.pause(); + } + } - @Override - public void deleteMission(int i) { - DownloadMission mission = getMission(i); - if(mission.finished) { - mDownloadDataSource.deleteMission(mission); - } - mission.delete(); - mMissions.remove(i); - } + @Override + public void deleteMission(int i) { + DownloadMission mission = getMission(i); + if (mission.finished) { + mDownloadDataSource.deleteMission(mission); + } + mission.delete(); + mMissions.remove(i); + } - private void loadMissions(Iterable searchLocations) { - mMissions.clear(); - loadFinishedMissions(); - for(String location: searchLocations) { - loadMissions(location); - } + private void loadMissions(Iterable searchLocations) { + mMissions.clear(); + loadFinishedMissions(); + for (String location : searchLocations) { + loadMissions(location); + } - } + } - /** - * Loads finished missions from the data source - */ - private void loadFinishedMissions() { - List finishedMissions = mDownloadDataSource.loadMissions(); - if(finishedMissions == null) { + /** + * Loads finished missions from the data source + */ + private void loadFinishedMissions() { + List finishedMissions = mDownloadDataSource.loadMissions(); + if (finishedMissions == null) { finishedMissions = new ArrayList<>(); } // Ensure its sorted @@ -117,251 +118,255 @@ public class DownloadManagerImpl implements DownloadManager } }); mMissions.ensureCapacity(mMissions.size() + finishedMissions.size()); - for(DownloadMission mission: finishedMissions) { - File downloadedFile = mission.getDownloadedFile(); - if(!downloadedFile.isFile()) { - if(DEBUG) { - Log.d(TAG, "downloaded file removed: " + downloadedFile.getAbsolutePath()); - } - mDownloadDataSource.deleteMission(mission); - } else { - mission.length = downloadedFile.length(); - mission.finished = true; - mission.running = false; - mMissions.add(mission); - } - } - } + for (DownloadMission mission : finishedMissions) { + File downloadedFile = mission.getDownloadedFile(); + if (!downloadedFile.isFile()) { + if (DEBUG) { + Log.d(TAG, "downloaded file removed: " + downloadedFile.getAbsolutePath()); + } + mDownloadDataSource.deleteMission(mission); + } else { + mission.length = downloadedFile.length(); + mission.finished = true; + mission.running = false; + mMissions.add(mission); + } + } + } - private void loadMissions(String location) { + private void loadMissions(String location) { - File f = new File(location); + File f = new File(location); - if (f.exists() && f.isDirectory()) { - File[] subs = f.listFiles(); + if (f.exists() && f.isDirectory()) { + File[] subs = f.listFiles(); - if(subs == null) { + if (subs == null) { Log.e(TAG, "listFiles() returned null"); return; } - for (File sub : subs) { - if (sub.isFile() && sub.getName().endsWith(".giga")) { - String str = Utility.readFromFile(sub.getAbsolutePath()); - if (str != null && !str.trim().equals("")) { + for (File sub : subs) { + if (sub.isFile() && sub.getName().endsWith(".giga")) { + String str = Utility.readFromFile(sub.getAbsolutePath()); + if (str != null && !str.trim().equals("")) { - if (DEBUG) { - Log.d(TAG, "loading mission " + sub.getName()); - Log.d(TAG, str); - } + if (DEBUG) { + Log.d(TAG, "loading mission " + sub.getName()); + Log.d(TAG, str); + } - DownloadMission mis = new Gson().fromJson(str, DownloadMission.class); + DownloadMission mis = new Gson().fromJson(str, DownloadMission.class); - if (mis.finished) { - if(!sub.delete()) { + if (mis.finished) { + if (!sub.delete()) { Log.w(TAG, "Unable to delete .giga file: " + sub.getPath()); } - continue; - } + continue; + } - mis.running = false; - mis.recovered = true; - insertMission(mis); - } - } - } - } - } - - @Override - public DownloadMission getMission(int i) { - return mMissions.get(i); - } - - @Override - public int getCount() { - return mMissions.size(); - } - - private int insertMission(DownloadMission mission) { - int i = -1; - - DownloadMission m = null; - - if (mMissions.size() > 0) { - do { - m = mMissions.get(++i); - } while (m.timestamp > mission.timestamp && i < mMissions.size() - 1); - - //if (i > 0) i--; - } else { - i = 0; - } - - mMissions.add(i, mission); - - return i; - } + mis.running = false; + mis.recovered = true; + insertMission(mis); + } + } + } + } + } + + @Override + public DownloadMission getMission(int i) { + return mMissions.get(i); + } + + @Override + public int getCount() { + return mMissions.size(); + } + + private int insertMission(DownloadMission mission) { + int i = -1; + + DownloadMission m = null; + + if (mMissions.size() > 0) { + do { + m = mMissions.get(++i); + } while (m.timestamp > mission.timestamp && i < mMissions.size() - 1); + + //if (i > 0) i--; + } else { + i = 0; + } + + mMissions.add(i, mission); + + return i; + } /** - * Get a mission by its location and name - * @param location the location - * @param name the name - * @return the mission or null if no such mission exists - */ - private @Nullable DownloadMission getMissionByLocation(String location, String name) { - for(DownloadMission mission: mMissions) { - if(location.equals(mission.location) && name.equals(mission.name)) { - return mission; - } - } - return null; - } - - /** - * Splits the filename into name and extension - * - * Dots are ignored if they appear: not at all, at the beginning of the file, - * at the end of the file - * - * @param name the name to split - * @return a string array with a length of 2 containing the name and the extension + * Get a mission by its location and name + * + * @param location the location + * @param name the name + * @return the mission or null if no such mission exists */ - private 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)}; - } - } + 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; + } - /** - * Generates a unique file name. - * - * e.g. "myname (1).txt" if the name "myname.txt" exists. - * @param location the location (to check for existing files) - * @param name the name of the file + /** + * Splits the filename into name and extension + *

+ * Dots are ignored if they appear: not at all, at the beginning of the file, + * at the end of the file + * + * @param name the name to split + * @return a string array with a length of 2 containing the name and the extension + */ + private static String[] splitName(String name) { + int dotIndex = name.lastIndexOf('.'); + if (dotIndex <= 0 || (dotIndex == name.length() - 1)) { + return new String[]{name, ""}; + } else { + return new String[]{name.substring(0, dotIndex), name.substring(dotIndex + 1)}; + } + } + + /** + * Generates a unique file name. + *

+ * e.g. "myname (1).txt" if the name "myname.txt" exists. + * + * @param location the location (to check for existing files) + * @param name the name of the file * @return the unique file name - * @throws IllegalArgumentException if the location is not a directory - * @throws SecurityException if the location is not readable + * @throws IllegalArgumentException if the location is not a directory + * @throws SecurityException if the location is not readable */ - private static String generateUniqueName(String location, String name) { - if(location == null) throw new NullPointerException("location is null"); - if(name == null) throw new NullPointerException("name is null"); - File destination = new File(location); - if(!destination.isDirectory()) { - throw new IllegalArgumentException("location is not a directory: " + location); - } - final String[] nameParts = splitName(name); - String[] existingName = destination.list(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.startsWith(nameParts[0]); - } - }); - Arrays.sort(existingName); - String newName; - int downloadIndex = 0; - do { - newName = nameParts[0] + " (" + downloadIndex + ")." + nameParts[1]; - ++downloadIndex; - if(downloadIndex == 1000) { // Probably an error on our side - throw new RuntimeException("Too many existing files"); - } - } while (Arrays.binarySearch(existingName, newName) >= 0); - return newName; - } + private static String generateUniqueName(String location, String name) { + if (location == null) throw new NullPointerException("location is null"); + if (name == null) throw new NullPointerException("name is null"); + File destination = new File(location); + if (!destination.isDirectory()) { + throw new IllegalArgumentException("location is not a directory: " + location); + } + final String[] nameParts = splitName(name); + String[] existingName = destination.list(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.startsWith(nameParts[0]); + } + }); + Arrays.sort(existingName); + String newName; + int downloadIndex = 0; + do { + newName = nameParts[0] + " (" + downloadIndex + ")." + nameParts[1]; + ++downloadIndex; + if (downloadIndex == 1000) { // Probably an error on our side + throw new RuntimeException("Too many existing files"); + } + } while (Arrays.binarySearch(existingName, newName) >= 0); + return newName; + } - private class Initializer extends Thread { - private 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++; - } - + private class Initializer extends Thread { + private DownloadMission mission; - 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); - } - } - } + public Initializer(DownloadMission mission) { + this.mission = mission; + } - /** - * Waits for mission to finish to add it to the {@link #mDownloadDataSource} - */ - private class MissionListener implements DownloadMission.MissionListener { - private final DownloadMission mMission; + @Override + public void run() { + try { + URL url = new URL(mission.url); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + mission.length = conn.getContentLength(); - private MissionListener(DownloadMission mission) { - if(mission == null) throw new NullPointerException("mission is null"); - // Could the mission be passed in onFinish()? - mMission = mission; - } + if (mission.length <= 0) { + mission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED; + //mission.notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED); + return; + } - @Override - public void onProgressUpdate(DownloadMission downloadMission, long done, long total) { - } + // Open again + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestProperty("Range", "bytes=" + (mission.length - 10) + "-" + mission.length); - @Override - public void onFinish(DownloadMission downloadMission) { - mDownloadDataSource.addMission(mMission); - } + if (conn.getResponseCode() != 206) { + // Fallback to single thread if no partial content support + mission.fallback = true; - @Override - public void onError(DownloadMission downloadMission, int errCode) { - } - } + if (DEBUG) { + Log.d(TAG, "falling back"); + } + } + + if (DEBUG) { + Log.d(TAG, "response = " + conn.getResponseCode()); + } + + mission.blocks = mission.length / BLOCK_SIZE; + + if (mission.threadCount > mission.blocks) { + mission.threadCount = (int) mission.blocks; + } + + if (mission.threadCount <= 0) { + mission.threadCount = 1; + } + + if (mission.blocks * BLOCK_SIZE < mission.length) { + mission.blocks++; + } + + + new File(mission.location).mkdirs(); + new File(mission.location + "/" + mission.name).createNewFile(); + RandomAccessFile af = new RandomAccessFile(mission.location + "/" + mission.name, "rw"); + af.setLength(mission.length); + af.close(); + + mission.start(); + } catch (Exception e) { + // TODO Notify + throw new RuntimeException(e); + } + } + } + + /** + * 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) { + } + } } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMission.java b/app/src/main/java/us/shandian/giga/get/DownloadMission.java index 54e84c5ab..9938d2d86 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMission.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMission.java @@ -18,311 +18,315 @@ import us.shandian.giga.util.Utility; import static org.schabi.newpipe.BuildConfig.DEBUG; -public class DownloadMission -{ - private static final String TAG = DownloadMission.class.getSimpleName(); +public class DownloadMission { + private static final String TAG = DownloadMission.class.getSimpleName(); - public interface MissionListener { - HashMap 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; + public interface MissionListener { + HashMap handlerStore = new HashMap<>(); - /** - * The filename - */ - public String name; + void onProgressUpdate(DownloadMission downloadMission, long done, long total); - /** - * The url of the file to download - */ - public String url; + void onFinish(DownloadMission downloadMission); - /** - * The directory to store the download - */ - public String location; + void onError(DownloadMission downloadMission, int errCode); + } - /** - * Number of blocks the size of {@link DownloadManager#BLOCK_SIZE} - */ - public long blocks; + public static final int ERROR_SERVER_UNSUPPORTED = 206; + public static final int ERROR_UNKNOWN = 233; - /** - * Number of bytes - */ - public long length; - - /** - * Number of bytes downloaded - */ - public long done; - public int threadCount = 3; - public int finishCount; - private List threadPositions = new ArrayList(); - public final Map blockState = new HashMap(); - public boolean running; - public boolean finished; - public boolean fallback; - public int errCode = -1; - public long timestamp; - - public transient boolean recovered; - - private transient ArrayList> mListeners = new ArrayList>(); - 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 + /** + * The filename */ - public boolean isBlockPreserved(long block) { - checkBlock(block); - return blockState.containsKey(block) ? blockState.get(block) : false; - } - - public void preserveBlock(long block) { - checkBlock(block); - synchronized (blockState) { - blockState.put(block, true); - } - } + public String name; - /** - * Set the download position of the file - * @param threadId the identifier of the thread - * @param position the download position of the thread + /** + * The url of the file to download */ - public void setPosition(int threadId, long position) { - threadPositions.set(threadId, position); - } + public String url; - /** - * Get the position of a thread - * @param threadId the identifier of the thread - * @return the position for the thread + /** + * The directory to store the download */ - 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 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); - } - }); - } - } - } + public String location; - /** - * 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 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 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(listener)); - } - - public synchronized void removeListener(MissionListener listener) { - for (Iterator> iterator = mListeners.iterator(); - iterator.hasNext(); ) { - WeakReference 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 + /** + * Number of blocks the size of {@link DownloadManager#BLOCK_SIZE} */ - private String getMetaFilename() { - return location + "/" + name + ".giga"; - } + public long blocks; - 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 threadPositions = new ArrayList(); + public final Map blockState = new HashMap(); + public boolean running; + public boolean finished; + public boolean fallback; + public int errCode = -1; + public long timestamp; + + public transient boolean recovered; + + private transient ArrayList> mListeners = new ArrayList>(); + 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 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 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 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(listener)); + } + + public synchronized void removeListener(MissionListener listener) { + for (Iterator> iterator = mListeners.iterator(); + iterator.hasNext(); ) { + WeakReference 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); + } } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java index 1df5e716f..37dc64a2c 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java @@ -13,166 +13,165 @@ import static org.schabi.newpipe.BuildConfig.DEBUG; * Runnable to download blocks of a file until the file is completely downloaded, * an error occurs or the process is stopped. */ -public class DownloadRunnable implements Runnable -{ - private static final String TAG = DownloadRunnable.class.getSimpleName(); - - private final DownloadMission mMission; - private final int mId; - - public DownloadRunnable(DownloadMission mission, int id) { - if(mission == null) throw new NullPointerException("mission is null"); - mMission = mission; - mId = id; - } - - @Override - public void run() { - boolean retry = mMission.recovered; - long position = mMission.getPosition(mId); - - if (DEBUG) { - Log.d(TAG, mId + ":default pos " + position); - Log.d(TAG, mId + ":recovered: " + mMission.recovered); - } - - while (mMission.errCode == -1 && mMission.running && position < mMission.blocks) { - - if (Thread.currentThread().isInterrupted()) { - mMission.pause(); - return; - } - - if (DEBUG && retry) { - Log.d(TAG, mId + ":retry is true. Resuming at " + position); - } - - // Wait for an unblocked position - while (!retry && position < mMission.blocks && mMission.isBlockPreserved(position)) { - - if (DEBUG) { - Log.d(TAG, mId + ":position " + position + " preserved, passing"); - } - - position++; - } - - retry = false; - - if (position >= mMission.blocks) { - break; - } - - if (DEBUG) { - Log.d(TAG, mId + ":preserving position " + position); - } - - mMission.preserveBlock(position); - mMission.setPosition(mId, position); - - long start = position * DownloadManager.BLOCK_SIZE; - long end = start + DownloadManager.BLOCK_SIZE - 1; - - if (end >= mMission.length) { - end = mMission.length - 1; - } - - HttpURLConnection conn = null; - - int total = 0; - - try { - URL url = new URL(mMission.url); - conn = (HttpURLConnection) url.openConnection(); - conn.setRequestProperty("Range", "bytes=" + start + "-" + end); - - if (DEBUG) { - Log.d(TAG, mId + ":" + conn.getRequestProperty("Range")); - Log.d(TAG, mId + ":Content-Length=" + conn.getContentLength() + " Code:" + conn.getResponseCode()); - } - - // A server may be ignoring the range request - if (conn.getResponseCode() != 206) { - mMission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED; - notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED); - - if (DEBUG) { - Log.e(TAG, mId + ":Unsupported " + conn.getResponseCode()); - } - - break; - } - - RandomAccessFile f = new RandomAccessFile(mMission.location + "/" + mMission.name, "rw"); - f.seek(start); - BufferedInputStream ipt = new BufferedInputStream(conn.getInputStream()); - byte[] buf = new byte[512]; - - while (start < end && mMission.running) { - int len = ipt.read(buf, 0, 512); - - if (len == -1) { - break; - } else { - start += len; - total += len; - f.write(buf, 0, len); - notifyProgress(len); - } - } - - if (DEBUG && mMission.running) { - Log.d(TAG, mId + ":position " + position + " finished, total length " + total); - } - - f.close(); - ipt.close(); - - // TODO We should save progress for each thread - } catch (Exception e) { - // TODO Retry count limit & notify error - retry = true; - - notifyProgress(-total); - - if (DEBUG) { - Log.d(TAG, mId + ":position " + position + " retrying", e); - } - } - } - - if (DEBUG) { - Log.d(TAG, "thread " + mId + " exited main loop"); - } - - if (mMission.errCode == -1 && mMission.running) { - if (DEBUG) { - Log.d(TAG, "no error has happened, notifying"); - } - notifyFinished(); - } - - if (DEBUG && !mMission.running) { - Log.d(TAG, "The mission has been paused. Passing."); - } - } - - private void notifyProgress(final long len) { - synchronized (mMission) { - mMission.notifyProgress(len); - } - } - - private void notifyError(final int err) { - synchronized (mMission) { - mMission.notifyError(err); - mMission.pause(); - } - } - - private void notifyFinished() { - synchronized (mMission) { - mMission.notifyFinished(); - } - } +public class DownloadRunnable implements Runnable { + private static final String TAG = DownloadRunnable.class.getSimpleName(); + + private final DownloadMission mMission; + private final int mId; + + public DownloadRunnable(DownloadMission mission, int id) { + if (mission == null) throw new NullPointerException("mission is null"); + mMission = mission; + mId = id; + } + + @Override + public void run() { + boolean retry = mMission.recovered; + long position = mMission.getPosition(mId); + + if (DEBUG) { + Log.d(TAG, mId + ":default pos " + position); + Log.d(TAG, mId + ":recovered: " + mMission.recovered); + } + + while (mMission.errCode == -1 && mMission.running && position < mMission.blocks) { + + if (Thread.currentThread().isInterrupted()) { + mMission.pause(); + return; + } + + if (DEBUG && retry) { + Log.d(TAG, mId + ":retry is true. Resuming at " + position); + } + + // Wait for an unblocked position + while (!retry && position < mMission.blocks && mMission.isBlockPreserved(position)) { + + if (DEBUG) { + Log.d(TAG, mId + ":position " + position + " preserved, passing"); + } + + position++; + } + + retry = false; + + if (position >= mMission.blocks) { + break; + } + + if (DEBUG) { + Log.d(TAG, mId + ":preserving position " + position); + } + + mMission.preserveBlock(position); + mMission.setPosition(mId, position); + + long start = position * DownloadManager.BLOCK_SIZE; + long end = start + DownloadManager.BLOCK_SIZE - 1; + + if (end >= mMission.length) { + end = mMission.length - 1; + } + + HttpURLConnection conn = null; + + int total = 0; + + try { + URL url = new URL(mMission.url); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestProperty("Range", "bytes=" + start + "-" + end); + + if (DEBUG) { + Log.d(TAG, mId + ":" + conn.getRequestProperty("Range")); + Log.d(TAG, mId + ":Content-Length=" + conn.getContentLength() + " Code:" + conn.getResponseCode()); + } + + // A server may be ignoring the range request + if (conn.getResponseCode() != 206) { + mMission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED; + notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED); + + if (DEBUG) { + Log.e(TAG, mId + ":Unsupported " + conn.getResponseCode()); + } + + break; + } + + RandomAccessFile f = new RandomAccessFile(mMission.location + "/" + mMission.name, "rw"); + f.seek(start); + BufferedInputStream ipt = new BufferedInputStream(conn.getInputStream()); + byte[] buf = new byte[512]; + + while (start < end && mMission.running) { + int len = ipt.read(buf, 0, 512); + + if (len == -1) { + break; + } else { + start += len; + total += len; + f.write(buf, 0, len); + notifyProgress(len); + } + } + + if (DEBUG && mMission.running) { + Log.d(TAG, mId + ":position " + position + " finished, total length " + total); + } + + f.close(); + ipt.close(); + + // TODO We should save progress for each thread + } catch (Exception e) { + // TODO Retry count limit & notify error + retry = true; + + notifyProgress(-total); + + if (DEBUG) { + Log.d(TAG, mId + ":position " + position + " retrying", e); + } + } + } + + if (DEBUG) { + Log.d(TAG, "thread " + mId + " exited main loop"); + } + + if (mMission.errCode == -1 && mMission.running) { + if (DEBUG) { + Log.d(TAG, "no error has happened, notifying"); + } + notifyFinished(); + } + + if (DEBUG && !mMission.running) { + Log.d(TAG, "The mission has been paused. Passing."); + } + } + + private void notifyProgress(final long len) { + synchronized (mMission) { + mMission.notifyProgress(len); + } + } + + private void notifyError(final int err) { + synchronized (mMission) { + mMission.notifyError(err); + mMission.pause(); + } + } + + private void notifyFinished() { + synchronized (mMission) { + mMission.notifyFinished(); + } + } } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java index e0a737024..f24139910 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java @@ -6,70 +6,69 @@ import java.net.HttpURLConnection; import java.net.URL; // Single-threaded fallback mode -public class DownloadRunnableFallback implements Runnable -{ - private final DownloadMission mMission; - //private int mId; - - public DownloadRunnableFallback(DownloadMission mission) { - if(mission == null) throw new NullPointerException("mission is null"); - //mId = id; - mMission = mission; - } +public class DownloadRunnableFallback implements Runnable { + private final DownloadMission mMission; + //private int mId; - @Override - public void run() { - try { - URL url = new URL(mMission.url); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - - if (conn.getResponseCode() != 200 && conn.getResponseCode() != 206) { - notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED); - } else { - RandomAccessFile f = new RandomAccessFile(mMission.location + "/" + mMission.name, "rw"); - f.seek(0); - BufferedInputStream ipt = new BufferedInputStream(conn.getInputStream()); - byte[] buf = new byte[512]; - int len = 0; - - while ((len = ipt.read(buf, 0, 512)) != -1 && mMission.running) { - f.write(buf, 0, len); - notifyProgress(len); - - if (Thread.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(); - } - } + public DownloadRunnableFallback(DownloadMission mission) { + if (mission == null) throw new NullPointerException("mission is null"); + //mId = id; + mMission = mission; + } - private void notifyFinished() { - synchronized (mMission) { - mMission.notifyFinished(); - } - } + @Override + public void run() { + try { + URL url = new URL(mMission.url); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + + if (conn.getResponseCode() != 200 && conn.getResponseCode() != 206) { + notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED); + } else { + RandomAccessFile f = new RandomAccessFile(mMission.location + "/" + mMission.name, "rw"); + f.seek(0); + BufferedInputStream ipt = new BufferedInputStream(conn.getInputStream()); + byte[] buf = new byte[512]; + int len = 0; + + while ((len = ipt.read(buf, 0, 512)) != -1 && mMission.running) { + f.write(buf, 0, len); + notifyProgress(len); + + if (Thread.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(); + } + } } diff --git a/app/src/main/java/us/shandian/giga/get/sqlite/DownloadMissionSQLiteHelper.java b/app/src/main/java/us/shandian/giga/get/sqlite/DownloadMissionSQLiteHelper.java index 6c29be474..d5a83551b 100644 --- a/app/src/main/java/us/shandian/giga/get/sqlite/DownloadMissionSQLiteHelper.java +++ b/app/src/main/java/us/shandian/giga/get/sqlite/DownloadMissionSQLiteHelper.java @@ -64,6 +64,7 @@ public class DownloadMissionSQLiteHelper extends SQLiteOpenHelper { /** * Returns all values of the download mission as ContentValues. + * * @param downloadMission the download mission * @return the content values */ @@ -88,7 +89,7 @@ public class DownloadMissionSQLiteHelper extends SQLiteOpenHelper { } 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; String name = cursor.getString(cursor.getColumnIndexOrThrow(KEY_NAME)); String location = cursor.getString(cursor.getColumnIndexOrThrow(KEY_LOCATION)); diff --git a/app/src/main/java/us/shandian/giga/get/sqlite/SQLiteDownloadDataSource.java b/app/src/main/java/us/shandian/giga/get/sqlite/SQLiteDownloadDataSource.java index 556e26a39..e7b4caeb8 100644 --- a/app/src/main/java/us/shandian/giga/get/sqlite/SQLiteDownloadDataSource.java +++ b/app/src/main/java/us/shandian/giga/get/sqlite/SQLiteDownloadDataSource.java @@ -37,7 +37,7 @@ public class SQLiteDownloadDataSource implements DownloadDataSource { null, null, null, DownloadMissionSQLiteHelper.KEY_TIMESTAMP); int count = cursor.getCount(); - if(count == 0) return new ArrayList<>(); + if (count == 0) return new ArrayList<>(); result = new ArrayList<>(count); while (cursor.moveToNext()) { result.add(DownloadMissionSQLiteHelper.getMissionFromCursor(cursor)); @@ -47,7 +47,7 @@ public class SQLiteDownloadDataSource implements DownloadDataSource { @Override 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(); ContentValues values = DownloadMissionSQLiteHelper.getValuesOfMission(downloadMission); database.insert(MISSIONS_TABLE_NAME, null, values); @@ -55,25 +55,25 @@ public class SQLiteDownloadDataSource implements DownloadDataSource { @Override 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(); ContentValues values = DownloadMissionSQLiteHelper.getValuesOfMission(downloadMission); - String whereClause = KEY_LOCATION+ " = ? AND " + + String whereClause = KEY_LOCATION + " = ? AND " + KEY_NAME + " = ?"; int rowsAffected = database.update(MISSIONS_TABLE_NAME, values, whereClause, new String[]{downloadMission.location, downloadMission.name}); - if(rowsAffected != 1) { + if (rowsAffected != 1) { Log.e(TAG, "Expected 1 row to be affected by update but got " + rowsAffected); } } @Override public void deleteMission(DownloadMission downloadMission) { - if(downloadMission == null) throw new NullPointerException("downloadMission is null"); + if (downloadMission == null) throw new NullPointerException("downloadMission is null"); SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase(); database.delete(MISSIONS_TABLE_NAME, KEY_LOCATION + " = ? AND " + - KEY_NAME + " = ?", + KEY_NAME + " = ?", new String[]{downloadMission.location, downloadMission.name}); } } diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java index d83ed6dc8..9727ffb69 100755 --- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java @@ -6,7 +6,6 @@ import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; -import android.content.ServiceConnection; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -16,6 +15,7 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Message; import android.support.v4.app.NotificationCompat.Builder; +import android.support.v4.content.ContextCompat; import android.support.v4.content.PermissionChecker; import android.util.Log; import android.widget.Toast; @@ -34,237 +34,235 @@ import us.shandian.giga.get.sqlite.SQLiteDownloadDataSource; 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}. - */ - private static final int UPDATE_MESSAGE = 0; - private static final int NOTIFICATION_ID = 1000; - private static final String EXTRA_NAME = "DownloadManagerService.extra.name"; - private static final String EXTRA_LOCATION = "DownloadManagerService.extra.location"; - private static final String EXTRA_IS_AUDIO = "DownloadManagerService.extra.is_audio"; - private static final String EXTRA_THREADS = "DownloadManagerService.extra.threads"; + /** + * Message code of update messages stored as {@link Message#what}. + */ + private static final int UPDATE_MESSAGE = 0; + private static final int NOTIFICATION_ID = 1000; + private static final String EXTRA_NAME = "DownloadManagerService.extra.name"; + private static final String EXTRA_LOCATION = "DownloadManagerService.extra.location"; + private static final String EXTRA_IS_AUDIO = "DownloadManagerService.extra.is_audio"; + private static final String EXTRA_THREADS = "DownloadManagerService.extra.threads"; - private DMBinder mBinder; - private DownloadManager mManager; - private Notification mNotification; - private Handler mHandler; - private long mLastTimeStamp = System.currentTimeMillis(); - private DownloadDataSource mDataSource; + private DMBinder mBinder; + private DownloadManager mManager; + private Notification mNotification; + private Handler mHandler; + private long mLastTimeStamp = System.currentTimeMillis(); + private DownloadDataSource mDataSource; - - private MissionListener missionListener = new MissionListener(); + private MissionListener missionListener = new MissionListener(); - private void notifyMediaScanner(DownloadMission mission) { - Uri uri = Uri.parse("file://" + mission.location + "/" + mission.name); - // notify media scanner on downloaded media file ... - sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri)); - } + private void notifyMediaScanner(DownloadMission mission) { + Uri uri = Uri.parse("file://" + mission.location + "/" + mission.name); + // notify media scanner on downloaded media file ... + sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri)); + } - @Override - public void onCreate() { - super.onCreate(); + @Override + public void onCreate() { + super.onCreate(); - if (DEBUG) { - Log.d(TAG, "onCreate"); - } + if (DEBUG) { + Log.d(TAG, "onCreate"); + } - mBinder = new DMBinder(); - if(mDataSource == null) { - mDataSource = new SQLiteDownloadDataSource(this); - } - if (mManager == null) { - ArrayList paths = new ArrayList<>(2); - paths.add(NewPipeSettings.getVideoDownloadPath(this)); - paths.add(NewPipeSettings.getAudioDownloadPath(this)); - mManager = new DownloadManagerImpl(paths, mDataSource); - if (DEBUG) { - Log.d(TAG, "mManager == null"); - Log.d(TAG, "Download directory: " + paths); - } - } + mBinder = new DMBinder(); + if (mDataSource == null) { + mDataSource = new SQLiteDownloadDataSource(this); + } + if (mManager == null) { + ArrayList paths = new ArrayList<>(2); + paths.add(NewPipeSettings.getVideoDownloadPath(this)); + paths.add(NewPipeSettings.getAudioDownloadPath(this)); + mManager = new DownloadManagerImpl(paths, mDataSource); + if (DEBUG) { + Log.d(TAG, "mManager == null"); + Log.d(TAG, "Download directory: " + paths); + } + } - Intent i = new Intent(); - i.setAction(Intent.ACTION_MAIN); - i.setClass(this, DownloadActivity.class); + Intent i = new Intent(); + i.setAction(Intent.ACTION_MAIN); + 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) - .setContentIntent(PendingIntent.getActivity(this, 0, i, 0)) - .setSmallIcon(android.R.drawable.stat_sys_download) - .setLargeIcon(((BitmapDrawable) icon).getBitmap()) - .setContentTitle(getString(R.string.msg_running)) - .setContentText(getString(R.string.msg_running_detail)); + Builder builder = new Builder(this) + .setContentIntent(PendingIntent.getActivity(this, 0, i, 0)) + .setSmallIcon(android.R.drawable.stat_sys_download) + .setLargeIcon(((BitmapDrawable) icon).getBitmap()) + .setContentTitle(getString(R.string.msg_running)) + .setContentText(getString(R.string.msg_running_detail)); - PendingIntent pendingIntent = - PendingIntent.getActivity( - this, - 0, - new Intent(this, DownloadActivity.class) - .setAction(DownloadActivity.INTENT_LIST), - PendingIntent.FLAG_UPDATE_CURRENT - ); + PendingIntent pendingIntent = + PendingIntent.getActivity( + this, + 0, + new Intent(this, DownloadActivity.class) + .setAction(DownloadActivity.INTENT_LIST), + PendingIntent.FLAG_UPDATE_CURRENT + ); - builder.setContentIntent(pendingIntent); + builder.setContentIntent(pendingIntent); - mNotification = builder.build(); + mNotification = builder.build(); - HandlerThread thread = new HandlerThread("ServiceMessenger"); - thread.start(); - - mHandler = new Handler(thread.getLooper()) { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case UPDATE_MESSAGE: { - int runningCount = 0; + HandlerThread thread = new HandlerThread("ServiceMessenger"); + thread.start(); - for (int i = 0; i < mManager.getCount(); i++) { - if (mManager.getMission(i).running) { - runningCount++; - } - } - updateState(runningCount); - break; - } - } - } - }; - - } + mHandler = new Handler(thread.getLooper()) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case UPDATE_MESSAGE: { + int runningCount = 0; - private void startMissionAsync(final String url, final String location, final String name, - final boolean isAudio, final int threads) { - mHandler.post(new Runnable() { - @Override - public void run() { - int missionId = mManager.startMission(url, location, name, isAudio, threads); - mBinder.onMissionAdded(mManager.getMission(missionId)); - } - }); - } + for (int i = 0; i < mManager.getCount(); i++) { + if (mManager.getMission(i).running) { + runningCount++; + } + } + updateState(runningCount); + 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 - public void onDestroy() { - super.onDestroy(); - - if (DEBUG) { - Log.d(TAG, "Destroying"); - } - - for (int i = 0; i < mManager.getCount(); i++) { - mManager.pauseMission(i); - } + private void startMissionAsync(final String url, final String location, final String name, + final boolean isAudio, final int threads) { + mHandler.post(new Runnable() { + @Override + public void run() { + int missionId = mManager.startMission(url, location, name, isAudio, threads); + mBinder.onMissionAdded(mManager.getMission(missionId)); + } + }); + } - stopForeground(true); - } - - @Override - public IBinder onBind(Intent intent) { - int permissionCheck; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { - permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE); - if(permissionCheck == PermissionChecker.PERMISSION_DENIED) { - Toast.makeText(this, "Permission denied (read)", Toast.LENGTH_SHORT).show(); - } - } + @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; + } - permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); - if(permissionCheck == PermissionChecker.PERMISSION_DENIED) { - Toast.makeText(this, "Permission denied (write)", Toast.LENGTH_SHORT).show(); - } + @Override + public void onDestroy() { + super.onDestroy(); - return mBinder; - } + if (DEBUG) { + Log.d(TAG, "Destroying"); + } - private void postUpdateMessage() { - mHandler.sendEmptyMessage(UPDATE_MESSAGE); - } - - private void updateState(int runningCount) { - if (runningCount == 0) { - stopForeground(true); - } else { - startForeground(NOTIFICATION_ID, mNotification); - } - } + for (int i = 0; i < mManager.getCount(); i++) { + mManager.pauseMission(i); + } - 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); - } + stopForeground(true); + } + + @Override + public IBinder onBind(Intent intent) { + int permissionCheck; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { + permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE); + if (permissionCheck == PermissionChecker.PERMISSION_DENIED) { + Toast.makeText(this, "Permission denied (read)", Toast.LENGTH_SHORT).show(); + } + } + + permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); + if (permissionCheck == PermissionChecker.PERMISSION_DENIED) { + Toast.makeText(this, "Permission denied (write)", Toast.LENGTH_SHORT).show(); + } + + return mBinder; + } + + 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 { - @Override - public void onProgressUpdate(DownloadMission downloadMission, long done, long total) { - long now = System.currentTimeMillis(); - long delta = now - mLastTimeStamp; - if (delta > 2000) { - postUpdateMessage(); - mLastTimeStamp = now; - } - } + private class MissionListener implements DownloadMission.MissionListener { + @Override + public void onProgressUpdate(DownloadMission downloadMission, long done, long total) { + long now = System.currentTimeMillis(); + long delta = now - mLastTimeStamp; + if (delta > 2000) { + postUpdateMessage(); + mLastTimeStamp = now; + } + } - @Override - public void onFinish(DownloadMission downloadMission) { - postUpdateMessage(); - notifyMediaScanner(downloadMission); - } + @Override + public void onFinish(DownloadMission downloadMission) { + postUpdateMessage(); + notifyMediaScanner(downloadMission); + } - @Override - public void onError(DownloadMission downloadMission, int errCode) { - postUpdateMessage(); - } - } + @Override + public void onError(DownloadMission downloadMission, int errCode) { + postUpdateMessage(); + } + } - // Wrapper of DownloadManager - public class DMBinder extends Binder { - public DownloadManager getDownloadManager() { - return mManager; - } - - public void onMissionAdded(DownloadMission mission) { - mission.addListener(missionListener); - postUpdateMessage(); - } - - public void onMissionRemoved(DownloadMission mission) { - mission.removeListener(missionListener); - postUpdateMessage(); - } - } + // Wrapper of DownloadManager + public class DMBinder extends Binder { + public DownloadManager getDownloadManager() { + return mManager; + } + + public void onMissionAdded(DownloadMission mission) { + mission.addListener(missionListener); + postUpdateMessage(); + } + + public void onMissionRemoved(DownloadMission mission) { + mission.removeListener(missionListener); + postUpdateMessage(); + } + } } diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index 5960c64fd..5b394de11 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -7,6 +7,8 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.support.v4.content.FileProvider; +import android.support.v4.view.ViewCompat; +import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -18,14 +20,13 @@ import android.widget.ImageView; import android.widget.PopupMenu; import android.widget.TextView; -import android.support.v7.widget.RecyclerView; +import org.schabi.newpipe.R; import java.io.File; import java.util.HashMap; import java.util.Locale; import java.util.Map; -import org.schabi.newpipe.R; import us.shandian.giga.get.DownloadManager; import us.shandian.giga.get.DownloadMission; import us.shandian.giga.service.DownloadManagerService; @@ -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_READ_URI_PERMISSION; -public class MissionAdapter extends RecyclerView.Adapter -{ - private static final Map ALGORITHMS = new HashMap<>(); - private static final String TAG = "MissionAdapter"; +public class MissionAdapter extends RecyclerView.Adapter { + private static final Map ALGORITHMS = new HashMap<>(); + private static final String TAG = "MissionAdapter"; - static { - ALGORITHMS.put(R.id.md5, "MD5"); - ALGORITHMS.put(R.id.sha1, "SHA1"); - } - - private Context mContext; - private LayoutInflater mInflater; - private DownloadManager mManager; - private DownloadManagerService.DMBinder mBinder; - private int mLayout; - - public MissionAdapter(Context context, DownloadManagerService.DMBinder binder, DownloadManager manager, boolean isLinear) { - mContext = context; - mManager = manager; - mBinder = binder; - - mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - mLayout = isLinear ? R.layout.mission_item_linear : R.layout.mission_item; - } + static { + ALGORITHMS.put(R.id.md5, "MD5"); + ALGORITHMS.put(R.id.sha1, "SHA1"); + } + + private Context mContext; + private LayoutInflater mInflater; + private DownloadManager mManager; + private DownloadManagerService.DMBinder mBinder; + private int mLayout; + + public MissionAdapter(Context context, DownloadManagerService.DMBinder binder, DownloadManager manager, boolean isLinear) { + mContext = context; + mManager = manager; + mBinder = binder; + + mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + mLayout = isLinear ? R.layout.mission_item_linear : R.layout.mission_item; + } + + @Override + public MissionAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + final ViewHolder h = new ViewHolder(mInflater.inflate(mLayout, parent, false)); + + h.menu.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + buildPopup(h); + } + }); - @Override - public MissionAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - final ViewHolder h = new ViewHolder(mInflater.inflate(mLayout, parent, false)); - - h.menu.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - buildPopup(h); - } - }); - /*h.itemView.setOnClickListener(new View.OnClickListener() { - @Override + @Override public void onClick(View v) { showDetail(h); } });*/ - - return h; - } - @Override - public void onViewRecycled(MissionAdapter.ViewHolder h) { - super.onViewRecycled(h); - h.mission.removeListener(h.observer); - h.mission = null; - h.observer = null; - h.progress = null; - h.position = -1; - h.lastTimeStamp = -1; - h.lastDone = -1; - h.colorId = 0; - } + return h; + } - @Override - public void onBindViewHolder(MissionAdapter.ViewHolder h, int pos) { - DownloadMission ms = mManager.getMission(pos); - h.mission = ms; - h.position = pos; - - Utility.FileType type = Utility.getFileType(ms.name); - - h.icon.setImageResource(Utility.getIconForFileType(type)); - h.name.setText(ms.name); - h.size.setText(Utility.formatBytes(ms.length)); - - h.progress = new ProgressDrawable(mContext, Utility.getBackgroundForFileType(type), Utility.getForegroundForFileType(type)); - h.bkg.setBackgroundDrawable(h.progress); - - h.observer = new MissionObserver(this, h); - ms.addListener(h.observer); - - updateProgress(h); - } + @Override + public void onViewRecycled(MissionAdapter.ViewHolder h) { + super.onViewRecycled(h); + h.mission.removeListener(h.observer); + h.mission = null; + h.observer = null; + h.progress = null; + h.position = -1; + h.lastTimeStamp = -1; + h.lastDone = -1; + h.colorId = 0; + } - @Override - public int getItemCount() { - return mManager.getCount(); - } + @Override + public void onBindViewHolder(MissionAdapter.ViewHolder h, int pos) { + DownloadMission ms = mManager.getMission(pos); + h.mission = ms; + h.position = pos; - @Override - public long getItemId(int position) { - return position; - } - - private void updateProgress(ViewHolder h) { - updateProgress(h, false); - } - - private void updateProgress(ViewHolder h, boolean finished) { - if (h.mission == null) return; - - long now = System.currentTimeMillis(); - - if (h.lastTimeStamp == -1) { - h.lastTimeStamp = now; - } - - if (h.lastDone == -1) { - h.lastDone = h.mission.done; - } - - long deltaTime = now - h.lastTimeStamp; - long deltaDone = h.mission.done - h.lastDone; - - if (deltaTime == 0 || deltaTime > 1000 || finished) { - if (h.mission.errCode > 0) { - h.status.setText(R.string.msg_error); - } else { - float progress = (float) h.mission.done / h.mission.length; - h.status.setText(String.format(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; - } - } - + Utility.FileType type = Utility.getFileType(ms.name); - 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); + h.icon.setImageResource(Utility.getIconForFileType(type)); + h.name.setText(ms.name); + h.size.setText(Utility.formatBytes(ms.length)); - 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) { - 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(); - } + h.observer = new MissionObserver(this, h); + ms.addListener(h.observer); - 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); - } + updateProgress(h); + } - private void viewFileWithFileProvider(File file, String mimetype) { - String ourPackage = mContext.getApplicationContext().getPackageName(); - Uri uri = FileProvider.getUriForFile(mContext, ourPackage + ".provider", file); - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_VIEW); - intent.setDataAndType(uri, mimetype); - intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - intent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION); - } - //mContext.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); - Log.v(TAG, "Starting intent: " + intent); - mContext.startActivity(intent); - } - - private class ChecksumTask extends AsyncTask { - ProgressDialog prog; + @Override + public int getItemCount() { + return mManager.getCount(); + } - @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 + public long getItemId(int position) { + return position; + } - @Override - protected String doInBackground(String... params) { - return Utility.checksum(params[0], params[1]); - } + private void updateProgress(ViewHolder h) { + updateProgress(h, false); + } - @Override - protected void onPostExecute(String result) { - super.onPostExecute(result); - prog.dismiss(); - Utility.copyToClipboard(mContext, result); - } - } - - static class ViewHolder extends RecyclerView.ViewHolder { - public DownloadMission mission; - public int position; + private void updateProgress(ViewHolder h, boolean finished) { + if (h.mission == null) return; - public TextView status; - public ImageView icon; - public TextView name; - public TextView size; - public View bkg; - public ImageView menu; - public ProgressDrawable progress; - public MissionObserver observer; + long now = System.currentTimeMillis(); - public long lastTimeStamp = -1; - public long lastDone = -1; - 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); - } + if (h.lastTimeStamp == -1) { + h.lastTimeStamp = now; + } - @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); - } - } + if (h.lastDone == -1) { + h.lastDone = h.mission.done; + } - @Override - public void onError(DownloadMission downloadMission, int errCode) { - mAdapter.updateProgress(mHolder); - } - - } + 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) { + 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 { + 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); + } + } } diff --git a/app/src/main/java/us/shandian/giga/ui/common/ProgressDrawable.java b/app/src/main/java/us/shandian/giga/ui/common/ProgressDrawable.java index 1b6d6de0f..6a0e35cff 100644 --- a/app/src/main/java/us/shandian/giga/ui/common/ProgressDrawable.java +++ b/app/src/main/java/us/shandian/giga/ui/common/ProgressDrawable.java @@ -6,53 +6,55 @@ import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; 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 -{ - private float mProgress; - 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(); - } +public class ProgressDrawable extends Drawable { + private float mProgress; + private int mBackgroundColor, mForegroundColor; - @Override - public void draw(Canvas canvas) { - int width = canvas.getWidth(); - int height = canvas.getHeight(); - - Paint paint = new Paint(); - - paint.setColor(mBackgroundColor); - canvas.drawRect(0, 0, width, height, paint); - - paint.setColor(mForegroundColor); - canvas.drawRect(0, 0, (int) (mProgress * width), height, paint); - } + public ProgressDrawable(Context context, @ColorRes int background, @ColorRes int foreground) { + this(ContextCompat.getColor(context, background), ContextCompat.getColor(context, foreground)); + } - @Override - public void setAlpha(int alpha) { - // Unsupported - } + public ProgressDrawable(int background, int foreground) { + mBackgroundColor = background; + mForegroundColor = foreground; + } - @Override - public void setColorFilter(ColorFilter filter) { - // Unsupported - } + public void setProgress(float progress) { + mProgress = progress; + invalidateSelf(); + } - @Override - public int getOpacity() { - return PixelFormat.OPAQUE; - } + @Override + public void draw(@NonNull Canvas canvas) { + int width = canvas.getWidth(); + int height = canvas.getHeight(); + + Paint paint = new Paint(); + + paint.setColor(mBackgroundColor); + canvas.drawRect(0, 0, width, height, paint); + + paint.setColor(mForegroundColor); + canvas.drawRect(0, 0, (int) (mProgress * width), height, paint); + } + + @Override + public void setAlpha(int alpha) { + // Unsupported + } + + @Override + public void setColorFilter(ColorFilter filter) { + // Unsupported + } + + @Override + public int getOpacity() { + return PixelFormat.OPAQUE; + } } diff --git a/app/src/main/java/us/shandian/giga/ui/common/ToolbarActivity.java b/app/src/main/java/us/shandian/giga/ui/common/ToolbarActivity.java index 7200bb522..7e732f6f9 100644 --- a/app/src/main/java/us/shandian/giga/ui/common/ToolbarActivity.java +++ b/app/src/main/java/us/shandian/giga/ui/common/ToolbarActivity.java @@ -1,26 +1,23 @@ package us.shandian.giga.ui.common; import android.os.Bundle; - import android.support.v7.app.ActionBarActivity; import android.support.v7.widget.Toolbar; import org.schabi.newpipe.R; -import us.shandian.giga.util.Utility; -public abstract class ToolbarActivity extends ActionBarActivity -{ - protected Toolbar mToolbar; +public abstract class ToolbarActivity extends ActionBarActivity { + protected Toolbar mToolbar; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(getLayoutResource()); - - mToolbar = Utility.findViewById(this, R.id.toolbar); - - setSupportActionBar(mToolbar); - } - - protected abstract int getLayoutResource(); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(getLayoutResource()); + + mToolbar = (Toolbar) this.findViewById(R.id.toolbar); + + setSupportActionBar(mToolbar); + } + + protected abstract int getLayoutResource(); } diff --git a/app/src/main/java/us/shandian/giga/ui/fragment/AllMissionsFragment.java b/app/src/main/java/us/shandian/giga/ui/fragment/AllMissionsFragment.java index 452024223..ec8d7fc22 100644 --- a/app/src/main/java/us/shandian/giga/ui/fragment/AllMissionsFragment.java +++ b/app/src/main/java/us/shandian/giga/ui/fragment/AllMissionsFragment.java @@ -3,11 +3,10 @@ package us.shandian.giga.ui.fragment; import us.shandian.giga.get.DownloadManager; import us.shandian.giga.service.DownloadManagerService; -public class AllMissionsFragment extends MissionsFragment -{ +public class AllMissionsFragment extends MissionsFragment { - @Override - protected DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder) { - return binder.getDownloadManager(); - } + @Override + protected DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder) { + return binder.getDownloadManager(); + } } diff --git a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java index b18fc58d1..9e77fbafb 100644 --- a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java +++ b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java @@ -23,126 +23,128 @@ import org.schabi.newpipe.R; import us.shandian.giga.get.DownloadManager; import us.shandian.giga.service.DownloadManagerService; import us.shandian.giga.ui.adapter.MissionAdapter; -import us.shandian.giga.util.Utility; -public abstract class MissionsFragment extends Fragment -{ - private DownloadManager mManager; - private DownloadManagerService.DMBinder mBinder; - - private SharedPreferences mPrefs; - private boolean mLinear; - private MenuItem mSwitch; - - private RecyclerView mList; - private MissionAdapter mAdapter; - private GridLayoutManager mGridManager; - private LinearLayoutManager mLinearManager; - private Context mActivity; - - private ServiceConnection mConnection = new ServiceConnection() { +public abstract class MissionsFragment extends Fragment { + private DownloadManager mManager; + private DownloadManagerService.DMBinder mBinder; - @Override - public void onServiceConnected(ComponentName name, IBinder binder) { - mBinder = (DownloadManagerService.DMBinder) binder; - mManager = setupDownloadManager(mBinder); - updateList(); - } + private SharedPreferences mPrefs; + private boolean mLinear; + private MenuItem mSwitch; - @Override - public void onServiceDisconnected(ComponentName name) { - // What to do? - } + private RecyclerView mList; + private MissionAdapter mAdapter; + private GridLayoutManager mGridManager; + private LinearLayoutManager mLinearManager; + private Context mActivity; - - }; + private ServiceConnection mConnection = new ServiceConnection() { - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.missions, container, false); + @Override + public void onServiceConnected(ComponentName name, IBinder binder) { + mBinder = (DownloadManagerService.DMBinder) binder; + mManager = setupDownloadManager(mBinder); + updateList(); + } - mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); - mLinear = mPrefs.getBoolean("linear", false); + @Override + 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 - public void onAttach(Context activity) { - super.onAttach(activity); + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.missions, container, false); - // Bug: in api< 23 this is never called - // so mActivity=null - // so app crashes with nullpointer exception - mActivity = activity; - } + mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); + mLinear = mPrefs.getBoolean("linear", false); - /** deprecated in API level 23, - * but must remain to allow compatibility with api<23 */ - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); + // Bind the service + Intent i = new Intent(); + i.setClass(getActivity(), DownloadManagerService.class); + getActivity().bindService(i, mConnection, Context.BIND_AUTO_CREATE); - mActivity = activity; - } + // Views + mList = (RecyclerView) v.findViewById(R.id.mission_recycler); - @Override - public void onDestroyView() { - super.onDestroyView(); - getActivity().unbindService(mConnection); - } + // Init + mGridManager = new GridLayoutManager(getActivity(), 2); + mLinearManager = new LinearLayoutManager(getActivity()); + mList.setLayoutManager(mGridManager); - @Override - public boolean onOptionsItemSelected(MenuItem item) { - return super.onOptionsItemSelected(item); + setHasOptionsMenu(true); + + 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()) { - case R.id.switch_mode: + case R.id.switch_mode: mLinear = !mLinear; updateList(); return true; default: return super.onOptionsItemSelected(item); }*/ - } + } - public void notifyChange() { - mAdapter.notifyDataSetChanged(); - } - - private void updateList() { - mAdapter = new MissionAdapter(mActivity, mBinder, mManager, mLinear); - - if (mLinear) { - mList.setLayoutManager(mLinearManager); - } else { - mList.setLayoutManager(mGridManager); - } - - mList.setAdapter(mAdapter); - - if (mSwitch != null) { - mSwitch.setIcon(mLinear ? R.drawable.grid : R.drawable.list); - } - - mPrefs.edit().putBoolean("linear", mLinear).commit(); - } - - protected abstract DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder); + public void notifyChange() { + mAdapter.notifyDataSetChanged(); + } + + private void updateList() { + mAdapter = new MissionAdapter(mActivity, mBinder, mManager, mLinear); + + if (mLinear) { + mList.setLayoutManager(mLinearManager); + } else { + mList.setLayoutManager(mGridManager); + } + + mList.setAdapter(mAdapter); + + if (mSwitch != null) { + mSwitch.setIcon(mLinear ? R.drawable.grid : R.drawable.list); + } + + mPrefs.edit().putBoolean("linear", mLinear).commit(); + } + + protected abstract DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder); } diff --git a/app/src/main/java/us/shandian/giga/util/CrashHandler.java b/app/src/main/java/us/shandian/giga/util/CrashHandler.java index 6f6564e2f..66baf6f59 100644 --- a/app/src/main/java/us/shandian/giga/util/CrashHandler.java +++ b/app/src/main/java/us/shandian/giga/util/CrashHandler.java @@ -9,77 +9,76 @@ import java.io.File; import java.io.PrintWriter; //todo: replace this by using the internal crash handler of newpipe -public class CrashHandler implements Thread.UncaughtExceptionHandler -{ - 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_TAG = CRASH_DIR + ".crashed"; +public class CrashHandler implements Thread.UncaughtExceptionHandler { + 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_TAG = CRASH_DIR + ".crashed"; - private static String ANDROID = Build.VERSION.RELEASE; - private static String MODEL = Build.MODEL; - private static String MANUFACTURER = Build.MANUFACTURER; + private static String ANDROID = Build.VERSION.RELEASE; + private static String MODEL = Build.MODEL; + 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) { - try { - PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); - VERSION = info.versionName + info.versionCode; - } catch (Exception e) { - throw new RuntimeException(e); - } - } + public static void init(Context context) { + try { + PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + VERSION = info.versionName + info.versionCode; + } catch (Exception e) { + throw new RuntimeException(e); + } + } - public static void register() { - new CrashHandler(); - } + public static void register() { + new CrashHandler(); + } - private CrashHandler() { - mPrevious = Thread.currentThread().getUncaughtExceptionHandler(); - Thread.currentThread().setUncaughtExceptionHandler(this); - } + private CrashHandler() { + mPrevious = Thread.currentThread().getUncaughtExceptionHandler(); + Thread.currentThread().setUncaughtExceptionHandler(this); + } - @Override - public void uncaughtException(Thread thread, Throwable throwable) { - File f = new File(CRASH_LOG); - if (f.exists()) { - f.delete(); - } else { - try { - new File(CRASH_DIR).mkdirs(); - f.createNewFile(); - } catch (Exception e) { - return; - } - } + @Override + public void uncaughtException(Thread thread, Throwable throwable) { + File f = new File(CRASH_LOG); + if (f.exists()) { + f.delete(); + } else { + try { + new File(CRASH_DIR).mkdirs(); + f.createNewFile(); + } catch (Exception e) { + return; + } + } - PrintWriter p; - try { - p = new PrintWriter(f); - } catch (Exception e) { - return; - } + PrintWriter p; + try { + p = new PrintWriter(f); + } catch (Exception e) { + return; + } - p.write("Android Version: " + ANDROID + "\n"); - p.write("Device Model: " + MODEL + "\n"); - p.write("Device Manufacturer: " + MANUFACTURER + "\n"); - p.write("App Version: " + VERSION + "\n"); - p.write("*********************\n"); - throwable.printStackTrace(p); + p.write("Android Version: " + ANDROID + "\n"); + p.write("Device Model: " + MODEL + "\n"); + p.write("Device Manufacturer: " + MANUFACTURER + "\n"); + p.write("App Version: " + VERSION + "\n"); + p.write("*********************\n"); + throwable.printStackTrace(p); - p.close(); + p.close(); - try { - new File(CRASH_TAG).createNewFile(); - } catch (Exception e) { - return; - } + try { + new File(CRASH_TAG).createNewFile(); + } catch (Exception e) { + return; + } - if (mPrevious != null) { - mPrevious.uncaughtException(thread, throwable); - } - } + if (mPrevious != null) { + mPrevious.uncaughtException(thread, throwable); + } + } } diff --git a/app/src/main/java/us/shandian/giga/util/Utility.java b/app/src/main/java/us/shandian/giga/util/Utility.java index 856b841fe..1d7b629e4 100644 --- a/app/src/main/java/us/shandian/giga/util/Utility.java +++ b/app/src/main/java/us/shandian/giga/util/Utility.java @@ -1,256 +1,221 @@ package us.shandian.giga.util; -import android.app.Activity; -import android.content.ClipboardManager; import android.content.ClipData; +import android.content.ClipboardManager; import android.content.Context; -import android.content.Intent; -import android.view.View; +import android.support.annotation.ColorRes; +import android.support.annotation.DrawableRes; +import android.support.annotation.Nullable; import android.widget.Toast; +import org.schabi.newpipe.R; + import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import org.schabi.newpipe.settings.NewPipeSettings; -import org.schabi.newpipe.R; +public class Utility { -import com.nononsenseapps.filepicker.FilePickerActivity; -import com.nononsenseapps.filepicker.AbstractFilePickerFragment; + public enum FileType { + VIDEO, + MUSIC, + UNKNOWN + } -public class Utility -{ - - public static enum FileType { - VIDEO, - MUSIC, - UNKNOWN - } - - public static String formatBytes(long bytes) { - if (bytes < 1024) { - return String.format("%d B", bytes); - } else if (bytes < 1024 * 1024) { - return String.format("%.2f kB", (float) bytes / 1024); - } else if (bytes < 1024 * 1024 * 1024) { - return String.format("%.2f MB", (float) bytes / 1024 / 1024); - } else { - return String.format("%.2f GB", (float) bytes / 1024 / 1024 / 1024); - } - } - - public static String formatSpeed(float speed) { - if (speed < 1024) { - return String.format("%.2f B/s", speed); - } else if (speed < 1024 * 1024) { - return String.format("%.2f kB/s", speed / 1024); - } else if (speed < 1024 * 1024 * 1024) { - return String.format("%.2f MB/s", speed / 1024 / 1024); - } else { - return String.format("%.2f GB/s", speed / 1024 / 1024 / 1024); - } - } - - public static void writeToFile(String fileName, String content) { - try { - writeToFile(fileName, content.getBytes("UTF-8")); - } catch (Exception e) { - - } - } - - public static void writeToFile(String fileName, byte[] content) { - File f = new File(fileName); - - if (!f.exists()) { - try { - f.createNewFile(); - } catch (Exception e) { - - } - } - - try { - FileOutputStream opt = new FileOutputStream(f, false); - opt.write(content, 0, content.length); - opt.close(); - } catch (Exception e) { - - } - } - - public static String readFromFile(String file) { - try { - File f = new File(file); - - if (!f.exists() || !f.canRead()) { - return null; - } - - BufferedInputStream ipt = new BufferedInputStream(new FileInputStream(f)); - - byte[] buf = new byte[512]; - StringBuilder sb = new StringBuilder(); - - while (ipt.available() > 0) { - int len = ipt.read(buf, 0, 512); - sb.append(new String(buf, 0, len, "UTF-8")); - } - - ipt.close(); - return sb.toString(); - } catch (Exception e) { - return null; - } - } - - public static T findViewById(View v, int id) { - return (T) v.findViewById(id); - } - - public static 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 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 FileType getFileType(String file) { - 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; - } - } + public static void writeToFile(String fileName, String content) { + try { + writeToFile(fileName, content.getBytes("UTF-8")); + } catch (Exception e) { - public static Boolean isMusicFile(String file) - { - return Utility.getFileType(file) == FileType.MUSIC; - } + } + } - public static Boolean isVideoFile(String file) - { - return Utility.getFileType(file) == FileType.VIDEO; - } - - public static int getBackgroundForFileType(FileType type) { - switch (type) { - case 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 void writeToFile(String fileName, byte[] content) { + File f = new File(fileName); - 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; - } - } + if (!f.exists()) { + try { + f.createNewFile(); + } catch (Exception e) { - public static boolean isDirectoryAvailble(String path) { - File dir = new File(path); - return dir.exists() && dir.isDirectory(); - } + } + } - public static boolean isDownloadDirectoryAvailble(Context context) { - return isDirectoryAvailble(NewPipeSettings.getVideoDownloadPath(context)); - } + try { + 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); - i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true); - i.putExtra(FilePickerActivity.EXTRA_MODE, AbstractFilePickerFragment.MODE_DIR); - activity.startActivityForResult(i, 233); - } - - 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(); - - } + } + } + + 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; + } + } + + @Nullable + public static String getFileExt(String url) { + int index; + if ((index = url.indexOf("?")) > -1) { + url = url.substring(0, index); + } + + index = url.lastIndexOf("."); + if (index == -1) { + return null; + } else { + String ext = url.substring(index); + if ((index = ext.indexOf("%")) > -1) { + ext = ext.substring(0, index); + } + if ((index = ext.indexOf("/")) > -1) { + ext = ext.substring(0, index); + } + return ext.toLowerCase(); + } + } + + public static FileType getFileType(String file) { + 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(); + + } } diff --git a/app/src/main/res/anim/custom_fade_in.xml b/app/src/main/res/anim/custom_fade_in.xml new file mode 100644 index 000000000..2b94a1394 --- /dev/null +++ b/app/src/main/res/anim/custom_fade_in.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/custom_fade_out.xml b/app/src/main/res/anim/custom_fade_out.xml new file mode 100644 index 000000000..03a6bc9bb --- /dev/null +++ b/app/src/main/res/anim/custom_fade_out.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 1e56b5ce8..484eca319 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/layout/channel_item.xml b/app/src/main/res/layout/channel_item.xml index bbdf6779b..6347c4946 100644 --- a/app/src/main/res/layout/channel_item.xml +++ b/app/src/main/res/layout/channel_item.xml @@ -4,11 +4,10 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/itemRoot" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/video_item_search_height" android:background="?attr/selectableItemBackground" android:clickable="true" - android:orientation="vertical" - android:padding="12dp"> + android:padding="@dimen/video_item_search_padding"> - + android:layout_marginBottom="@dimen/video_item_search_image_right_margin" + 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"/> - + - - + + - - diff --git a/app/src/main/res/layout/error_retry.xml b/app/src/main/res/layout/error_retry.xml index edd576e1c..567012f1e 100644 --- a/app/src/main/res/layout/error_retry.xml +++ b/app/src/main/res/layout/error_retry.xml @@ -1,17 +1,17 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_channel.xml b/app/src/main/res/layout/fragment_channel.xml index 807a5ac4c..03e561a79 100644 --- a/app/src/main/res/layout/fragment_channel.xml +++ b/app/src/main/res/layout/fragment_channel.xml @@ -1,9 +1,9 @@ - + android:layout_height="match_parent" + tools:context=".fragments.channel.ChannelFragment"> + tools:listitem="@layout/stream_item" /> - - - - - + android:layout_gravity="center" /> + diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 80aa1ab4c..a779a18e0 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -1,6 +1,5 @@ - + tools:listitem="@layout/stream_item" /> - - - - - + android:layout_gravity="center_vertical" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index aa29b822c..7e7fff952 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -1,6 +1,5 @@ - - + android:layout_height="wrap_content" + android:orientation="vertical"> - + android:background="@android:color/black" + android:clickable="true" + android:foreground="?attr/selectableItemBackground"> + tools:src="@drawable/dummy_thumbnail" /> + tools:visibility="visible" /> -