diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 873c1780f85..f61e320c9d0 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -12,57 +12,53 @@ add a comment to it. You'll see exactly what is sent, the system is 100% transpa
## Issue reporting/feature requests
* Search the [existing issues](https://github.com/TeamNewPipe/NewPipe/issues) first to make sure your issue/feature
-hasn't been reported/requested before
-* Check whether your issue/feature is already fixed/implemented
-* Check if the issue still exists in the latest release/beta version
-* If you are an Android/Java developer, you are always welcome to fix/implement an issue/a feature yourself. PRs welcome!
+hasn't been reported/requested before.
+* Check whether your issue/feature is already fixed/implemented.
+* Check if the issue still exists in the latest release/beta version.
+* If you are an Android/Java developer, you are always welcome to fix an issue or implement a feature yourself. PRs welcome!
* We use English for development. Issues in other languages will be closed and ignored.
* Please only add *one* issue at a time. Do not put multiple issues into one thread.
-* When reporting a bug please give us a context, and a description how to reproduce it.
-* Issues that only contain a generated bug report, but no description might be closed.
+* Follow the template! Issues or feature requests not matching the template might be closed.
## Bug Fixing
* If you want to help NewPipe to become free of bugs (this is our utopic goal for NewPipe), you can send us an email to
-tnp@newpipe.schabi.org to let me know that you intend to help. We'll send you further instructions. You may, on request,
-register at our [Sentry](https://sentry.schabi.org) instance (see section "Crash reporting" for more information.
+tnp@newpipe.schabi.org to let us know that you intend to help. We'll send you further instructions. You may, on request,
+register at our [Sentry](https://sentry.schabi.org) instance (see section "Crash reporting" for more information).
## Translation
-* NewPipe can be translated via [Weblate](https://hosted.weblate.org/projects/newpipe/strings/). You can log in there
+* NewPipe is translated via [Weblate](https://hosted.weblate.org/projects/newpipe/strings/). You can log in there
with your GitHub account.
+* If the language you want to translate is not on Weblate, you can add it: see [How to add a new language](https://github.com/TeamNewPipe/NewPipe/wiki/How-to-add-a-new-language-to-NewPipe) in the wiki.
## Code contribution
-* Stick to NewPipe's style conventions (well, just look the other code and then do it the same way :))
-* Do not bring non-free software (e.g., binary blobs) into the project. Also, make sure you do not introduce Google
+* Stick to NewPipe's style conventions: follow [checkStyle](https://github.com/checkstyle/checkstyle). It will run each time you build the project.
+* Do not bring non-free software (e.g. binary blobs) into the project. Also, make sure you do not introduce Google
libraries.
-* Stick to [F-Droid contribution guidelines](https://f-droid.org/wiki/page/Inclusion_Policy)
-* Make changes on a separate branch, not on the master branch. This is commonly known as *feature branch workflow*. You
- may then send your changes as a pull request on GitHub. Patches to the email address mentioned in this document might
- not be considered, GitHub is the primary platform. (This only affects you if you are a member of TeamNewPipe)
+* Stick to [F-Droid contribution guidelines](https://f-droid.org/wiki/page/Inclusion_Policy).
+* Make changes on a separate branch with a meaningful name, not on the master neither dev branch. This is commonly known as *feature branch workflow*. You
+ may then send your changes as a pull request (PR) on GitHub.
* When submitting changes, you confirm that your code is licensed under the terms of the
[GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.html).
* Please test (compile and run) your code before you submit changes! Ideally, provide test feedback in the PR
description. Untested code will **not** be merged!
* Try to figure out yourself why builds on our CI fail.
* Make sure your PR is up-to-date with the rest of the code. Often, a simple click on "Update branch" will do the job,
- but if not, you are asked to merge the master branch manually and resolve the problems on your own. That will make the
+ but if not, you are asked to rebase the dev branch manually and resolve the problems on your own. You can find help [on the wiki](https://github.com/TeamNewPipe/NewPipe/wiki/How-to-merge-a-PR). That will make the
maintainers' jobs way easier.
* Please show intention to maintain your features and code after you contributed it. Unmaintained code is a hassle for
the core developers, and just adds work. If you do not intend to maintain features you contributed, please think again
about submission, or clearly state that in the description of your PR.
* Respond yourselves if someone requests changes or otherwise raises issues about your PRs.
-* Check if your contributions align with the [fdroid inclusion guidelines](https://f-droid.org/en/docs/Inclusion_Policy/).
-* Check if your submission can be build with the current fdroid build server setup.
* Send PR that only cover one specific issue/solution/bug. Do not send PRs that are huge and consists of multiple
independent solutions.
## Communication
-* WE DO NOW HAVE A MAILING LIST: [newpipe@list.schabi.org](https://list.schabi.org/cgi-bin/mailman/listinfo/newpipe).
* There is an IRC channel on Freenode which is regularly visited by the core team and other developers:
[#newpipe](irc:irc.freenode.net/newpipe). [Click here for Webchat](https://webchat.freenode.net/?channels=newpipe)!
* If you want to get in touch with the core team or one of our other contributors you can send an email to
- tnp(at)schabi.org. Please do not send issue reports, they will be ignored and remain unanswered! Use the GitHub issue
+ tnp@newpipe.schabi.org. Please do not send issue reports, they will be ignored and remain unanswered! Use the GitHub issue
tracker described above!
-* Feel free to post suggestions, changes, ideas etc. on GitHub, IRC or the mailing list!
+* Feel free to post suggestions, changes, ideas etc. on GitHub or IRC!
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 202e8a71ad6..dbc1c05a5b5 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -7,38 +7,40 @@ assignees: ''
---
-### Version
-
--
+
+
+### Version
+
+-
### Steps to reproduce the bug
-
-Steps to reproduce the behavior:
+
+
+
### Expected behavior
-Tell us what you expected to happen.
+
### Actual behaviour
-Tell us what happens instead.
+
-### Screenshots/Screen records
-If applicable, add screenshots or a screen recording to help explain your problem. GitHub should support uploading them directly in the issue field. If your file is too big, feel free to paste a link from an image/video hoster here instead.
+### Screenshots/Screen recordings
+
### Logs
-If your bug includes a crash, please head over to the [incredible bugreport to markdown converter](https://teamnewpipe.github.io/CrashReportToMarkdown/). Copy the result. Paste it here:
+
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 89fe58658a9..90134a204c2 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -7,22 +7,33 @@ assignees: ''
---
-#### Is your feature request related to a problem? Please describe it
-A clear and concise description of what the problem is.
-Example: *I want to do X, but there is no way to do it.*
-#### Describe the solution you'd like
-A clear and concise description of what you want to happen.
+
+
+#### Describe the feature you want
+
+
+
+
+#### Is your feature request related to a problem? Please describe it
+
+
+
#### Additional context
-Add any other context or screenshots about the feature request here.
-Example: *Here's a photo of my cat!*
+
+
+
#### How will you/everyone benefit from this feature?
-Convince us! How does it change your NewPipe experience and/or your life?
+
+
+
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 9a1193767dd..f12eb2fe81b 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,10 +1,12 @@
#### What is it?
-- [ ] Bug fix
-- [ ] Feature
+- [ ] Bug fix (user facing)
+- [ ] Feature (user facing)
+- [ ] Code base improvement (dev facing)
+- [ ] Meta improvement to the project (dev facing)
-#### Long description of the changes in your PR
+#### Description of the changes in your PR
- record videos
- create clones
diff --git a/README.md b/README.md
index 987327ab857..50eb40594ae 100644
--- a/README.md
+++ b/README.md
@@ -4,10 +4,10 @@
* It includes a workaround to fix the menu visibility when the adapter is restored.
+ *
*
* When restoring the state of this adapter, all the fragments' menu visibility were set to false,
- * effectively disabling the menu from the user until he switched pages or another event that triggered the
- * menu to be visible again happened.
+ * effectively disabling the menu from the user until he switched pages or another event
+ * that triggered the menu to be visible again happened.
+ *
*
- * Check out the changes in:
+ * Check out the changes in:
+ *
*
*
{@link #saveState()}
*
{@link #restoreState(Parcelable, ClassLoader)}
@@ -88,8 +91,8 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
private Fragment mCurrentPrimaryItem = null;
/**
- * Constructor for {@link FragmentStatePagerAdapterMenuWorkaround} that sets the fragment manager for the
- * adapter. This is the equivalent of calling
+ * Constructor for {@link FragmentStatePagerAdapterMenuWorkaround}
+ * that sets the fragment manager for the adapter. This is the equivalent of calling
* {@link #FragmentStatePagerAdapterMenuWorkaround(FragmentManager, int)} and passing in
* {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}.
*
@@ -101,7 +104,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}
*/
@Deprecated
- public FragmentStatePagerAdapterMenuWorkaround(@NonNull FragmentManager fm) {
+ public FragmentStatePagerAdapterMenuWorkaround(@NonNull final FragmentManager fm) {
this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
}
@@ -117,20 +120,21 @@ public FragmentStatePagerAdapterMenuWorkaround(@NonNull FragmentManager fm) {
* @param fm fragment manager that will interact with this adapter
* @param behavior determines if only current fragments are in a resumed state
*/
- public FragmentStatePagerAdapterMenuWorkaround(@NonNull FragmentManager fm,
- @Behavior int behavior) {
+ public FragmentStatePagerAdapterMenuWorkaround(@NonNull final FragmentManager fm,
+ @Behavior final int behavior) {
mFragmentManager = fm;
mBehavior = behavior;
}
/**
- * Return the Fragment associated with a specified position.
+ * @param position the position of the item you want
+ * @return the {@link Fragment} associated with a specified position
*/
@NonNull
public abstract Fragment getItem(int position);
@Override
- public void startUpdate(@NonNull ViewGroup container) {
+ public void startUpdate(@NonNull final ViewGroup container) {
if (container.getId() == View.NO_ID) {
throw new IllegalStateException("ViewPager with adapter " + this
+ " requires a view id");
@@ -140,7 +144,7 @@ public void startUpdate(@NonNull ViewGroup container) {
@SuppressWarnings("deprecation")
@NonNull
@Override
- public Object instantiateItem(@NonNull ViewGroup container, int position) {
+ public Object instantiateItem(@NonNull final ViewGroup container, final int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
@@ -157,7 +161,9 @@ public Object instantiateItem(@NonNull ViewGroup container, int position) {
}
Fragment fragment = getItem(position);
- if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
+ if (DEBUG) {
+ Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
+ }
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
@@ -183,14 +189,17 @@ public Object instantiateItem(@NonNull ViewGroup container, int position) {
}
@Override
- public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
+ public void destroyItem(@NonNull final ViewGroup container, final int position,
+ @NonNull final Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
- if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
- + " v=" + ((Fragment)object).getView());
+ if (DEBUG) {
+ Log.v(TAG, "Removing item #" + position + ": f=" + object
+ + " v=" + ((Fragment) object).getView());
+ }
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
@@ -206,8 +215,9 @@ public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Obj
@Override
@SuppressWarnings({"ReferenceEquality", "deprecation"})
- public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
- Fragment fragment = (Fragment)object;
+ public void setPrimaryItem(@NonNull final ViewGroup container, final int position,
+ @NonNull final Object object) {
+ Fragment fragment = (Fragment) object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
@@ -235,7 +245,7 @@ public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull
}
@Override
- public void finishUpdate(@NonNull ViewGroup container) {
+ public void finishUpdate(@NonNull final ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitNowAllowingStateLoss();
mCurTransaction = null;
@@ -243,12 +253,12 @@ public void finishUpdate(@NonNull ViewGroup container) {
}
@Override
- public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
- return ((Fragment)object).getView() == view;
+ public boolean isViewFromObject(@NonNull final View view, @NonNull final Object object) {
+ return ((Fragment) object).getView() == view;
}
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- private final String SELECTED_FRAGMENT = "selected_fragment";
+ private final String selectedFragment = "selected_fragment";
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@Override
@@ -261,7 +271,7 @@ public Parcelable saveState() {
mSavedState.toArray(fss);
state.putParcelableArray("states", fss);
}
- for (int i=0; i keys = bundle.keySet();
@@ -304,7 +314,8 @@ public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loade
mFragments.add(null);
}
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- final boolean wasSelected = bundle.getString(SELECTED_FRAGMENT, "").equals(key);
+ final boolean wasSelected = bundle.getString(selectedFragment, "")
+ .equals(key);
f.setMenuVisibility(wasSelected);
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
mFragments.set(index, f);
diff --git a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java
index 4a2662f5384..09f9aea586f 100644
--- a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java
+++ b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java
@@ -1,24 +1,60 @@
package com.google.android.material.appbar;
import android.content.Context;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.OverScroller;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import java.lang.reflect.Field;
-// check this https://stackoverflow.com/questions/56849221/recyclerview-fling-causes-laggy-while-appbarlayout-is-scrolling/57997489#57997489
+// See https://stackoverflow.com/questions/56849221#57997489
public final class FlingBehavior extends AppBarLayout.Behavior {
+ private final Rect focusScrollRect = new Rect();
- public FlingBehavior(Context context, AttributeSet attrs) {
+ public FlingBehavior(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
@Override
- public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
+ public boolean onRequestChildRectangleOnScreen(
+ @NonNull final CoordinatorLayout coordinatorLayout, @NonNull final AppBarLayout child,
+ @NonNull final Rect rectangle, final boolean immediate) {
+
+ focusScrollRect.set(rectangle);
+
+ coordinatorLayout.offsetDescendantRectToMyCoords(child, focusScrollRect);
+
+ int height = coordinatorLayout.getHeight();
+
+ if (focusScrollRect.top <= 0 && focusScrollRect.bottom >= height) {
+ // the child is too big to fit inside ourselves completely, ignore request
+ return false;
+ }
+
+ int dy;
+
+ if (focusScrollRect.bottom > height) {
+ dy = focusScrollRect.top;
+ } else if (focusScrollRect.top < 0) {
+ // scrolling up
+ dy = -(height - focusScrollRect.bottom);
+ } else {
+ // nothing to do
+ return false;
+ }
+
+ int consumed = scroll(coordinatorLayout, child, dy, getMaxDragOffset(child), 0);
+
+ return consumed == dy;
+ }
+
+ public boolean onInterceptTouchEvent(final CoordinatorLayout parent, final AppBarLayout child,
+ final MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
// remove reference to old nested scrolling child
@@ -35,7 +71,8 @@ public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout chil
@Nullable
private OverScroller getScrollerField() {
try {
- Class> headerBehaviorType = this.getClass().getSuperclass().getSuperclass().getSuperclass();
+ Class> headerBehaviorType = this.getClass()
+ .getSuperclass().getSuperclass().getSuperclass();
if (headerBehaviorType != null) {
Field field = headerBehaviorType.getDeclaredField("scroller");
field.setAccessible(true);
@@ -62,12 +99,14 @@ private Field getLastNestedScrollingChildRefField() {
return null;
}
- private void resetNestedScrollingChild(){
+ private void resetNestedScrollingChild() {
Field field = getLastNestedScrollingChildRefField();
- if(field != null){
+ if (field != null) {
try {
Object value = field.get(this);
- if(value != null) field.set(this, null);
+ if (value != null) {
+ field.set(this, null);
+ }
} catch (IllegalAccessException e) {
// ?
}
@@ -76,7 +115,8 @@ private void resetNestedScrollingChild(){
private void stopAppBarLayoutFling() {
OverScroller scroller = getScrollerField();
- if (scroller != null) scroller.forceFinished(true);
+ if (scroller != null) {
+ scroller.forceFinished(true);
+ }
}
-
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java b/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java
index da601a42f75..9321b307196 100644
--- a/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java
+++ b/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java
@@ -23,17 +23,25 @@
/**
* Singleton:
* Used to send data between certain Activity/Services within the same process.
- * This can be considered as an ugly hack inside the Android universe. **/
+ * This can be considered as an ugly hack inside the Android universe.
+ **/
public class ActivityCommunicator {
private static ActivityCommunicator activityCommunicator;
+ private volatile Class returnActivity;
public static ActivityCommunicator getCommunicator() {
- if(activityCommunicator == null) {
+ if (activityCommunicator == null) {
activityCommunicator = new ActivityCommunicator();
}
return activityCommunicator;
}
- public volatile Class returnActivity;
+ public Class getReturnActivity() {
+ return returnActivity;
+ }
+
+ public void setReturnActivity(final Class returnActivity) {
+ this.returnActivity = returnActivity;
+ }
}
diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java
index dae143b6cd0..4d05c69cc60 100644
--- a/app/src/main/java/org/schabi/newpipe/App.java
+++ b/app/src/main/java/org/schabi/newpipe/App.java
@@ -5,10 +5,12 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
+import android.content.SharedPreferences;
import android.os.Build;
import android.util.Log;
import androidx.annotation.Nullable;
+import androidx.preference.PreferenceManager;
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
import com.nostra13.universalimageloader.core.ImageLoader;
@@ -27,7 +29,7 @@
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.SettingsActivity;
-import org.schabi.newpipe.util.ExtractorHelper;
+import org.schabi.newpipe.util.ExceptionUtils;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver;
@@ -66,15 +68,24 @@
public class App extends Application {
protected static final String TAG = App.class.toString();
- private RefWatcher refWatcher;
- private static App app;
-
@SuppressWarnings("unchecked")
private static final Class extends ReportSenderFactory>[]
- reportSenderFactoryClasses = new Class[]{AcraReportSenderFactory.class};
+ REPORT_SENDER_FACTORY_CLASSES = new Class[]{AcraReportSenderFactory.class};
+ private static App app;
+ private RefWatcher refWatcher;
+
+ @Nullable
+ public static RefWatcher getRefWatcher(final Context context) {
+ final App application = (App) context.getApplicationContext();
+ return application.refWatcher;
+ }
+
+ public static App getApp() {
+ return app;
+ }
@Override
- protected void attachBaseContext(Context base) {
+ protected void attachBaseContext(final Context base) {
super.attachBaseContext(base);
initACRA();
@@ -116,31 +127,46 @@ public void onCreate() {
}
protected Downloader getDownloader() {
- return DownloaderImpl.init(null);
+ DownloaderImpl downloader = DownloaderImpl.init(null);
+ setCookiesToDownloader(downloader);
+ return downloader;
+ }
+
+ protected void setCookiesToDownloader(final DownloaderImpl downloader) {
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
+ getApplicationContext());
+ final String key = getApplicationContext().getString(R.string.recaptcha_cookies_key);
+ downloader.setCookies(prefs.getString(key, ""));
}
private void configureRxJavaErrorHandler() {
// https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling
RxJavaPlugins.setErrorHandler(new Consumer() {
@Override
- public void accept(@NonNull Throwable throwable) {
- Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : " +
- "throwable = [" + throwable.getClass().getName() + "]");
+ public void accept(@NonNull final Throwable throwable) {
+ Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : "
+ + "throwable = [" + throwable.getClass().getName() + "]");
+ final Throwable actualThrowable;
if (throwable instanceof UndeliverableException) {
- // As UndeliverableException is a wrapper, get the cause of it to get the "real" exception
- throwable = throwable.getCause();
+ // As UndeliverableException is a wrapper,
+ // get the cause of it to get the "real" exception
+ actualThrowable = throwable.getCause();
+ } else {
+ actualThrowable = throwable;
}
final List errors;
- if (throwable instanceof CompositeException) {
- errors = ((CompositeException) throwable).getExceptions();
+ if (actualThrowable instanceof CompositeException) {
+ errors = ((CompositeException) actualThrowable).getExceptions();
} else {
- errors = Collections.singletonList(throwable);
+ errors = Collections.singletonList(actualThrowable);
}
for (final Throwable error : errors) {
- if (isThrowableIgnored(error)) return;
+ if (isThrowableIgnored(error)) {
+ return;
+ }
if (isThrowableCritical(error)) {
reportException(error);
return;
@@ -150,22 +176,24 @@ public void accept(@NonNull Throwable throwable) {
// Out-of-lifecycle exceptions should only be reported if a debug user wishes so,
// When exception is not reported, log it
if (isDisposedRxExceptionsReported()) {
- reportException(throwable);
+ reportException(actualThrowable);
} else {
- Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", throwable);
+ Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", actualThrowable);
}
}
private boolean isThrowableIgnored(@NonNull final Throwable throwable) {
// Don't crash the application over a simple network problem
- return ExtractorHelper.hasAssignableCauseThrowable(throwable,
- IOException.class, SocketException.class, // network api cancellation
- InterruptedException.class, InterruptedIOException.class); // blocking code disposed
+ return ExceptionUtils.hasAssignableCause(throwable,
+ // network api cancellation
+ IOException.class, SocketException.class,
+ // blocking code disposed
+ InterruptedException.class, InterruptedIOException.class);
}
private boolean isThrowableCritical(@NonNull final Throwable throwable) {
// Though these exceptions cannot be ignored
- return ExtractorHelper.hasAssignableCauseThrowable(throwable,
+ return ExceptionUtils.hasAssignableCause(throwable,
NullPointerException.class, IllegalArgumentException.class, // bug in app
OnErrorNotImplementedException.class, MissingBackpressureException.class,
IllegalStateException.class); // bug in operator
@@ -191,7 +219,7 @@ private ImageLoaderConfiguration getImageLoaderConfigurations(final int memoryCa
private void initACRA() {
try {
final ACRAConfiguration acraConfig = new ConfigurationBuilder(this)
- .setReportSenderFactoryClasses(reportSenderFactoryClasses)
+ .setReportSenderFactoryClasses(REPORT_SENDER_FACTORY_CLASSES)
.setBuildConfigClass(BuildConfig.class)
.build();
ACRA.init(this, acraConfig);
@@ -202,7 +230,7 @@ private void initACRA() {
null,
null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
- "Could not initialize ACRA crash report", R.string.app_ui_crash));
+ "Could not initialize ACRA crash report", R.string.app_ui_crash));
}
}
@@ -230,11 +258,11 @@ public void initNotificationChannel() {
/**
* Set up notification channel for app update.
+ *
* @param importance
*/
@TargetApi(Build.VERSION_CODES.O)
- private void setUpUpdateNotificationChannel(int importance) {
-
+ private void setUpUpdateNotificationChannel(final int importance) {
final String appUpdateId
= getString(R.string.app_update_notification_channel_id);
final CharSequence appUpdateName
@@ -251,12 +279,6 @@ private void setUpUpdateNotificationChannel(int importance) {
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
}
- @Nullable
- public static RefWatcher getRefWatcher(Context context) {
- final App application = (App) context.getApplicationContext();
- return application.refWatcher;
- }
-
protected RefWatcher installLeakCanary() {
return RefWatcher.DISABLED;
}
@@ -264,8 +286,4 @@ protected RefWatcher installLeakCanary() {
protected boolean isDisposedRxExceptionsReported() {
return false;
}
-
- public static App getApp() {
- return app;
- }
}
diff --git a/app/src/main/java/org/schabi/newpipe/BaseFragment.java b/app/src/main/java/org/schabi/newpipe/BaseFragment.java
index d4795cde272..9a86fd5adfb 100644
--- a/app/src/main/java/org/schabi/newpipe/BaseFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/BaseFragment.java
@@ -2,12 +2,13 @@
import android.content.Context;
import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+
import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
-import androidx.appcompat.app.AppCompatActivity;
-import android.util.Log;
-import android.view.View;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.squareup.leakcanary.RefWatcher;
@@ -16,18 +17,16 @@
import icepick.State;
public abstract class BaseFragment extends Fragment {
+ public static final ImageLoader IMAGE_LOADER = ImageLoader.getInstance();
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
protected final boolean DEBUG = MainActivity.DEBUG;
-
protected AppCompatActivity activity;
- public static final ImageLoader imageLoader = ImageLoader.getInstance();
-
- //These values are used for controlling framgents when they are part of the frontpage
+ //These values are used for controlling fragments when they are part of the frontpage
@State
protected boolean useAsFrontPage = false;
- protected boolean mIsVisibleToUser = false;
+ private boolean mIsVisibleToUser = false;
- public void useAsFrontPage(boolean value) {
+ public void useAsFrontPage(final boolean value) {
useAsFrontPage = value;
}
@@ -36,7 +35,7 @@ public void useAsFrontPage(boolean value) {
//////////////////////////////////////////////////////////////////////////*/
@Override
- public void onAttach(Context context) {
+ public void onAttach(final Context context) {
super.onAttach(context);
activity = (AppCompatActivity) context;
}
@@ -48,43 +47,51 @@ public void onDetach() {
}
@Override
- public void onCreate(Bundle savedInstanceState) {
- if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
+ public void onCreate(final Bundle savedInstanceState) {
+ if (DEBUG) {
+ Log.d(TAG, "onCreate() called with: "
+ + "savedInstanceState = [" + savedInstanceState + "]");
+ }
super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
- if (savedInstanceState != null) onRestoreInstanceState(savedInstanceState);
+ if (savedInstanceState != null) {
+ onRestoreInstanceState(savedInstanceState);
+ }
}
@Override
- public void onViewCreated(View rootView, Bundle savedInstanceState) {
+ public void onViewCreated(final View rootView, final Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState);
if (DEBUG) {
- Log.d(TAG, "onViewCreated() called with: rootView = [" + rootView + "], savedInstanceState = [" + savedInstanceState + "]");
+ Log.d(TAG, "onViewCreated() called with: "
+ + "rootView = [" + rootView + "], "
+ + "savedInstanceState = [" + savedInstanceState + "]");
}
initViews(rootView, savedInstanceState);
initListeners();
}
@Override
- public void onSaveInstanceState(Bundle outState) {
+ public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
}
- protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
- }
+ protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) { }
@Override
public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = App.getRefWatcher(getActivity());
- if (refWatcher != null) refWatcher.watch(this);
+ if (refWatcher != null) {
+ refWatcher.watch(this);
+ }
}
@Override
- public void setUserVisibleHint(boolean isVisibleToUser) {
+ public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
mIsVisibleToUser = isVisibleToUser;
}
@@ -93,20 +100,20 @@ public void setUserVisibleHint(boolean isVisibleToUser) {
// Init
//////////////////////////////////////////////////////////////////////////*/
- protected void initViews(View rootView, Bundle savedInstanceState) {
- }
+ protected void initViews(final View rootView, final Bundle savedInstanceState) { }
- protected void initListeners() {
- }
+ protected void initListeners() { }
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
- public void setTitle(String title) {
- if (DEBUG) Log.d(TAG, "setTitle() called with: title = [" + title + "]");
- if((!useAsFrontPage || mIsVisibleToUser)
- && (activity != null && activity.getSupportActionBar() != null)) {
+ public void setTitle(final String title) {
+ if (DEBUG) {
+ Log.d(TAG, "setTitle() called with: title = [" + title + "]");
+ }
+ if ((!useAsFrontPage || mIsVisibleToUser)
+ && (activity != null && activity.getSupportActionBar() != null)) {
activity.getSupportActionBar().setDisplayShowTitleEnabled(true);
activity.getSupportActionBar().setTitle(title);
}
diff --git a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java
index 22f7bc55862..625f514e983 100644
--- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java
+++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java
@@ -12,12 +12,16 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
+import android.util.Log;
+
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
-import android.util.Log;
-import org.json.JSONException;
-import org.json.JSONObject;
+import com.grack.nanojson.JsonObject;
+import com.grack.nanojson.JsonParser;
+import com.grack.nanojson.JsonParserException;
+
+import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
@@ -30,11 +34,6 @@
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
-import java.util.concurrent.TimeUnit;
-
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.Response;
/**
* AsyncTask to check if there is a newer version of the NewPipe github apk available or not.
@@ -42,80 +41,137 @@
* the notification, the user will be directed to the download link.
*/
public class CheckForNewAppVersionTask extends AsyncTask {
-
private static final boolean DEBUG = MainActivity.DEBUG;
private static final String TAG = CheckForNewAppVersionTask.class.getSimpleName();
- private static final Application app = App.getApp();
- private static final String GITHUB_APK_SHA1 = "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
- private static final String newPipeApiUrl = "https://newpipe.schabi.org/api/data.json";
- private static final int timeoutPeriod = 30;
- private SharedPreferences mPrefs;
- private OkHttpClient client;
+ private static final Application APP = App.getApp();
+ private static final String GITHUB_APK_SHA1
+ = "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
+ private static final String NEWPIPE_API_URL = "https://newpipe.schabi.org/api/data.json";
+
+ /**
+ * Method to get the apk's SHA1 key. See https://stackoverflow.com/questions/9293019/#22506133.
+ *
+ * @return String with the apk's SHA1 fingeprint in hexadecimal
+ */
+ private static String getCertificateSHA1Fingerprint() {
+ final PackageManager pm = APP.getPackageManager();
+ final String packageName = APP.getPackageName();
+ final int flags = PackageManager.GET_SIGNATURES;
+ PackageInfo packageInfo = null;
+
+ try {
+ packageInfo = pm.getPackageInfo(packageName, flags);
+ } catch (PackageManager.NameNotFoundException e) {
+ ErrorActivity.reportError(APP, e, null, null,
+ ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
+ "Could not find package info", R.string.app_ui_crash));
+ }
+
+ final Signature[] signatures = packageInfo.signatures;
+ final byte[] cert = signatures[0].toByteArray();
+ final InputStream input = new ByteArrayInputStream(cert);
+
+ X509Certificate c = null;
+
+ try {
+ final CertificateFactory cf = CertificateFactory.getInstance("X509");
+ c = (X509Certificate) cf.generateCertificate(input);
+ } catch (CertificateException e) {
+ ErrorActivity.reportError(APP, e, null, null,
+ ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
+ "Certificate error", R.string.app_ui_crash));
+ }
+
+ String hexString = null;
+
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA1");
+ final byte[] publicKey = md.digest(c.getEncoded());
+ hexString = byte2HexFormatted(publicKey);
+ } catch (NoSuchAlgorithmException | CertificateEncodingException e) {
+ ErrorActivity.reportError(APP, e, null, null,
+ ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
+ "Could not retrieve SHA1 key", R.string.app_ui_crash));
+ }
+
+ return hexString;
+ }
+
+ private static String byte2HexFormatted(final byte[] arr) {
+ final StringBuilder str = new StringBuilder(arr.length * 2);
+
+ for (int i = 0; i < arr.length; i++) {
+ String h = Integer.toHexString(arr[i]);
+ final int l = h.length();
+ if (l == 1) {
+ h = "0" + h;
+ }
+ if (l > 2) {
+ h = h.substring(l - 2, l);
+ }
+ str.append(h.toUpperCase());
+ if (i < (arr.length - 1)) {
+ str.append(':');
+ }
+ }
+ return str.toString();
+ }
+
+ public static boolean isGithubApk() {
+ return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1);
+ }
@Override
protected void onPreExecute() {
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(APP);
- mPrefs = PreferenceManager.getDefaultSharedPreferences(app);
-
- // Check if user has enabled/ disabled update checking
+ // Check if user has enabled/disabled update checking
// and if the current apk is a github one or not.
- if (!mPrefs.getBoolean(app.getString(R.string.update_app_key), true)
- || !isGithubApk()) {
+ if (!prefs.getBoolean(APP.getString(R.string.update_app_key), true) || !isGithubApk()) {
this.cancel(true);
}
}
@Override
- protected String doInBackground(Void... voids) {
-
- if(isCancelled() || !isConnected()) return null;
-
- // Make a network request to get latest NewPipe data.
- if (client == null) {
-
- client = new OkHttpClient
- .Builder()
- .readTimeout(timeoutPeriod, TimeUnit.SECONDS)
- .build();
+ protected String doInBackground(final Void... voids) {
+ if (isCancelled() || !isConnected()) {
+ return null;
}
- Request request = new Request.Builder()
- .url(newPipeApiUrl)
- .build();
-
+ // Make a network request to get latest NewPipe data.
try {
- Response response = client.newCall(request).execute();
- return response.body().string();
- } catch (IOException ex) {
+ return DownloaderImpl.getInstance().get(NEWPIPE_API_URL).responseBody();
+ } catch (IOException | ReCaptchaException e) {
// connectivity problems, do not alarm user and fail silently
- if (DEBUG) Log.w(TAG, Log.getStackTraceString(ex));
+ if (DEBUG) {
+ Log.w(TAG, Log.getStackTraceString(e));
+ }
}
return null;
}
@Override
- protected void onPostExecute(String response) {
-
+ protected void onPostExecute(final String response) {
// Parse the json from the response.
if (response != null) {
try {
- JSONObject mainObject = new JSONObject(response);
- JSONObject flavoursObject = mainObject.getJSONObject("flavors");
- JSONObject githubObject = flavoursObject.getJSONObject("github");
- JSONObject githubStableObject = githubObject.getJSONObject("stable");
+ final JsonObject githubStableObject = JsonParser.object().from(response)
+ .getObject("flavors").getObject("github").getObject("stable");
- String versionName = githubStableObject.getString("version");
- String versionCode = githubStableObject.getString("version_code");
- String apkLocationUrl = githubStableObject.getString("apk");
+ final String versionName = githubStableObject.getString("version");
+ final int versionCode = githubStableObject.getInt("version_code");
+ final String apkLocationUrl = githubStableObject.getString("apk");
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode);
- } catch (JSONException ex) {
+ } catch (JsonParserException e) {
// connectivity problems, do not alarm user and fail silently
- if (DEBUG) Log.w(TAG, Log.getStackTraceString(ex));
+ if (DEBUG) {
+ Log.w(TAG, Log.getStackTraceString(e));
+ }
}
}
}
@@ -123,116 +179,43 @@ protected void onPostExecute(String response) {
/**
* Method to compare the current and latest available app version.
* If a newer version is available, we show the update notification.
- * @param versionName
- * @param apkLocationUrl
+ *
+ * @param versionName Name of new version
+ * @param apkLocationUrl Url with the new apk
+ * @param versionCode Code of new version
*/
- private void compareAppVersionAndShowNotification(String versionName,
- String apkLocationUrl,
- String versionCode) {
-
- int NOTIFICATION_ID = 2000;
+ private void compareAppVersionAndShowNotification(final String versionName,
+ final String apkLocationUrl,
+ final int versionCode) {
+ int notificationId = 2000;
- if (BuildConfig.VERSION_CODE < Integer.valueOf(versionCode)) {
+ if (BuildConfig.VERSION_CODE < versionCode) {
// A pending intent to open the apk location url in the browser.
- Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
- PendingIntent pendingIntent
- = PendingIntent.getActivity(app, 0, intent, 0);
+ final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
+ final PendingIntent pendingIntent
+ = PendingIntent.getActivity(APP, 0, intent, 0);
- NotificationCompat.Builder notificationBuilder = new NotificationCompat
- .Builder(app, app.getString(R.string.app_update_notification_channel_id))
+ final NotificationCompat.Builder notificationBuilder = new NotificationCompat
+ .Builder(APP, APP.getString(R.string.app_update_notification_channel_id))
.setSmallIcon(R.drawable.ic_newpipe_update)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
- .setContentTitle(app.getString(R.string.app_update_notification_content_title))
- .setContentText(app.getString(R.string.app_update_notification_content_text)
+ .setContentTitle(APP.getString(R.string.app_update_notification_content_title))
+ .setContentText(APP.getString(R.string.app_update_notification_content_text)
+ " " + versionName);
- NotificationManagerCompat notificationManager = NotificationManagerCompat.from(app);
- notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
+ final NotificationManagerCompat notificationManager
+ = NotificationManagerCompat.from(APP);
+ notificationManager.notify(notificationId, notificationBuilder.build());
}
}
- /**
- * Method to get the apk's SHA1 key.
- * https://stackoverflow.com/questions/9293019/get-certificate-fingerprint-from-android-app#22506133
- */
- private static String getCertificateSHA1Fingerprint() {
-
- PackageManager pm = app.getPackageManager();
- String packageName = app.getPackageName();
- int flags = PackageManager.GET_SIGNATURES;
- PackageInfo packageInfo = null;
-
- try {
- packageInfo = pm.getPackageInfo(packageName, flags);
- } catch (PackageManager.NameNotFoundException ex) {
- ErrorActivity.reportError(app, ex, null, null,
- ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
- "Could not find package info", R.string.app_ui_crash));
- }
-
- Signature[] signatures = packageInfo.signatures;
- byte[] cert = signatures[0].toByteArray();
- InputStream input = new ByteArrayInputStream(cert);
-
- CertificateFactory cf = null;
- X509Certificate c = null;
-
- try {
- cf = CertificateFactory.getInstance("X509");
- c = (X509Certificate) cf.generateCertificate(input);
- } catch (CertificateException ex) {
- ErrorActivity.reportError(app, ex, null, null,
- ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
- "Certificate error", R.string.app_ui_crash));
- }
-
- String hexString = null;
-
- try {
- MessageDigest md = MessageDigest.getInstance("SHA1");
- byte[] publicKey = md.digest(c.getEncoded());
- hexString = byte2HexFormatted(publicKey);
- } catch (NoSuchAlgorithmException ex1) {
- ErrorActivity.reportError(app, ex1, null, null,
- ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
- "Could not retrieve SHA1 key", R.string.app_ui_crash));
- } catch (CertificateEncodingException ex2) {
- ErrorActivity.reportError(app, ex2, null, null,
- ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
- "Could not retrieve SHA1 key", R.string.app_ui_crash));
- }
-
- return hexString;
- }
-
- private static String byte2HexFormatted(byte[] arr) {
-
- StringBuilder str = new StringBuilder(arr.length * 2);
-
- for (int i = 0; i < arr.length; i++) {
- String h = Integer.toHexString(arr[i]);
- int l = h.length();
- if (l == 1) h = "0" + h;
- if (l > 2) h = h.substring(l - 2, l);
- str.append(h.toUpperCase());
- if (i < (arr.length - 1)) str.append(':');
- }
- return str.toString();
- }
-
- public static boolean isGithubApk() {
-
- return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1);
- }
-
private boolean isConnected() {
-
- ConnectivityManager cm =
- (ConnectivityManager) app.getSystemService(Context.CONNECTIVITY_SERVICE);
+ final ConnectivityManager cm =
+ (ConnectivityManager) APP.getSystemService(Context.CONNECTIVITY_SERVICE);
return cm.getActiveNetworkInfo() != null
- && cm.getActiveNetworkInfo().isConnected();
+ && cm.getActiveNetworkInfo().isConnected();
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java
index 8c551d2a71c..ed517f160a0 100644
--- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java
+++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java
@@ -3,6 +3,9 @@
import android.os.Build;
import android.text.TextUtils;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.extractor.downloader.Request;
import org.schabi.newpipe.extractor.downloader.Response;
@@ -26,9 +29,6 @@
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
import okhttp3.CipherSuite;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
@@ -37,20 +37,22 @@
import static org.schabi.newpipe.MainActivity.DEBUG;
-public class DownloaderImpl extends Downloader {
- public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0";
+public final class DownloaderImpl extends Downloader {
+ public static final String USER_AGENT
+ = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0";
private static DownloaderImpl instance;
private String mCookies;
private OkHttpClient client;
- private DownloaderImpl(OkHttpClient.Builder builder) {
+ private DownloaderImpl(final OkHttpClient.Builder builder) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
enableModernTLS(builder);
}
this.client = builder
.readTimeout(30, TimeUnit.SECONDS)
- //.cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"), 16 * 1024 * 1024))
+// .cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"),
+// 16 * 1024 * 1024))
.build();
}
@@ -58,20 +60,72 @@ private DownloaderImpl(OkHttpClient.Builder builder) {
* It's recommended to call exactly once in the entire lifetime of the application.
*
* @param builder if null, default builder will be used
+ * @return a new instance of {@link DownloaderImpl}
*/
- public static DownloaderImpl init(@Nullable OkHttpClient.Builder builder) {
- return instance = new DownloaderImpl(builder != null ? builder : new OkHttpClient.Builder());
+ public static DownloaderImpl init(@Nullable final OkHttpClient.Builder builder) {
+ instance = new DownloaderImpl(
+ builder != null ? builder : new OkHttpClient.Builder());
+ return instance;
}
public static DownloaderImpl getInstance() {
return instance;
}
+ /**
+ * Enable TLS 1.2 and 1.1 on Android Kitkat. This function is mostly taken
+ * from the documentation of OkHttpClient.Builder.sslSocketFactory(_,_).
+ *
+ * If there is an error, the function will safely fall back to doing nothing
+ * and printing the error to the console.
+ *
+ *
+ * @param builder The HTTPClient Builder on which TLS is enabled on (will be modified in-place)
+ */
+ private static void enableModernTLS(final OkHttpClient.Builder builder) {
+ try {
+ // get the default TrustManager
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+ trustManagerFactory.init((KeyStore) null);
+ TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
+ if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
+ throw new IllegalStateException("Unexpected default trust managers:"
+ + Arrays.toString(trustManagers));
+ }
+ X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
+
+ // insert our own TLSSocketFactory
+ SSLSocketFactory sslSocketFactory = TLSSocketFactoryCompat.getInstance();
+
+ builder.sslSocketFactory(sslSocketFactory, trustManager);
+
+ // This will try to enable all modern CipherSuites(+2 more)
+ // that are supported on the device.
+ // Necessary because some servers (e.g. Framatube.org)
+ // don't support the old cipher suites.
+ // https://github.com/square/okhttp/issues/4053#issuecomment-402579554
+ List cipherSuites = new ArrayList<>();
+ cipherSuites.addAll(ConnectionSpec.MODERN_TLS.cipherSuites());
+ cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA);
+ cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA);
+ ConnectionSpec legacyTLS = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
+ .cipherSuites(cipherSuites.toArray(new CipherSuite[0]))
+ .build();
+
+ builder.connectionSpecs(Arrays.asList(legacyTLS, ConnectionSpec.CLEARTEXT));
+ } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
+ if (DEBUG) {
+ e.printStackTrace();
+ }
+ }
+ }
+
public String getCookies() {
return mCookies;
}
- public void setCookies(String cookies) {
+ public void setCookies(final String cookies) {
mCookies = cookies;
}
@@ -81,7 +135,7 @@ public void setCookies(String cookies) {
* @param url an url pointing to the content
* @return the size of the content, in bytes
*/
- public long getContentLength(String url) throws IOException {
+ public long getContentLength(final String url) throws IOException {
try {
final Response response = head(url);
return Long.parseLong(response.getHeader("Content-Length"));
@@ -92,7 +146,7 @@ public long getContentLength(String url) throws IOException {
}
}
- public InputStream stream(String siteUrl) throws IOException {
+ public InputStream stream(final String siteUrl) throws IOException {
try {
final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder()
.method("GET", null).url(siteUrl)
@@ -122,7 +176,8 @@ public InputStream stream(String siteUrl) throws IOException {
}
@Override
- public Response execute(@NonNull Request request) throws IOException, ReCaptchaException {
+ public Response execute(@NonNull final Request request)
+ throws IOException, ReCaptchaException {
final String httpMethod = request.httpMethod();
final String url = request.url();
final Map> headers = request.headers();
@@ -172,49 +227,7 @@ public Response execute(@NonNull Request request) throws IOException, ReCaptchaE
}
final String latestUrl = response.request().url().toString();
- return new Response(response.code(), response.message(), response.headers().toMultimap(), responseBodyToReturn, latestUrl);
- }
-
- /**
- * Enable TLS 1.2 and 1.1 on Android Kitkat. This function is mostly taken from the documentation of
- * OkHttpClient.Builder.sslSocketFactory(_,_)
- *
- * If there is an error, the function will safely fall back to doing nothing and printing the error to the console.
- *
- * @param builder The HTTPClient Builder on which TLS is enabled on (will be modified in-place)
- */
- private static void enableModernTLS(OkHttpClient.Builder builder) {
- try {
- // get the default TrustManager
- TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
- TrustManagerFactory.getDefaultAlgorithm());
- trustManagerFactory.init((KeyStore) null);
- TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
- if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
- throw new IllegalStateException("Unexpected default trust managers:"
- + Arrays.toString(trustManagers));
- }
- X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
-
- // insert our own TLSSocketFactory
- SSLSocketFactory sslSocketFactory = TLSSocketFactoryCompat.getInstance();
-
- builder.sslSocketFactory(sslSocketFactory, trustManager);
-
- // This will try to enable all modern CipherSuites(+2 more) that are supported on the device.
- // Necessary because some servers (e.g. Framatube.org) don't support the old cipher suites.
- // https://github.com/square/okhttp/issues/4053#issuecomment-402579554
- List cipherSuites = new ArrayList<>();
- cipherSuites.addAll(ConnectionSpec.MODERN_TLS.cipherSuites());
- cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA);
- cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA);
- ConnectionSpec legacyTLS = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
- .cipherSuites(cipherSuites.toArray(new CipherSuite[0]))
- .build();
-
- builder.connectionSpecs(Arrays.asList(legacyTLS, ConnectionSpec.CLEARTEXT));
- } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
- if (DEBUG) e.printStackTrace();
- }
+ return new Response(response.code(), response.message(), response.headers().toMultimap(),
+ responseBodyToReturn, latestUrl);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/ExitActivity.java b/app/src/main/java/org/schabi/newpipe/ExitActivity.java
index 1ea3abe34b8..94eff956064 100644
--- a/app/src/main/java/org/schabi/newpipe/ExitActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/ExitActivity.java
@@ -1,4 +1,3 @@
-
package org.schabi.newpipe;
import android.annotation.SuppressLint;
@@ -27,9 +26,20 @@
public class ExitActivity extends Activity {
+ public static void exitAndRemoveFromRecentApps(final Activity activity) {
+ Intent intent = new Intent(activity, ExitActivity.class);
+
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK
+ | Intent.FLAG_ACTIVITY_NO_ANIMATION);
+
+ activity.startActivity(intent);
+ }
+
@SuppressLint("NewApi")
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= 21) {
@@ -40,15 +50,4 @@ protected void onCreate(Bundle savedInstanceState) {
System.exit(0);
}
-
- public static void exitAndRemoveFromRecentApps(Activity activity) {
- Intent intent = new Intent(activity, ExitActivity.class);
-
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
- | Intent.FLAG_ACTIVITY_CLEAR_TASK
- | Intent.FLAG_ACTIVITY_NO_ANIMATION);
-
- activity.startActivity(intent);
- }
}
diff --git a/app/src/main/java/org/schabi/newpipe/ImageDownloader.java b/app/src/main/java/org/schabi/newpipe/ImageDownloader.java
index dfb7d3276d1..ca61c965587 100644
--- a/app/src/main/java/org/schabi/newpipe/ImageDownloader.java
+++ b/app/src/main/java/org/schabi/newpipe/ImageDownloader.java
@@ -18,7 +18,7 @@ public class ImageDownloader extends BaseImageDownloader {
private final SharedPreferences preferences;
private final String downloadThumbnailKey;
- public ImageDownloader(Context context) {
+ public ImageDownloader(final Context context) {
super(context);
this.resources = context.getResources();
this.preferences = PreferenceManager.getDefaultSharedPreferences(context);
@@ -31,7 +31,7 @@ private boolean isDownloadingThumbnail() {
@SuppressLint("ResourceType")
@Override
- public InputStream getStream(String imageUri, Object extra) throws IOException {
+ public InputStream getStream(final String imageUri, final Object extra) throws IOException {
if (isDownloadingThumbnail()) {
return super.getStream(imageUri, extra);
} else {
@@ -39,7 +39,8 @@ public InputStream getStream(String imageUri, Object extra) throws IOException {
}
}
- protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
+ protected InputStream getStreamFromNetwork(final String imageUri, final Object extra)
+ throws IOException {
final DownloaderImpl downloader = (DownloaderImpl) NewPipe.getDownloader();
return downloader.stream(imageUri);
}
diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
index 4ca16082a55..6f1862f319b 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -33,6 +33,7 @@
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
+import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
@@ -63,6 +64,7 @@
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.fragments.list.search.SearchFragment;
import org.schabi.newpipe.report.ErrorActivity;
+import org.schabi.newpipe.util.AndroidTvUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.Localization;
@@ -73,6 +75,7 @@
import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.TLSSocketFactoryCompat;
import org.schabi.newpipe.util.ThemeHelper;
+import org.schabi.newpipe.views.FocusOverlayView;
import java.util.ArrayList;
import java.util.List;
@@ -93,11 +96,11 @@ public class MainActivity extends AppCompatActivity {
private boolean servicesShown = false;
private ImageView serviceArrow;
- private static final int ITEM_ID_SUBSCRIPTIONS = - 1;
- private static final int ITEM_ID_FEED = - 2;
- private static final int ITEM_ID_BOOKMARKS = - 3;
- private static final int ITEM_ID_DOWNLOADS = - 4;
- private static final int ITEM_ID_HISTORY = - 5;
+ private static final int ITEM_ID_SUBSCRIPTIONS = -1;
+ private static final int ITEM_ID_FEED = -2;
+ private static final int ITEM_ID_BOOKMARKS = -3;
+ private static final int ITEM_ID_DOWNLOADS = -4;
+ private static final int ITEM_ID_HISTORY = -5;
private static final int ITEM_ID_SETTINGS = 0;
private static final int ITEM_ID_ABOUT = 1;
@@ -108,8 +111,11 @@ public class MainActivity extends AppCompatActivity {
//////////////////////////////////////////////////////////////////////////*/
@Override
- protected void onCreate(Bundle savedInstanceState) {
- if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
+ protected void onCreate(final Bundle savedInstanceState) {
+ if (DEBUG) {
+ Log.d(TAG, "onCreate() called with: "
+ + "savedInstanceState = [" + savedInstanceState + "]");
+ }
// enable TLS1.1/1.2 for kitkat devices, to fix download and play for mediaCCC sources
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
@@ -123,10 +129,12 @@ protected void onCreate(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window w = getWindow();
- w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
+ WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
- if (getSupportFragmentManager() != null && getSupportFragmentManager().getBackStackEntryCount() == 0) {
+ if (getSupportFragmentManager() != null
+ && getSupportFragmentManager().getBackStackEntryCount() == 0) {
initFragments();
}
@@ -136,6 +144,10 @@ protected void onCreate(Bundle savedInstanceState) {
} catch (Exception e) {
ErrorActivity.reportUiError(this, e);
}
+
+ if (AndroidTvUtils.isTv(this)) {
+ FocusOverlayView.setupFocusObserver(this);
+ }
}
private void setupDrawer() throws Exception {
@@ -151,13 +163,15 @@ private void setupDrawer() throws Exception {
for (final String ks : service.getKioskList().getAvailableKiosks()) {
drawerItems.getMenu()
- .add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator.getTranslatedKioskName(ks, this))
+ .add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator
+ .getTranslatedKioskName(ks, this))
.setIcon(KioskTranslator.getKioskIcons(ks, this));
- kioskId ++;
+ kioskId++;
}
drawerItems.getMenu()
- .add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER, R.string.tab_subscriptions)
+ .add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER,
+ R.string.tab_subscriptions)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
@@ -180,20 +194,21 @@ private void setupDrawer() throws Exception {
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info));
- toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open, R.string.drawer_close);
+ toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open,
+ R.string.drawer_close);
toggle.syncState();
drawer.addDrawerListener(toggle);
drawer.addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
private int lastService;
@Override
- public void onDrawerOpened(View drawerView) {
+ public void onDrawerOpened(final View drawerView) {
lastService = ServiceHelper.getSelectedServiceId(MainActivity.this);
}
@Override
- public void onDrawerClosed(View drawerView) {
- if(servicesShown) {
+ public void onDrawerClosed(final View drawerView) {
+ if (servicesShown) {
toggleServices();
}
if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) {
@@ -206,7 +221,7 @@ public void onDrawerClosed(View drawerView) {
setupDrawerHeader();
}
- private boolean drawerItemSelected(MenuItem item) {
+ private boolean drawerItemSelected(final MenuItem item) {
switch (item.getGroupId()) {
case R.id.menu_services_group:
changeService(item);
@@ -229,14 +244,16 @@ private boolean drawerItemSelected(MenuItem item) {
return true;
}
- private void changeService(MenuItem item) {
- drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(false);
+ private void changeService(final MenuItem item) {
+ drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this))
+ .setChecked(false);
ServiceHelper.setSelectedServiceId(this, item.getItemId());
- drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true);
+ drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this))
+ .setChecked(true);
}
- private void tabSelected(MenuItem item) throws ExtractionException {
- switch(item.getItemId()) {
+ private void tabSelected(final MenuItem item) throws ExtractionException {
+ switch (item.getItemId()) {
case ITEM_ID_SUBSCRIPTIONS:
NavigationHelper.openSubscriptionFragment(getSupportFragmentManager());
break;
@@ -259,19 +276,20 @@ private void tabSelected(MenuItem item) throws ExtractionException {
int kioskId = 0;
for (final String ks : service.getKioskList().getAvailableKiosks()) {
- if(kioskId == item.getItemId()) {
+ if (kioskId == item.getItemId()) {
serviceName = ks;
}
- kioskId ++;
+ kioskId++;
}
- NavigationHelper.openKioskFragment(getSupportFragmentManager(), currentServiceId, serviceName);
+ NavigationHelper.openKioskFragment(getSupportFragmentManager(), currentServiceId,
+ serviceName);
break;
}
}
- private void optionsAboutSelected(MenuItem item) {
- switch(item.getItemId()) {
+ private void optionsAboutSelected(final MenuItem item) {
+ switch (item.getItemId()) {
case ITEM_ID_SETTINGS:
NavigationHelper.openSettings(this);
break;
@@ -283,13 +301,27 @@ private void optionsAboutSelected(MenuItem item) {
private void setupDrawerHeader() {
NavigationView navigationView = findViewById(R.id.navigation);
- View hView = navigationView.getHeaderView(0);
+ View hView = navigationView.getHeaderView(0);
serviceArrow = hView.findViewById(R.id.drawer_arrow);
headerServiceIcon = hView.findViewById(R.id.drawer_header_service_icon);
headerServiceView = hView.findViewById(R.id.drawer_header_service_view);
toggleServiceButton = hView.findViewById(R.id.drawer_header_action_button);
toggleServiceButton.setOnClickListener(view -> toggleServices());
+
+ // If the current app name is bigger than the default "NewPipe" (7 chars),
+ // let the text view grow a little more as well.
+ if (getString(R.string.app_name).length() > "NewPipe".length()) {
+ final TextView headerTitle = hView.findViewById(R.id.drawer_header_newpipe_title);
+ final ViewGroup.LayoutParams layoutParams = headerTitle.getLayoutParams();
+ layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+ headerTitle.setLayoutParams(layoutParams);
+ headerTitle.setMaxLines(2);
+ headerTitle.setMinWidth(getResources()
+ .getDimensionPixelSize(R.dimen.drawer_header_newpipe_title_default_width));
+ headerTitle.setMaxWidth(getResources()
+ .getDimensionPixelSize(R.dimen.drawer_header_newpipe_title_max_width));
+ }
}
private void toggleServices() {
@@ -299,7 +331,7 @@ private void toggleServices() {
drawerItems.getMenu().removeGroup(R.id.menu_tabs_group);
drawerItems.getMenu().removeGroup(R.id.menu_options_about_group);
- if(servicesShown) {
+ if (servicesShown) {
showServices();
} else {
try {
@@ -313,55 +345,62 @@ private void toggleServices() {
private void showServices() {
serviceArrow.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp);
- for(StreamingService s : NewPipe.getServices()) {
- final String title = s.getServiceInfo().getName() +
- (ServiceHelper.isBeta(s) ? " (beta)" : "");
+ for (StreamingService s : NewPipe.getServices()) {
+ final String title = s.getServiceInfo().getName()
+ + (ServiceHelper.isBeta(s) ? " (beta)" : "");
MenuItem menuItem = drawerItems.getMenu()
.add(R.id.menu_services_group, s.getServiceId(), ORDER, title)
.setIcon(ServiceHelper.getIcon(s.getServiceId()));
// peertube specifics
- if(s.getServiceId() == 3){
+ if (s.getServiceId() == 3) {
enhancePeertubeMenu(s, menuItem);
}
}
- drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true);
+ drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this))
+ .setChecked(true);
}
- private void enhancePeertubeMenu(StreamingService s, MenuItem menuItem) {
+ private void enhancePeertubeMenu(final StreamingService s, final MenuItem menuItem) {
PeertubeInstance currentInstace = PeertubeHelper.getCurrentInstance();
menuItem.setTitle(currentInstace.getName() + (ServiceHelper.isBeta(s) ? " (beta)" : ""));
- Spinner spinner = (Spinner) LayoutInflater.from(this).inflate(R.layout.instance_spinner_layout, null);
+ Spinner spinner = (Spinner) LayoutInflater.from(this)
+ .inflate(R.layout.instance_spinner_layout, null);
List instances = PeertubeHelper.getInstanceList(this);
List items = new ArrayList<>();
int defaultSelect = 0;
- for(PeertubeInstance instance: instances){
+ for (PeertubeInstance instance : instances) {
items.add(instance.getName());
- if(instance.getUrl().equals(currentInstace.getUrl())){
- defaultSelect = items.size()-1;
+ if (instance.getUrl().equals(currentInstace.getUrl())) {
+ defaultSelect = items.size() - 1;
}
}
- ArrayAdapter adapter = new ArrayAdapter<>(this, R.layout.instance_spinner_item, items);
+ ArrayAdapter adapter = new ArrayAdapter<>(this,
+ R.layout.instance_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.setSelection(defaultSelect, false);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
- public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ public void onItemSelected(final AdapterView> parent, final View view,
+ final int position, final long id) {
PeertubeInstance newInstance = instances.get(position);
- if(newInstance.getUrl().equals(PeertubeHelper.getCurrentInstance().getUrl())) return;
+ if (newInstance.getUrl().equals(PeertubeHelper.getCurrentInstance().getUrl())) {
+ return;
+ }
PeertubeHelper.selectInstance(newInstance, getApplicationContext());
changeService(menuItem);
drawer.closeDrawers();
new Handler(Looper.getMainLooper()).postDelayed(() -> {
- getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ getSupportFragmentManager().popBackStack(null,
+ FragmentManager.POP_BACK_STACK_INCLUSIVE);
recreate();
}, 300);
}
@Override
- public void onNothingSelected(AdapterView> parent) {
+ public void onNothingSelected(final AdapterView> parent) {
}
});
@@ -379,9 +418,10 @@ private void showTabs() throws ExtractionException {
for (final String ks : service.getKioskList().getAvailableKiosks()) {
drawerItems.getMenu()
- .add(R.id.menu_tabs_group, kioskId, ORDER, KioskTranslator.getTranslatedKioskName(ks, this))
+ .add(R.id.menu_tabs_group, kioskId, ORDER,
+ KioskTranslator.getTranslatedKioskName(ks, this))
.setIcon(KioskTranslator.getKioskIcons(ks, this));
- kioskId ++;
+ kioskId++;
}
drawerItems.getMenu()
@@ -420,15 +460,17 @@ protected void onDestroy() {
@Override
protected void onResume() {
assureCorrectAppLanguage(this);
- Localization.init(getApplicationContext()); //change the date format to match the selected language on resume
+ // Change the date format to match the selected language on resume
+ Localization.init(getApplicationContext());
super.onResume();
- // close drawer on return, and don't show animation, so its looks like the drawer isn't open
- // when the user returns to MainActivity
+ // Close drawer on return, and don't show animation,
+ // so it looks like the drawer isn't open when the user returns to MainActivity
drawer.closeDrawer(GravityCompat.START, false);
try {
final int selectedServiceId = ServiceHelper.getSelectedServiceId(this);
- final String selectedServiceName = NewPipe.getService(selectedServiceId).getServiceInfo().getName();
+ final String selectedServiceName = NewPipe.getService(selectedServiceId)
+ .getServiceInfo().getName();
headerServiceView.setText(selectedServiceName);
headerServiceIcon.setImageResource(ServiceHelper.getIcon(selectedServiceId));
@@ -441,15 +483,20 @@ protected void onResume() {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) {
- if (DEBUG) Log.d(TAG, "Theme has changed, recreating activity...");
+ if (DEBUG) {
+ Log.d(TAG, "Theme has changed, recreating activity...");
+ }
sharedPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, false).apply();
- // https://stackoverflow.com/questions/10844112/runtimeexception-performing-pause-of-activity-that-is-not-resumed
- // Briefly, let the activity resume properly posting the recreate call to end of the message queue
+ // https://stackoverflow.com/questions/10844112/
+ // Briefly, let the activity resume
+ // properly posting the recreate call to end of the message queue
new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate);
}
if (sharedPreferences.getBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false)) {
- if (DEBUG) Log.d(TAG, "main page has changed, recreating main fragment...");
+ if (DEBUG) {
+ Log.d(TAG, "main page has changed, recreating main fragment...");
+ }
sharedPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false).apply();
NavigationHelper.openMainActivity(this);
}
@@ -460,13 +507,18 @@ protected void onResume() {
}
@Override
- protected void onNewIntent(Intent intent) {
- if (DEBUG) Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]");
+ protected void onNewIntent(final Intent intent) {
+ if (DEBUG) {
+ Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]");
+ }
if (intent != null) {
// Return if launched from a launcher (e.g. Nova Launcher, Pixel Launcher ...)
// to not destroy the already created backstack
String action = intent.getAction();
- if ((action != null && action.equals(Intent.ACTION_MAIN)) && intent.hasCategory(Intent.CATEGORY_LAUNCHER)) return;
+ if ((action != null && action.equals(Intent.ACTION_MAIN))
+ && intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
+ return;
+ }
}
super.onNewIntent(intent);
@@ -476,24 +528,40 @@ protected void onNewIntent(Intent intent) {
@Override
public void onBackPressed() {
- if (DEBUG) Log.d(TAG, "onBackPressed() called");
+ if (DEBUG) {
+ Log.d(TAG, "onBackPressed() called");
+ }
+
+ if (AndroidTvUtils.isTv(this)) {
+ View drawerPanel = findViewById(R.id.navigation);
+ if (drawer.isDrawerOpen(drawerPanel)) {
+ drawer.closeDrawers();
+ return;
+ }
+ }
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
- // If current fragment implements BackPressable (i.e. can/wanna handle back press) delegate the back press to it
+ // If current fragment implements BackPressable (i.e. can/wanna handle back press)
+ // delegate the back press to it
if (fragment instanceof BackPressable) {
- if (((BackPressable) fragment).onBackPressed()) return;
+ if (((BackPressable) fragment).onBackPressed()) {
+ return;
+ }
}
-
if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
finish();
- } else super.onBackPressed();
+ } else {
+ super.onBackPressed();
+ }
}
@Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- for (int i: grantResults){
- if (i == PackageManager.PERMISSION_DENIED){
+ public void onRequestPermissionsResult(final int requestCode,
+ @NonNull final String[] permissions,
+ @NonNull final int[] grantResults) {
+ for (int i : grantResults) {
+ if (i == PackageManager.PERMISSION_DENIED) {
return;
}
}
@@ -502,7 +570,8 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis
NavigationHelper.openDownloads(this);
break;
case PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE:
- Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
+ Fragment fragment = getSupportFragmentManager()
+ .findFragmentById(R.id.fragment_holder);
if (fragment instanceof VideoDetailFragment) {
((VideoDetailFragment) fragment).openDownloadDialog();
}
@@ -547,8 +616,10 @@ private void onHomeButtonPressed() {
//////////////////////////////////////////////////////////////////////////*/
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
- if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "]");
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ if (DEBUG) {
+ Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "]");
+ }
super.onCreateOptionsMenu(menu);
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
@@ -557,8 +628,8 @@ public boolean onCreateOptionsMenu(Menu menu) {
}
if (!(fragment instanceof SearchFragment)) {
- findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container).setVisibility(View.GONE);
-
+ findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container)
+ .setVisibility(View.GONE);
}
ActionBar actionBar = getSupportActionBar();
@@ -572,8 +643,10 @@ public boolean onCreateOptionsMenu(Menu menu) {
}
@Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (DEBUG) Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ if (DEBUG) {
+ Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
+ }
int id = item.getItemId();
switch (id) {
@@ -590,11 +663,15 @@ public boolean onOptionsItemSelected(MenuItem item) {
//////////////////////////////////////////////////////////////////////////*/
private void initFragments() {
- if (DEBUG) Log.d(TAG, "initFragments() called");
+ if (DEBUG) {
+ Log.d(TAG, "initFragments() called");
+ }
StateSaver.clearStateFiles();
if (getIntent() != null && getIntent().hasExtra(Constants.KEY_LINK_TYPE)) {
handleIntent(getIntent());
- } else NavigationHelper.gotoMainFragment(getSupportFragmentManager());
+ } else {
+ NavigationHelper.gotoMainFragment(getSupportFragmentManager());
+ }
}
/*//////////////////////////////////////////////////////////////////////////
@@ -602,12 +679,14 @@ private void initFragments() {
//////////////////////////////////////////////////////////////////////////*/
private void updateDrawerNavigation() {
- if (getSupportActionBar() == null) return;
+ if (getSupportActionBar() == null) {
+ return;
+ }
final Toolbar toolbar = findViewById(R.id.toolbar);
- final DrawerLayout drawer = findViewById(R.id.drawer_layout);
- final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
+ final Fragment fragment = getSupportFragmentManager()
+ .findFragmentById(R.id.fragment_holder);
if (fragment instanceof MainFragment) {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
if (toggle != null) {
@@ -622,26 +701,23 @@ private void updateDrawerNavigation() {
}
}
- private void updateDrawerHeaderString(String content) {
- NavigationView navigationView = findViewById(R.id.navigation);
- View hView = navigationView.getHeaderView(0);
- Button action = hView.findViewById(R.id.drawer_header_action_button);
-
- action.setContentDescription(content);
- }
-
- private void handleIntent(Intent intent) {
+ private void handleIntent(final Intent intent) {
try {
- if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
+ if (DEBUG) {
+ Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
+ }
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
String url = intent.getStringExtra(Constants.KEY_URL);
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
String title = intent.getStringExtra(Constants.KEY_TITLE);
- switch (((StreamingService.LinkType) intent.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
+ switch (((StreamingService.LinkType) intent
+ .getSerializableExtra(Constants.KEY_LINK_TYPE))) {
case STREAM:
- boolean autoPlay = intent.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
- NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(), serviceId, url, title, autoPlay);
+ boolean autoPlay = intent
+ .getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
+ NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(),
+ serviceId, url, title, autoPlay);
break;
case CHANNEL:
NavigationHelper.openChannelFragment(getSupportFragmentManager(),
@@ -658,7 +734,9 @@ private void handleIntent(Intent intent) {
}
} else if (intent.hasExtra(Constants.KEY_OPEN_SEARCH)) {
String searchString = intent.getStringExtra(Constants.KEY_SEARCH_STRING);
- if (searchString == null) searchString = "";
+ if (searchString == null) {
+ searchString = "";
+ }
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
NavigationHelper.openSearchFragment(
getSupportFragmentManager(),
diff --git a/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java b/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java
index 81b5dd72f2f..c59c48367fb 100644
--- a/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java
+++ b/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java
@@ -13,14 +13,13 @@
import static org.schabi.newpipe.database.Migrations.MIGRATION_2_3;
public final class NewPipeDatabase {
-
private static volatile AppDatabase databaseInstance;
private NewPipeDatabase() {
//no instance
}
- private static AppDatabase getDatabase(Context context) {
+ private static AppDatabase getDatabase(final Context context) {
return Room
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
@@ -28,13 +27,14 @@ private static AppDatabase getDatabase(Context context) {
}
@NonNull
- public static AppDatabase getInstance(@NonNull Context context) {
+ public static AppDatabase getInstance(@NonNull final Context context) {
AppDatabase result = databaseInstance;
if (result == null) {
synchronized (NewPipeDatabase.class) {
result = databaseInstance;
if (result == null) {
- databaseInstance = (result = getDatabase(context));
+ databaseInstance = getDatabase(context);
+ result = databaseInstance;
}
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/PanicResponderActivity.java b/app/src/main/java/org/schabi/newpipe/PanicResponderActivity.java
index 4118070d5cf..2e1abd59877 100644
--- a/app/src/main/java/org/schabi/newpipe/PanicResponderActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/PanicResponderActivity.java
@@ -1,4 +1,3 @@
-
package org.schabi.newpipe;
import android.annotation.SuppressLint;
@@ -26,17 +25,18 @@
*/
public class PanicResponderActivity extends Activity {
-
public static final String PANIC_TRIGGER_ACTION = "info.guardianproject.panic.action.TRIGGER";
@SuppressLint("NewApi")
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) {
- // TODO explicitly clear the search results once they are restored when the app restarts
- // or if the app reloads the current video after being killed, that should be cleared also
+ // TODO: Explicitly clear the search results
+ // once they are restored when the app restarts
+ // or if the app reloads the current video after being killed,
+ // that should be cleared also
ExitActivity.exitAndRemoveFromRecentApps(this);
}
diff --git a/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java b/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java
index 4219638d67b..49fb6b17980 100644
--- a/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java
@@ -1,24 +1,31 @@
package org.schabi.newpipe;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
-import androidx.core.app.NavUtils;
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
-
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.webkit.CookieManager;
+import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.app.NavUtils;
+import androidx.preference.PreferenceManager;
+
import org.schabi.newpipe.util.ThemeHelper;
-import androidx.annotation.NonNull;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
/*
* Created by beneth on 06.12.16.
@@ -49,7 +56,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
private String foundCookies = "";
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(final Bundle savedInstanceState) {
ThemeHelper.setTheme(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recaptcha);
@@ -72,10 +79,33 @@ protected void onCreate(Bundle savedInstanceState) {
webSettings.setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient() {
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ @Override
+ public boolean shouldOverrideUrlLoading(final WebView view,
+ final WebResourceRequest request) {
+ String url = request.getUrl().toString();
+ if (MainActivity.DEBUG) {
+ Log.d(TAG, "shouldOverrideUrlLoading: request.url=" + url);
+ }
+
+ handleCookiesFromUrl(url);
+ return false;
+ }
+
+ @Override
+ public boolean shouldOverrideUrlLoading(final WebView view, final String url) {
+ if (MainActivity.DEBUG) {
+ Log.d(TAG, "shouldOverrideUrlLoading: url=" + url);
+ }
+
+ handleCookiesFromUrl(url);
+ return false;
+ }
+
@Override
- public void onPageFinished(WebView view, String url) {
+ public void onPageFinished(final WebView view, final String url) {
super.onPageFinished(view, url);
- handleCookies(url);
+ handleCookiesFromUrl(url);
}
});
@@ -84,7 +114,8 @@ public void onPageFinished(WebView view, String url) {
webView.clearHistory();
android.webkit.CookieManager cookieManager = CookieManager.getInstance();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- cookieManager.removeAllCookies(aBoolean -> {});
+ cookieManager.removeAllCookies(aBoolean -> {
+ });
} else {
cookieManager.removeAllCookie();
}
@@ -93,7 +124,7 @@ public void onPageFinished(WebView view, String url) {
}
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
+ public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu_recaptcha, menu);
ActionBar actionBar = getSupportActionBar();
@@ -112,7 +143,7 @@ public void onBackPressed() {
}
@Override
- public boolean onOptionsItemSelected(MenuItem item) {
+ public boolean onOptionsItemSelected(final MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.menu_item_done:
@@ -124,8 +155,18 @@ public boolean onOptionsItemSelected(MenuItem item) {
}
private void saveCookiesAndFinish() {
- handleCookies(webView.getUrl()); // try to get cookies of unclosed page
+ handleCookiesFromUrl(webView.getUrl()); // try to get cookies of unclosed page
+ if (MainActivity.DEBUG) {
+ Log.d(TAG, "saveCookiesAndFinish: foundCookies=" + foundCookies);
+ }
+
if (!foundCookies.isEmpty()) {
+ // save cookies to preferences
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
+ getApplicationContext());
+ final String key = getApplicationContext().getString(R.string.recaptcha_cookies_key);
+ prefs.edit().putString(key, foundCookies).apply();
+
// give cookies to Downloader class
DownloaderImpl.getInstance().setCookies(foundCookies);
setResult(RESULT_OK);
@@ -137,24 +178,60 @@ private void saveCookiesAndFinish() {
}
+ private void handleCookiesFromUrl(@Nullable final String url) {
+ if (MainActivity.DEBUG) {
+ Log.d(TAG, "handleCookiesFromUrl: url=" + (url == null ? "null" : url));
+ }
+
+ if (url == null) {
+ return;
+ }
- private void handleCookies(String url) {
String cookies = CookieManager.getInstance().getCookie(url);
- if (MainActivity.DEBUG) Log.d(TAG, "handleCookies: url=" + url + "; cookies=" + (cookies == null ? "null" : cookies));
- if (cookies == null) return;
+ handleCookies(cookies);
+
+ // sometimes cookies are inside the url
+ int abuseStart = url.indexOf("google_abuse=");
+ if (abuseStart != -1) {
+ int abuseEnd = url.indexOf("+path");
+
+ try {
+ String abuseCookie = url.substring(abuseStart + 13, abuseEnd);
+ abuseCookie = URLDecoder.decode(abuseCookie, "UTF-8");
+ handleCookies(abuseCookie);
+ } catch (UnsupportedEncodingException | StringIndexOutOfBoundsException e) {
+ if (MainActivity.DEBUG) {
+ e.printStackTrace();
+ Log.d(TAG, "handleCookiesFromUrl: invalid google abuse starting at "
+ + abuseStart + " and ending at " + abuseEnd + " for url " + url);
+ }
+ }
+ }
+ }
+
+ private void handleCookies(@Nullable final String cookies) {
+ if (MainActivity.DEBUG) {
+ Log.d(TAG, "handleCookies: cookies=" + (cookies == null ? "null" : cookies));
+ }
+
+ if (cookies == null) {
+ return;
+ }
addYoutubeCookies(cookies);
- // add other methods to extract cookies here
+ // add here methods to extract cookies for other services
}
- private void addYoutubeCookies(@NonNull String cookies) {
- if (cookies.contains("s_gl=") || cookies.contains("goojf=") || cookies.contains("VISITOR_INFO1_LIVE=")) {
+ private void addYoutubeCookies(@NonNull final String cookies) {
+ if (cookies.contains("s_gl=") || cookies.contains("goojf=")
+ || cookies.contains("VISITOR_INFO1_LIVE=")
+ || cookies.contains("GOOGLE_ABUSE_EXEMPTION=")) {
// youtube seems to also need the other cookies:
addCookie(cookies);
}
}
- private void addCookie(String cookie) {
+ private void addCookie(final String cookie) {
if (foundCookies.contains(cookie)) {
return;
}
diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
index 1ed659e47fb..c927a910f62 100644
--- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
@@ -45,15 +45,19 @@
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
+import org.schabi.newpipe.util.AndroidTvUtils;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
+import org.schabi.newpipe.views.FocusOverlayView;
import org.schabi.newpipe.util.urlfinder.UrlFinder;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
import icepick.Icepick;
@@ -71,29 +75,31 @@
import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
/**
- * Get the url from the intent and open it in the chosen preferred player
+ * Get the url from the intent and open it in the chosen preferred player.
*/
public class RouterActivity extends AppCompatActivity {
-
+ public static final String INTERNAL_ROUTE_KEY = "internalRoute";
+ /**
+ * Removes invisible separators (\p{Z}) and punctuation characters including
+ * brackets (\p{P}). See http://www.regular-expressions.info/unicode.html for
+ * more details.
+ */
+ private static final String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]";
+ protected final CompositeDisposable disposables = new CompositeDisposable();
@State
protected int currentServiceId = -1;
- private StreamingService currentService;
@State
protected LinkType currentLinkType;
@State
protected int selectedRadioPosition = -1;
protected int selectedPreviously = -1;
-
protected String currentUrl;
protected boolean internalRoute = false;
- protected final CompositeDisposable disposables = new CompositeDisposable();
-
+ private StreamingService currentService;
private boolean selectionIsDownload = false;
- public static final String internalRouteKey = "internalRoute";
-
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
@@ -106,14 +112,14 @@ protected void onCreate(Bundle savedInstanceState) {
}
}
- internalRoute = getIntent().getBooleanExtra(internalRouteKey, false);
+ internalRoute = getIntent().getBooleanExtra(INTERNAL_ROUTE_KEY, false);
setTheme(ThemeHelper.isLightThemeSelected(this)
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
}
@Override
- protected void onSaveInstanceState(Bundle outState) {
+ protected void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
}
@@ -132,7 +138,7 @@ protected void onDestroy() {
disposables.clear();
}
- private void handleUrl(String url) {
+ private void handleUrl(final String url) {
disposables.add(Observable
.fromCallable(() -> {
if (currentServiceId == -1) {
@@ -157,13 +163,14 @@ private void handleUrl(String url) {
}, this::handleError));
}
- private void handleError(Throwable error) {
+ private void handleError(final Throwable error) {
error.printStackTrace();
if (error instanceof ExtractionException) {
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
} else {
- ExtractorHelper.handleGeneralException(this, -1, null, error, UserAction.SOMETHING_ELSE, null);
+ ExtractorHelper.handleGeneralException(this, -1, null, error,
+ UserAction.SOMETHING_ELSE, null);
}
finish();
@@ -175,8 +182,11 @@ private void onError() {
}
protected void onSuccess() {
- final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
- final String selectedChoiceKey = preferences.getString(getString(R.string.preferred_open_action_key), getString(R.string.preferred_open_action_default));
+ final SharedPreferences preferences = PreferenceManager
+ .getDefaultSharedPreferences(this);
+ final String selectedChoiceKey = preferences
+ .getString(getString(R.string.preferred_open_action_key),
+ getString(R.string.preferred_open_action_default));
final String showInfoKey = getString(R.string.show_info_key);
final String videoPlayerKey = getString(R.string.video_player_key);
@@ -186,7 +196,8 @@ protected void onSuccess() {
final String alwaysAskKey = getString(R.string.always_ask_open_action_key);
if (selectedChoiceKey.equals(alwaysAskKey)) {
- final List choices = getChoicesForService(currentService, currentLinkType);
+ final List choices
+ = getChoicesForService(currentService, currentLinkType);
switch (choices.size()) {
case 1:
@@ -204,20 +215,26 @@ protected void onSuccess() {
} else if (selectedChoiceKey.equals(downloadKey)) {
handleChoice(downloadKey);
} else {
- final boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
- final boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
- final boolean isVideoPlayerSelected = selectedChoiceKey.equals(videoPlayerKey) || selectedChoiceKey.equals(popupPlayerKey);
+ final boolean isExtVideoEnabled = preferences.getBoolean(
+ getString(R.string.use_external_video_player_key), false);
+ final boolean isExtAudioEnabled = preferences.getBoolean(
+ getString(R.string.use_external_audio_player_key), false);
+ final boolean isVideoPlayerSelected = selectedChoiceKey.equals(videoPlayerKey)
+ || selectedChoiceKey.equals(popupPlayerKey);
final boolean isAudioPlayerSelected = selectedChoiceKey.equals(backgroundPlayerKey);
if (currentLinkType != LinkType.STREAM) {
- if (isExtAudioEnabled && isAudioPlayerSelected || isExtVideoEnabled && isVideoPlayerSelected) {
- Toast.makeText(this, R.string.external_player_unsupported_link_type, Toast.LENGTH_LONG).show();
+ if (isExtAudioEnabled && isAudioPlayerSelected
+ || isExtVideoEnabled && isVideoPlayerSelected) {
+ Toast.makeText(this, R.string.external_player_unsupported_link_type,
+ Toast.LENGTH_LONG).show();
handleChoice(showInfoKey);
return;
}
}
- final List capabilities = currentService.getServiceInfo().getMediaCapabilities();
+ final List capabilities
+ = currentService.getServiceInfo().getMediaCapabilities();
boolean serviceSupportsChoice = false;
if (isVideoPlayerSelected) {
@@ -239,7 +256,8 @@ private void showDialog(final List choices) {
final Context themeWrapperContext = getThemeWrapperContext();
final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext);
- final LinearLayout rootLayout = (LinearLayout) inflater.inflate(R.layout.preferred_player_dialog_view, null, false);
+ final LinearLayout rootLayout = (LinearLayout) inflater.inflate(
+ R.layout.preferred_player_dialog_view, null, false);
final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list);
final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> {
@@ -250,7 +268,9 @@ private void showDialog(final List choices) {
handleChoice(choice.key);
if (which == DialogInterface.BUTTON_POSITIVE) {
- preferences.edit().putString(getString(R.string.preferred_open_action_key), choice.key).apply();
+ preferences.edit()
+ .putString(getString(R.string.preferred_open_action_key), choice.key)
+ .apply();
}
};
@@ -261,7 +281,9 @@ private void showDialog(final List choices) {
.setNegativeButton(R.string.just_once, dialogButtonsClickListener)
.setPositiveButton(R.string.always, dialogButtonsClickListener)
.setOnDismissListener((dialog) -> {
- if (!selectionIsDownload) finish();
+ if (!selectionIsDownload) {
+ finish();
+ }
})
.create();
@@ -270,10 +292,13 @@ private void showDialog(final List choices) {
setDialogButtonsState(alertDialog, radioGroup.getCheckedRadioButtonId() != -1);
});
- radioGroup.setOnCheckedChangeListener((group, checkedId) -> setDialogButtonsState(alertDialog, true));
+ radioGroup.setOnCheckedChangeListener((group, checkedId) ->
+ setDialogButtonsState(alertDialog, true));
final View.OnClickListener radioButtonsClickListener = v -> {
final int indexOfChild = radioGroup.indexOfChild(v);
- if (indexOfChild == -1) return;
+ if (indexOfChild == -1) {
+ return;
+ }
selectedPreviously = selectedRadioPosition;
selectedRadioPosition = indexOfChild;
@@ -285,18 +310,21 @@ private void showDialog(final List choices) {
int id = 12345;
for (AdapterChoiceItem item : choices) {
- final RadioButton radioButton = (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
+ final RadioButton radioButton
+ = (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
radioButton.setText(item.description);
radioButton.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0);
radioButton.setChecked(false);
radioButton.setId(id++);
- radioButton.setLayoutParams(new RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ radioButton.setLayoutParams(new RadioGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
radioButton.setOnClickListener(radioButtonsClickListener);
radioGroup.addView(radioButton);
}
if (selectedRadioPosition == -1) {
- final String lastSelectedPlayer = preferences.getString(getString(R.string.preferred_open_action_last_selected_key), null);
+ final String lastSelectedPlayer = preferences.getString(
+ getString(R.string.preferred_open_action_last_selected_key), null);
if (!TextUtils.isEmpty(lastSelectedPlayer)) {
for (int i = 0; i < choices.size(); i++) {
AdapterChoiceItem c = choices.get(i);
@@ -315,48 +343,64 @@ private void showDialog(final List choices) {
selectedPreviously = selectedRadioPosition;
alertDialog.show();
+
+ if (AndroidTvUtils.isTv(this)) {
+ FocusOverlayView.setupFocusObserver(alertDialog);
+ }
}
- private List getChoicesForService(StreamingService service, LinkType linkType) {
+ private List getChoicesForService(final StreamingService service,
+ final LinkType linkType) {
final Context context = getThemeWrapperContext();
final List returnList = new ArrayList<>();
- final List capabilities = service.getServiceInfo().getMediaCapabilities();
-
- final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
- boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
- boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
-
- returnList.add(new AdapterChoiceItem(getString(R.string.show_info_key), getString(R.string.show_info),
+ final List capabilities
+ = service.getServiceInfo().getMediaCapabilities();
+
+ final SharedPreferences preferences = PreferenceManager
+ .getDefaultSharedPreferences(this);
+ boolean isExtVideoEnabled = preferences.getBoolean(
+ getString(R.string.use_external_video_player_key), false);
+ boolean isExtAudioEnabled = preferences.getBoolean(
+ getString(R.string.use_external_audio_player_key), false);
+
+ returnList.add(new AdapterChoiceItem(getString(R.string.show_info_key),
+ getString(R.string.show_info),
resolveResourceIdFromAttr(context, R.attr.info)));
if (capabilities.contains(VIDEO) && !(isExtVideoEnabled && linkType != LinkType.STREAM)) {
- returnList.add(new AdapterChoiceItem(getString(R.string.video_player_key), getString(R.string.video_player),
+ returnList.add(new AdapterChoiceItem(getString(R.string.video_player_key),
+ getString(R.string.video_player),
resolveResourceIdFromAttr(context, R.attr.play)));
- returnList.add(new AdapterChoiceItem(getString(R.string.popup_player_key), getString(R.string.popup_player),
+ returnList.add(new AdapterChoiceItem(getString(R.string.popup_player_key),
+ getString(R.string.popup_player),
resolveResourceIdFromAttr(context, R.attr.popup)));
}
if (capabilities.contains(AUDIO) && !(isExtAudioEnabled && linkType != LinkType.STREAM)) {
- returnList.add(new AdapterChoiceItem(getString(R.string.background_player_key), getString(R.string.background_player),
+ returnList.add(new AdapterChoiceItem(getString(R.string.background_player_key),
+ getString(R.string.background_player),
resolveResourceIdFromAttr(context, R.attr.audio)));
}
- returnList.add(new AdapterChoiceItem(getString(R.string.download_key), getString(R.string.download),
+ returnList.add(new AdapterChoiceItem(getString(R.string.download_key),
+ getString(R.string.download),
resolveResourceIdFromAttr(context, R.attr.download)));
return returnList;
}
private Context getThemeWrapperContext() {
- return new ContextThemeWrapper(this,
- ThemeHelper.isLightThemeSelected(this) ? R.style.LightTheme : R.style.DarkTheme);
+ return new ContextThemeWrapper(this, ThemeHelper.isLightThemeSelected(this)
+ ? R.style.LightTheme : R.style.DarkTheme);
}
- private void setDialogButtonsState(AlertDialog dialog, boolean state) {
+ private void setDialogButtonsState(final AlertDialog dialog, final boolean state) {
final Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
- if (negativeButton == null || positiveButton == null) return;
+ if (negativeButton == null || positiveButton == null) {
+ return;
+ }
negativeButton.setEnabled(state);
positiveButton.setEnabled(state);
@@ -372,21 +416,25 @@ private void handleText() {
}
private void handleChoice(final String selectedChoiceKey) {
- final List validChoicesList = Arrays.asList(getResources().getStringArray(R.array.preferred_open_action_values_list));
+ final List validChoicesList = Arrays.asList(getResources()
+ .getStringArray(R.array.preferred_open_action_values_list));
if (validChoicesList.contains(selectedChoiceKey)) {
PreferenceManager.getDefaultSharedPreferences(this).edit()
- .putString(getString(R.string.preferred_open_action_last_selected_key), selectedChoiceKey)
+ .putString(getString(
+ R.string.preferred_open_action_last_selected_key), selectedChoiceKey)
.apply();
}
- if (selectedChoiceKey.equals(getString(R.string.popup_player_key)) && !PermissionHelper.isPopupEnabled(this)) {
+ if (selectedChoiceKey.equals(getString(R.string.popup_player_key))
+ && !PermissionHelper.isPopupEnabled(this)) {
PermissionHelper.showPopupEnablementToast(this);
finish();
return;
}
if (selectedChoiceKey.equals(getString(R.string.download_key))) {
- if (PermissionHelper.checkStoragePermissions(this, PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
+ if (PermissionHelper.checkStoragePermissions(this,
+ PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
selectionIsDownload = true;
openDownloadDialog();
}
@@ -414,7 +462,8 @@ private void handleChoice(final String selectedChoiceKey) {
}
final Intent intent = new Intent(this, FetcherService.class);
- final Choice choice = new Choice(currentService.getServiceId(), currentLinkType, currentUrl, selectedChoiceKey);
+ final Choice choice = new Choice(currentService.getServiceId(), currentLinkType,
+ currentUrl, selectedChoiceKey);
intent.putExtra(FetcherService.KEY_CHOICE, choice);
startService(intent);
@@ -427,12 +476,11 @@ private void openDownloadDialog() {
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@NonNull StreamInfo result) -> {
- List sortedVideoStreams = ListHelper.getSortedStreamVideosList(this,
- result.getVideoStreams(),
- result.getVideoOnlyStreams(),
- false);
- int selectedVideoStreamIndex = ListHelper.getDefaultResolutionIndex(this,
- sortedVideoStreams);
+ List sortedVideoStreams = ListHelper
+ .getSortedStreamVideosList(this, result.getVideoStreams(),
+ result.getVideoOnlyStreams(), false);
+ int selectedVideoStreamIndex = ListHelper
+ .getDefaultResolutionIndex(this, sortedVideoStreams);
FragmentManager fm = getSupportFragmentManager();
DownloadDialog downloadDialog = DownloadDialog.newInstance(result);
@@ -450,7 +498,9 @@ private void openDownloadDialog() {
}
@Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ public void onRequestPermissionsResult(final int requestCode,
+ @NonNull final String[] permissions,
+ @NonNull final int[] grantResults) {
for (int i : grantResults) {
if (i == PackageManager.PERMISSION_DENIED) {
finish();
@@ -462,12 +512,73 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis
}
}
+ /*//////////////////////////////////////////////////////////////////////////
+ // Service Fetcher
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private String removeHeadingGibberish(final String input) {
+ int start = 0;
+ for (int i = input.indexOf("://") - 1; i >= 0; i--) {
+ if (!input.substring(i, i + 1).matches("\\p{L}")) {
+ start = i + 1;
+ break;
+ }
+ }
+ return input.substring(start);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Utils
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private String trim(final String input) {
+ if (input == null || input.length() < 1) {
+ return input;
+ } else {
+ String output = input;
+ while (output.length() > 0 && output.substring(0, 1).matches(REGEX_REMOVE_FROM_URL)) {
+ output = output.substring(1);
+ }
+ while (output.length() > 0
+ && output.substring(output.length() - 1).matches(REGEX_REMOVE_FROM_URL)) {
+ output = output.substring(0, output.length() - 1);
+ }
+ return output;
+ }
+ }
+
+ /**
+ * Retrieves all Strings which look remotely like URLs from a text.
+ * Used if NewPipe was called through share menu.
+ *
+ * @param sharedText text to scan for URLs.
+ * @return potential URLs
+ */
+ protected String[] getUris(final String sharedText) {
+ final Collection result = new HashSet<>();
+ if (sharedText != null) {
+ final String[] array = sharedText.split("\\p{Space}");
+ for (String s : array) {
+ s = trim(s);
+ if (s.length() != 0) {
+ if (s.matches(".+://.+")) {
+ result.add(removeHeadingGibberish(s));
+ } else if (s.matches(".+\\..+")) {
+ result.add("http://" + s);
+ }
+ }
+ }
+ }
+ return result.toArray(new String[result.size()]);
+ }
+
private static class AdapterChoiceItem {
- final String description, key;
+ final String description;
+ final String key;
@DrawableRes
final int icon;
- AdapterChoiceItem(String key, String description, int icon) {
+ AdapterChoiceItem(final String key, final String description, final int icon) {
this.description = description;
this.key = key;
this.icon = icon;
@@ -476,10 +587,12 @@ private static class AdapterChoiceItem {
private static class Choice implements Serializable {
final int serviceId;
- final String url, playerChoice;
+ final String url;
+ final String playerChoice;
final LinkType linkType;
- Choice(int serviceId, LinkType linkType, String url, String playerChoice) {
+ Choice(final int serviceId, final LinkType linkType,
+ final String url, final String playerChoice) {
this.serviceId = serviceId;
this.linkType = linkType;
this.url = url;
@@ -492,14 +605,10 @@ public String toString() {
}
}
- /*//////////////////////////////////////////////////////////////////////////
- // Service Fetcher
- //////////////////////////////////////////////////////////////////////////*/
-
public static class FetcherService extends IntentService {
- private static final int ID = 456;
public static final String KEY_CHOICE = "key_choice";
+ private static final int ID = 456;
private Disposable fetcher;
public FetcherService() {
@@ -513,16 +622,20 @@ public void onCreate() {
}
@Override
- protected void onHandleIntent(@Nullable Intent intent) {
- if (intent == null) return;
+ protected void onHandleIntent(@Nullable final Intent intent) {
+ if (intent == null) {
+ return;
+ }
final Serializable serializable = intent.getSerializableExtra(KEY_CHOICE);
- if (!(serializable instanceof Choice)) return;
+ if (!(serializable instanceof Choice)) {
+ return;
+ }
Choice playerChoice = (Choice) serializable;
handleChoice(playerChoice);
}
- public void handleChoice(Choice choice) {
+ public void handleChoice(final Choice choice) {
Single extends Info> single = null;
UserAction userAction = UserAction.SOMETHING_ELSE;
@@ -549,22 +662,27 @@ public void handleChoice(Choice choice) {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(info -> {
resultHandler.accept(info);
- if (fetcher != null) fetcher.dispose();
+ if (fetcher != null) {
+ fetcher.dispose();
+ }
}, throwable -> ExtractorHelper.handleGeneralException(this,
- choice.serviceId, choice.url, throwable, finalUserAction, ", opened with " + choice.playerChoice));
+ choice.serviceId, choice.url, throwable, finalUserAction,
+ ", opened with " + choice.playerChoice));
}
}
- public Consumer getResultHandler(Choice choice) {
+ public Consumer getResultHandler(final Choice choice) {
return info -> {
final String videoPlayerKey = getString(R.string.video_player_key);
final String backgroundPlayerKey = getString(R.string.background_player_key);
final String popupPlayerKey = getString(R.string.popup_player_key);
- final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
- boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
- boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
- ;
+ final SharedPreferences preferences = PreferenceManager
+ .getDefaultSharedPreferences(this);
+ boolean isExtVideoEnabled = preferences.getBoolean(
+ getString(R.string.use_external_video_player_key), false);
+ boolean isExtAudioEnabled = preferences.getBoolean(
+ getString(R.string.use_external_audio_player_key), false);
PlayQueue playQueue;
String playerChoice = choice.playerChoice;
@@ -590,7 +708,9 @@ public Consumer getResultHandler(Choice choice) {
}
if (info instanceof ChannelInfo || info instanceof PlaylistInfo) {
- playQueue = info instanceof ChannelInfo ? new ChannelPlayQueue((ChannelInfo) info) : new PlaylistPlayQueue((PlaylistInfo) info);
+ playQueue = info instanceof ChannelInfo
+ ? new ChannelPlayQueue((ChannelInfo) info)
+ : new PlaylistPlayQueue((PlaylistInfo) info);
if (playerChoice.equals(videoPlayerKey)) {
NavigationHelper.playOnMainPlayer(this, playQueue, true);
@@ -607,7 +727,9 @@ public Consumer getResultHandler(Choice choice) {
public void onDestroy() {
super.onDestroy();
stopForeground(true);
- if (fetcher != null) fetcher.dispose();
+ if (fetcher != null) {
+ fetcher.dispose();
+ }
}
private NotificationCompat.Builder createNotification() {
@@ -615,8 +737,10 @@ private NotificationCompat.Builder createNotification() {
.setOngoing(true)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
- .setContentTitle(getString(R.string.preferred_player_fetcher_notification_title))
- .setContentText(getString(R.string.preferred_player_fetcher_notification_message));
+ .setContentTitle(
+ getString(R.string.preferred_player_fetcher_notification_title))
+ .setContentText(
+ getString(R.string.preferred_player_fetcher_notification_message));
}
}
@@ -625,7 +749,7 @@ private NotificationCompat.Builder createNotification() {
//////////////////////////////////////////////////////////////////////////*/
@Nullable
- private String getUrl(Intent intent) {
+ private String getUrl(final Intent intent) {
String foundUrl = null;
if (intent.getData() != null) {
// Called from another app
diff --git a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
index 0a4e9e8656b..2fb8ac7f7ad 100644
--- a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.java
@@ -4,21 +4,22 @@
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
-import com.google.android.material.tabs.TabLayout;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
+
+import com.google.android.material.tabs.TabLayout;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
@@ -27,26 +28,41 @@
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public class AboutActivity extends AppCompatActivity {
-
/**
- * List of all software components
+ * List of all software components.
*/
private static final SoftwareComponent[] SOFTWARE_COMPONENTS = new SoftwareComponent[]{
- new SoftwareComponent("Giga Get", "2014 - 2015", "Peter Cai", "https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL2),
- new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger", "https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
- new SoftwareComponent("Jsoup", "2017", "Jonathan Hedley", "https://github.com/jhy/jsoup", StandardLicenses.MIT),
- new SoftwareComponent("Rhino", "2015", "Mozilla", "https://www.mozilla.org/rhino/", StandardLicenses.MPL2),
- new SoftwareComponent("ACRA", "2013", "Kevin Gaudin", "http://www.acra.ch", StandardLicenses.APACHE2),
- new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich", "https://github.com/nostra13/Android-Universal-Image-Loader", StandardLicenses.APACHE2),
- new SoftwareComponent("CircleImageView", "2014 - 2020", "Henning Dodenhof", "https://github.com/hdodenhof/CircleImageView", StandardLicenses.APACHE2),
- new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam", "https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2),
- new SoftwareComponent("ExoPlayer", "2014 - 2020", "Google Inc", "https://github.com/google/ExoPlayer", StandardLicenses.APACHE2),
- new SoftwareComponent("RxAndroid", "2015 - 2018", "The RxAndroid authors", "https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2),
- new SoftwareComponent("RxJava", "2016 - 2020", "RxJava Contributors", "https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2),
- new SoftwareComponent("RxBinding", "2015 - 2018", "Jake Wharton", "https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2),
- new SoftwareComponent("PrettyTime", "2012 - 2020", "Lincoln Baxter, III", "https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2),
- new SoftwareComponent("Markwon", "2017 - 2020", "Noties", "https://github.com/noties/Markwon", StandardLicenses.APACHE2),
- new SoftwareComponent("Groupie", "2016", "Lisa Wray", "https://github.com/lisawray/groupie", StandardLicenses.MIT)
+ new SoftwareComponent("Giga Get", "2014 - 2015", "Peter Cai",
+ "https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL2),
+ new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger",
+ "https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
+ new SoftwareComponent("Jsoup", "2017", "Jonathan Hedley",
+ "https://github.com/jhy/jsoup", StandardLicenses.MIT),
+ new SoftwareComponent("Rhino", "2015", "Mozilla",
+ "https://www.mozilla.org/rhino/", StandardLicenses.MPL2),
+ new SoftwareComponent("ACRA", "2013", "Kevin Gaudin",
+ "http://www.acra.ch", StandardLicenses.APACHE2),
+ new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich",
+ "https://github.com/nostra13/Android-Universal-Image-Loader",
+ StandardLicenses.APACHE2),
+ new SoftwareComponent("CircleImageView", "2014 - 2020", "Henning Dodenhof",
+ "https://github.com/hdodenhof/CircleImageView", StandardLicenses.APACHE2),
+ new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam",
+ "https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2),
+ new SoftwareComponent("ExoPlayer", "2014 - 2020", "Google Inc",
+ "https://github.com/google/ExoPlayer", StandardLicenses.APACHE2),
+ new SoftwareComponent("RxAndroid", "2015 - 2018", "The RxAndroid authors",
+ "https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2),
+ new SoftwareComponent("RxJava", "2016 - 2020", "RxJava Contributors",
+ "https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2),
+ new SoftwareComponent("RxBinding", "2015 - 2018", "Jake Wharton",
+ "https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2),
+ new SoftwareComponent("PrettyTime", "2012 - 2020", "Lincoln Baxter, III",
+ "https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2),
+ new SoftwareComponent("Markwon", "2017 - 2020", "Noties",
+ "https://github.com/noties/Markwon", StandardLicenses.APACHE2),
+ new SoftwareComponent("Groupie", "2016", "Lisa Wray",
+ "https://github.com/lisawray/groupie", StandardLicenses.MIT)
};
/**
@@ -65,7 +81,7 @@ public class AboutActivity extends AppCompatActivity {
private ViewPager mViewPager;
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(final Bundle savedInstanceState) {
assureCorrectAppLanguage(this);
super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this);
@@ -88,10 +104,8 @@ protected void onCreate(Bundle savedInstanceState) {
tabLayout.setupWithViewPager(mViewPager);
}
-
@Override
- public boolean onOptionsItemSelected(MenuItem item) {
-
+ public boolean onOptionsItemSelected(final MenuItem item) {
int id = item.getItemId();
switch (id) {
@@ -107,21 +121,20 @@ public boolean onOptionsItemSelected(MenuItem item) {
* A placeholder fragment containing a simple view.
*/
public static class AboutFragment extends Fragment {
-
- public AboutFragment() {
- }
+ public AboutFragment() { }
/**
- * Returns a new instance of this fragment for the given section
- * number.
+ * Created a new instance of this fragment for the given section number.
+ *
+ * @return New instance of {@link AboutFragment}
*/
public static AboutFragment newInstance() {
return new AboutFragment();
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_about, container, false);
Context context = this.getContext();
@@ -129,40 +142,42 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
version.setText(BuildConfig.VERSION_NAME);
View githubLink = rootView.findViewById(R.id.github_link);
- githubLink.setOnClickListener(nv -> openWebsite(context.getString(R.string.github_url), context));
+ githubLink.setOnClickListener(nv ->
+ openWebsite(context.getString(R.string.github_url), context));
View donationLink = rootView.findViewById(R.id.donation_link);
- donationLink.setOnClickListener(v -> openWebsite(context.getString(R.string.donation_url), context));
+ donationLink.setOnClickListener(v ->
+ openWebsite(context.getString(R.string.donation_url), context));
View websiteLink = rootView.findViewById(R.id.website_link);
- websiteLink.setOnClickListener(nv -> openWebsite(context.getString(R.string.website_url), context));
+ websiteLink.setOnClickListener(nv ->
+ openWebsite(context.getString(R.string.website_url), context));
View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
- privacyPolicyLink.setOnClickListener(v -> openWebsite(context.getString(R.string.privacy_policy_url), context));
+ privacyPolicyLink.setOnClickListener(v ->
+ openWebsite(context.getString(R.string.privacy_policy_url), context));
return rootView;
}
- private void openWebsite(String url, Context context) {
+ private void openWebsite(final String url, final Context context) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
context.startActivity(intent);
}
}
-
/**
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
-
- public SectionsPagerAdapter(FragmentManager fm) {
+ public SectionsPagerAdapter(final FragmentManager fm) {
super(fm);
}
@Override
- public Fragment getItem(int position) {
+ public Fragment getItem(final int position) {
switch (position) {
case 0:
return AboutFragment.newInstance();
@@ -179,7 +194,7 @@ public int getCount() {
}
@Override
- public CharSequence getPageTitle(int position) {
+ public CharSequence getPageTitle(final int position) {
switch (position) {
case 0:
return getString(R.string.tab_about);
diff --git a/app/src/main/java/org/schabi/newpipe/about/License.java b/app/src/main/java/org/schabi/newpipe/about/License.java
index e51e1d0f1a9..3700098604f 100644
--- a/app/src/main/java/org/schabi/newpipe/about/License.java
+++ b/app/src/main/java/org/schabi/newpipe/about/License.java
@@ -5,18 +5,17 @@
import android.os.Parcelable;
/**
- * A software license
+ * Class for storing information about a software license.
*/
public class License implements Parcelable {
-
public static final Creator CREATOR = new Creator() {
@Override
- public License createFromParcel(Parcel source) {
+ public License createFromParcel(final Parcel source) {
return new License(source);
}
@Override
- public License[] newArray(int size) {
+ public License[] newArray(final int size) {
return new License[size];
}
};
@@ -24,16 +23,22 @@ public License[] newArray(int size) {
private final String name;
private String filename;
- public License(String name, String abbreviation, String filename) {
- if(name == null) throw new NullPointerException("name is null");
- if(abbreviation == null) throw new NullPointerException("abbreviation is null");
- if(filename == null) throw new NullPointerException("filename is null");
+ public License(final String name, final String abbreviation, final String filename) {
+ if (name == null) {
+ throw new NullPointerException("name is null");
+ }
+ if (abbreviation == null) {
+ throw new NullPointerException("abbreviation is null");
+ }
+ if (filename == null) {
+ throw new NullPointerException("filename is null");
+ }
this.name = name;
this.filename = filename;
this.abbreviation = abbreviation;
}
- protected License(Parcel in) {
+ protected License(final Parcel in) {
this.filename = in.readString();
this.abbreviation = in.readString();
this.name = in.readString();
@@ -50,7 +55,7 @@ public Uri getContentUri() {
public String getAbbreviation() {
return abbreviation;
}
-
+
public String getFilename() {
return filename;
}
@@ -61,7 +66,7 @@ public int describeContents() {
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(final Parcel dest, final int flags) {
dest.writeString(this.filename);
dest.writeString(this.abbreviation);
dest.writeString(this.name);
diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.java b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.java
index fe78ff9f14f..0bda79feefc 100644
--- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.java
@@ -5,26 +5,32 @@
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
-import android.view.*;
-import android.widget.TextView;
+
import org.schabi.newpipe.R;
import java.util.Arrays;
import java.util.Comparator;
/**
- * Fragment containing the software licenses
+ * Fragment containing the software licenses.
*/
public class LicenseFragment extends Fragment {
-
private static final String ARG_COMPONENTS = "components";
private SoftwareComponent[] softwareComponents;
private SoftwareComponent mComponentForContextMenu;
- public static LicenseFragment newInstance(SoftwareComponent[] softwareComponents) {
- if(softwareComponents == null) {
+ public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
+ if (softwareComponents == null) {
throw new NullPointerException("softwareComponents is null");
}
LicenseFragment fragment = new LicenseFragment();
@@ -35,23 +41,25 @@ public static LicenseFragment newInstance(SoftwareComponent[] softwareComponents
}
/**
- * Shows a popup containing the license
+ * Shows a popup containing the license.
+ *
* @param context the context to use
* @param license the license to show
*/
- public static void showLicense(Context context, License license) {
+ public static void showLicense(final Context context, final License license) {
new LicenseFragmentHelper((Activity) context).execute(license);
}
@Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
+ public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- softwareComponents = (SoftwareComponent[]) getArguments().getParcelableArray(ARG_COMPONENTS);
+ softwareComponents = (SoftwareComponent[]) getArguments()
+ .getParcelableArray(ARG_COMPONENTS);
// Sort components by name
Arrays.sort(softwareComponents, new Comparator() {
@Override
- public int compare(SoftwareComponent o1, SoftwareComponent o2) {
+ public int compare(final SoftwareComponent o1, final SoftwareComponent o2) {
return o1.getName().compareTo(o2.getName());
}
});
@@ -59,7 +67,8 @@ public int compare(SoftwareComponent o1, SoftwareComponent o2) {
@Nullable
@Override
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
+ @Nullable final Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_licenses, container, false);
ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
@@ -67,7 +76,8 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
licenseLink.setOnClickListener(new OnReadFullLicenseClickListener());
for (final SoftwareComponent component : softwareComponents) {
- View componentView = inflater.inflate(R.layout.item_software_component, container, false);
+ View componentView = inflater
+ .inflate(R.layout.item_software_component, container, false);
TextView softwareName = componentView.findViewById(R.id.name);
TextView copyright = componentView.findViewById(R.id.copyright);
softwareName.setText(component.getName());
@@ -79,7 +89,7 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
componentView.setTag(component);
componentView.setOnClickListener(new View.OnClickListener() {
@Override
- public void onClick(View v) {
+ public void onClick(final View v) {
Context context = v.getContext();
if (context != null) {
showLicense(context, component.getLicense());
@@ -93,7 +103,8 @@ public void onClick(View v) {
}
@Override
- public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+ public void onCreateContextMenu(final ContextMenu menu, final View v,
+ final ContextMenu.ContextMenuInfo menuInfo) {
MenuInflater inflater = getActivity().getMenuInflater();
SoftwareComponent component = (SoftwareComponent) v.getTag();
menu.setHeaderTitle(component.getName());
@@ -103,7 +114,7 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMen
}
@Override
- public boolean onContextItemSelected(MenuItem item) {
+ public boolean onContextItemSelected(final MenuItem item) {
// item.getMenuInfo() is null so we use the tag of the view
final SoftwareComponent component = mComponentForContextMenu;
if (component == null) {
@@ -119,14 +130,14 @@ public boolean onContextItemSelected(MenuItem item) {
return false;
}
- private void openWebsite(String componentLink) {
+ private void openWebsite(final String componentLink) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(componentLink));
startActivity(browserIntent);
}
private static class OnReadFullLicenseClickListener implements View.OnClickListener {
@Override
- public void onClick(View v) {
+ public void onClick(final View v) {
LicenseFragment.showLicense(v.getContext(), StandardLicenses.GPL3);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.java b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.java
index 9a11b19cc6c..94a1532f58a 100644
--- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.java
@@ -2,88 +2,53 @@
import android.app.Activity;
import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.Resources;
import android.os.AsyncTask;
+import android.webkit.WebView;
+
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
-import android.webkit.WebView;
+
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
+import java.nio.charset.StandardCharsets;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public class LicenseFragmentHelper extends AsyncTask