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.md b/.github/ISSUE_TEMPLATE.md
deleted file mode 100644
index 8073503adea..00000000000
--- a/.github/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,3 +0,0 @@
-- [ ] I carefully read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) and agree to them.
-- [ ] I checked if the issue/feature exists in the latest version.
-- [ ] I did use the [incredible bugreport to markdown converter](https://teamnewpipe.github.io/CrashReportToMarkdown/) to paste bug reports.
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000000..dbc1c05a5b5
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,46 @@
+---
+name: Bug report
+about: Create a bug report to help us improve
+labels: bug
+assignees: ''
+
+---
+
+
+
+
+
+### Version
+
+-
+
+### Steps to reproduce the bug
+
+
+
+
+### Expected behavior
+
+
+### Actual behaviour
+
+
+### Screenshots/Screen recordings
+
+
+### Logs
+
+
+
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000000..90134a204c2
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,39 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+labels: enhancement
+assignees: ''
+
+---
+
+
+
+
+#### Describe the feature you want
+
+
+
+
+#### Is your feature request related to a problem? Please describe it
+
+
+
+
+#### Additional context
+
+
+
+
+#### How will you/everyone benefit from this feature?
+
+
+
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index d0e58680ac1..f12eb2fe81b 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1 +1,28 @@
+
+
+#### What is it?
+- [ ] Bug fix (user facing)
+- [ ] Feature (user facing)
+- [ ] Code base improvement (dev facing)
+- [ ] Meta improvement to the project (dev facing)
+
+#### Description of the changes in your PR
+
+- record videos
+- create clones
+- take over the world
+
+#### Fixes the following issue(s)
+
+-
+
+#### Relies on the following changes
+
+-
+
+#### Testing apk
+
+debug.zip
+
+#### Agreement
- [ ] I carefully read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) and agree to them.
diff --git a/README.md b/README.md
index d4952b2b8ae..50eb40594ae 100644
--- a/README.md
+++ b/README.md
@@ -4,16 +4,16 @@
+ * 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.
+ *
+ *
+ * Check out the changes in:
+ *
+ *
+ *
{@link #saveState()}
+ *
{@link #restoreState(Parcelable, ClassLoader)}
+ *
+ */
+@SuppressWarnings("deprecation")
+public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapter {
+ private static final String TAG = "FragmentStatePagerAdapt";
+ private static final boolean DEBUG = false;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({BEHAVIOR_SET_USER_VISIBLE_HINT, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT})
+ private @interface Behavior { }
+
+ /**
+ * Indicates that {@link Fragment#setUserVisibleHint(boolean)} will be called when the current
+ * fragment changes.
+ *
+ * @deprecated This behavior relies on the deprecated
+ * {@link Fragment#setUserVisibleHint(boolean)} API. Use
+ * {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} to switch to its replacement,
+ * {@link FragmentTransaction#setMaxLifecycle}.
+ * @see #FragmentStatePagerAdapterMenuWorkaround(FragmentManager, int)
+ */
+ @Deprecated
+ public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;
+
+ /**
+ * Indicates that only the current fragment will be in the {@link Lifecycle.State#RESUMED}
+ * state. All other Fragments are capped at {@link Lifecycle.State#STARTED}.
+ *
+ * @see #FragmentStatePagerAdapterMenuWorkaround(FragmentManager, int)
+ */
+ public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;
+
+ private final FragmentManager mFragmentManager;
+ private final int mBehavior;
+ private FragmentTransaction mCurTransaction = null;
+
+ private ArrayList mSavedState = new ArrayList();
+ private ArrayList mFragments = new ArrayList();
+ private Fragment mCurrentPrimaryItem = null;
+
+ /**
+ * 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}.
+ *
+ *
Fragments will have {@link Fragment#setUserVisibleHint(boolean)} called whenever the
+ * current Fragment changes.
+ *
+ * @param fm fragment manager that will interact with this adapter
+ * @deprecated use {@link #FragmentStatePagerAdapterMenuWorkaround(FragmentManager, int)} with
+ * {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}
+ */
+ @Deprecated
+ public FragmentStatePagerAdapterMenuWorkaround(@NonNull final FragmentManager fm) {
+ this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
+ }
+
+ /**
+ * Constructor for {@link FragmentStatePagerAdapterMenuWorkaround}.
+ *
+ * If {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} is passed in, then only the current
+ * Fragment is in the {@link Lifecycle.State#RESUMED} state, while all other fragments are
+ * capped at {@link Lifecycle.State#STARTED}. If {@link #BEHAVIOR_SET_USER_VISIBLE_HINT} is
+ * passed, all fragments are in the {@link Lifecycle.State#RESUMED} state and there will be
+ * callbacks to {@link Fragment#setUserVisibleHint(boolean)}.
+ *
+ * @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 final FragmentManager fm,
+ @Behavior final int behavior) {
+ mFragmentManager = fm;
+ mBehavior = behavior;
+ }
+
+ /**
+ * @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 final ViewGroup container) {
+ if (container.getId() == View.NO_ID) {
+ throw new IllegalStateException("ViewPager with adapter " + this
+ + " requires a view id");
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ @NonNull
+ @Override
+ 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
+ // taken care of restoring the fragments we previously had instantiated.
+ if (mFragments.size() > position) {
+ Fragment f = mFragments.get(position);
+ if (f != null) {
+ return f;
+ }
+ }
+
+ if (mCurTransaction == null) {
+ mCurTransaction = mFragmentManager.beginTransaction();
+ }
+
+ Fragment fragment = getItem(position);
+ if (DEBUG) {
+ Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
+ }
+ if (mSavedState.size() > position) {
+ Fragment.SavedState fss = mSavedState.get(position);
+ if (fss != null) {
+ fragment.setInitialSavedState(fss);
+ }
+ }
+ while (mFragments.size() <= position) {
+ mFragments.add(null);
+ }
+ fragment.setMenuVisibility(false);
+ if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
+ fragment.setUserVisibleHint(false);
+ }
+
+ mFragments.set(position, fragment);
+ mCurTransaction.add(container.getId(), fragment);
+
+ if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
+ mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
+ }
+
+ return fragment;
+ }
+
+ @Override
+ 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());
+ }
+ while (mSavedState.size() <= position) {
+ mSavedState.add(null);
+ }
+ mSavedState.set(position, fragment.isAdded()
+ ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
+ mFragments.set(position, null);
+
+ mCurTransaction.remove(fragment);
+ if (fragment == mCurrentPrimaryItem) {
+ mCurrentPrimaryItem = null;
+ }
+ }
+
+ @Override
+ @SuppressWarnings({"ReferenceEquality", "deprecation"})
+ 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);
+ if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
+ if (mCurTransaction == null) {
+ mCurTransaction = mFragmentManager.beginTransaction();
+ }
+ mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
+ } else {
+ mCurrentPrimaryItem.setUserVisibleHint(false);
+ }
+ }
+ fragment.setMenuVisibility(true);
+ if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
+ if (mCurTransaction == null) {
+ mCurTransaction = mFragmentManager.beginTransaction();
+ }
+ mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
+ } else {
+ fragment.setUserVisibleHint(true);
+ }
+
+ mCurrentPrimaryItem = fragment;
+ }
+ }
+
+ @Override
+ public void finishUpdate(@NonNull final ViewGroup container) {
+ if (mCurTransaction != null) {
+ mCurTransaction.commitNowAllowingStateLoss();
+ mCurTransaction = null;
+ }
+ }
+
+ @Override
+ public boolean isViewFromObject(@NonNull final View view, @NonNull final Object object) {
+ return ((Fragment) object).getView() == view;
+ }
+
+ //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ private final String selectedFragment = "selected_fragment";
+ //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+ @Override
+ @Nullable
+ public Parcelable saveState() {
+ Bundle state = null;
+ if (mSavedState.size() > 0) {
+ state = new Bundle();
+ Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
+ mSavedState.toArray(fss);
+ state.putParcelableArray("states", fss);
+ }
+ for (int i = 0; i < mFragments.size(); i++) {
+ Fragment f = mFragments.get(i);
+ if (f != null && f.isAdded()) {
+ if (state == null) {
+ state = new Bundle();
+ }
+ String key = "f" + i;
+ mFragmentManager.putFragment(state, key, f);
+
+ //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ // Check if it's the same fragment instance
+ if (f == mCurrentPrimaryItem) {
+ state.putString(selectedFragment, key);
+ }
+ //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ }
+ }
+ return state;
+ }
+
+ @Override
+ public void restoreState(@Nullable final Parcelable state, @Nullable final ClassLoader loader) {
+ if (state != null) {
+ Bundle bundle = (Bundle) state;
+ bundle.setClassLoader(loader);
+ Parcelable[] fss = bundle.getParcelableArray("states");
+ mSavedState.clear();
+ mFragments.clear();
+ if (fss != null) {
+ for (int i = 0; i < fss.length; i++) {
+ mSavedState.add((Fragment.SavedState) fss[i]);
+ }
+ }
+ Iterable keys = bundle.keySet();
+ for (String key: keys) {
+ if (key.startsWith("f")) {
+ int index = Integer.parseInt(key.substring(1));
+ Fragment f = mFragmentManager.getFragment(bundle, key);
+ if (f != null) {
+ while (mFragments.size() <= index) {
+ mFragments.add(null);
+ }
+ //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ final boolean wasSelected = bundle.getString(selectedFragment, "")
+ .equals(key);
+ f.setMenuVisibility(wasSelected);
+ //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ mFragments.set(index, f);
+ } else {
+ Log.w(TAG, "Bad fragment at key " + key);
+ }
+ }
+ }
+ }
+ }
+}
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..78da9678b3e 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
@@ -10,15 +10,15 @@
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 {
-
- 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 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 +35,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 +63,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 +79,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 1da9d380311..2b9767a5ac8 100644
--- a/app/src/main/java/org/schabi/newpipe/App.java
+++ b/app/src/main/java/org/schabi/newpipe/App.java
@@ -21,14 +21,16 @@
import org.acra.config.ACRAConfigurationException;
import org.acra.config.ConfigurationBuilder;
import org.acra.sender.ReportSenderFactory;
-import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.notifications.scheduler.NotificationsScheduler;
import org.schabi.newpipe.report.AcraReportSenderFactory;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.SettingsActivity;
-import org.schabi.newpipe.util.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;
import java.io.IOException;
@@ -65,15 +67,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();
@@ -96,10 +107,15 @@ public void onCreate() {
SettingsActivity.initSettings(this);
NewPipe.init(getDownloader(),
- org.schabi.newpipe.util.Localization.getPreferredExtractorLocal(this));
+ Localization.getPreferredLocalization(this),
+ Localization.getPreferredContentCountry(this));
+ Localization.init(getApplicationContext());
+
StateSaver.init(this);
initNotificationChannel();
+ ServiceHelper.initServices(this);
+
// Initialize image loader
ImageLoader.getInstance().init(getImageLoaderConfigurations(10, 50));
@@ -113,31 +129,37 @@ public void onCreate() {
}
protected Downloader getDownloader() {
- return org.schabi.newpipe.Downloader.init(null);
+ return DownloaderImpl.init(null);
}
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;
@@ -147,22 +169,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
@@ -188,7 +212,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);
@@ -199,7 +223,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));
}
}
@@ -228,11 +252,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
@@ -264,12 +288,6 @@ private void setUpStreamsNotificationChannel() {
}
}
- @Nullable
- public static RefWatcher getRefWatcher(Context context) {
- final App application = (App) context.getApplicationContext();
- return application.refWatcher;
- }
-
protected RefWatcher installLeakCanary() {
return RefWatcher.DISABLED;
}
@@ -277,8 +295,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 ccdb806ef3c..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,21 @@ 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/Downloader.java b/app/src/main/java/org/schabi/newpipe/Downloader.java
deleted file mode 100644
index ae76d562397..00000000000
--- a/app/src/main/java/org/schabi/newpipe/Downloader.java
+++ /dev/null
@@ -1,280 +0,0 @@
-package org.schabi.newpipe;
-
-import androidx.annotation.Nullable;
-import android.text.TextUtils;
-
-import org.schabi.newpipe.extractor.DownloadRequest;
-import org.schabi.newpipe.extractor.DownloadResponse;
-import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
-import org.schabi.newpipe.extractor.utils.Localization;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import okhttp3.MediaType;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-import okhttp3.Response;
-import okhttp3.ResponseBody;
-
-
-/*
- * Created by Christian Schabesberger on 28.01.16.
- *
- * Copyright (C) Christian Schabesberger 2016
- * Downloader.java is part of NewPipe.
- *
- * NewPipe is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * NewPipe is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with NewPipe. If not, see .
- */
-
-public class Downloader implements org.schabi.newpipe.extractor.Downloader {
- public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0";
-
- private static Downloader instance;
- private String mCookies;
- private final OkHttpClient client;
-
- private Downloader(OkHttpClient.Builder builder) {
- this.client = builder
- .readTimeout(30, TimeUnit.SECONDS)
- //.cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"), 16 * 1024 * 1024))
- .build();
- }
-
- /**
- * It's recommended to call exactly once in the entire lifetime of the application.
- *
- * @param builder if null, default builder will be used
- */
- public static Downloader init(@Nullable OkHttpClient.Builder builder) {
- return instance = new Downloader(builder != null ? builder : new OkHttpClient.Builder());
- }
-
- public static Downloader getInstance() {
- return instance;
- }
-
- public String getCookies() {
- return mCookies;
- }
-
- public void setCookies(String cookies) {
- mCookies = cookies;
- }
-
- /**
- * Get the size of the content that the url is pointing by firing a HEAD request.
- *
- * @param url an url pointing to the content
- * @return the size of the content, in bytes
- */
- public long getContentLength(String url) throws IOException {
- Response response = null;
- try {
- final Request request = new Request.Builder()
- .head().url(url)
- .addHeader("User-Agent", USER_AGENT)
- .build();
- response = client.newCall(request).execute();
-
- String contentLength = response.header("Content-Length");
- return contentLength == null ? -1 : Long.parseLong(contentLength);
- } catch (NumberFormatException e) {
- throw new IOException("Invalid content length", e);
- } finally {
- if (response != null) {
- response.close();
- }
- }
- }
-
- /**
- * Download the text file at the supplied URL as in download(String),
- * but set the HTTP header field "Accept-Language" to the supplied string.
- *
- * @param siteUrl the URL of the text file to return the contents of
- * @param localization the language and country (usually a 2-character code) to set
- * @return the contents of the specified text file
- */
- @Override
- public String download(String siteUrl, Localization localization) throws IOException, ReCaptchaException {
- Map requestProperties = new HashMap<>();
- requestProperties.put("Accept-Language", localization.getLanguage());
- return download(siteUrl, requestProperties);
- }
-
- /**
- * Download the text file at the supplied URL as in download(String),
- * but set the HTTP headers included in the customProperties map.
- *
- * @param siteUrl the URL of the text file to return the contents of
- * @param customProperties set request header properties
- * @return the contents of the specified text file
- * @throws IOException
- */
- @Override
- public String download(String siteUrl, Map customProperties) throws IOException, ReCaptchaException {
- return getBody(siteUrl, customProperties).string();
- }
-
- public InputStream stream(String siteUrl) throws IOException {
- try {
- return getBody(siteUrl, Collections.emptyMap()).byteStream();
- } catch (ReCaptchaException e) {
- throw new IOException(e.getMessage(), e.getCause());
- }
- }
-
- private ResponseBody getBody(String siteUrl, Map customProperties) throws IOException, ReCaptchaException {
- final Request.Builder requestBuilder = new Request.Builder()
- .method("GET", null).url(siteUrl);
-
- for (Map.Entry header : customProperties.entrySet()) {
- requestBuilder.addHeader(header.getKey(), header.getValue());
- }
-
- if (!customProperties.containsKey("User-Agent")) {
- requestBuilder.header("User-Agent", USER_AGENT);
- }
-
- if (!TextUtils.isEmpty(mCookies)) {
- requestBuilder.addHeader("Cookie", mCookies);
- }
-
- final Request request = requestBuilder.build();
- final Response response = client.newCall(request).execute();
- final ResponseBody body = response.body();
-
- if (response.code() == 429) {
- throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl);
- }
-
- if (body == null) {
- response.close();
- return null;
- }
-
- return body;
- }
-
- /**
- * Download (via HTTP) the text file located at the supplied URL, and return its contents.
- * Primarily intended for downloading web pages.
- *
- * @param siteUrl the URL of the text file to download
- * @return the contents of the specified text file
- */
- @Override
- public String download(String siteUrl) throws IOException, ReCaptchaException {
- return download(siteUrl, Collections.emptyMap());
- }
-
-
- @Override
- public DownloadResponse get(String siteUrl, DownloadRequest request) throws IOException, ReCaptchaException {
- final Request.Builder requestBuilder = new Request.Builder()
- .method("GET", null).url(siteUrl);
-
- Map> requestHeaders = request.getRequestHeaders();
- // set custom headers in request
- for (Map.Entry> pair : requestHeaders.entrySet()) {
- for(String value : pair.getValue()){
- requestBuilder.addHeader(pair.getKey(), value);
- }
- }
-
- if (!requestHeaders.containsKey("User-Agent")) {
- requestBuilder.header("User-Agent", USER_AGENT);
- }
-
- if (!TextUtils.isEmpty(mCookies)) {
- requestBuilder.addHeader("Cookie", mCookies);
- }
-
- final Request okRequest = requestBuilder.build();
- final Response response = client.newCall(okRequest).execute();
- final ResponseBody body = response.body();
-
- if (response.code() == 429) {
- throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl);
- }
-
- if (body == null) {
- response.close();
- return null;
- }
-
- return new DownloadResponse(body.string(), response.headers().toMultimap());
- }
-
- @Override
- public DownloadResponse get(String siteUrl) throws IOException, ReCaptchaException {
- return get(siteUrl, DownloadRequest.emptyRequest);
- }
-
- @Override
- public DownloadResponse post(String siteUrl, DownloadRequest request) throws IOException, ReCaptchaException {
-
- Map> requestHeaders = request.getRequestHeaders();
- if(null == requestHeaders.get("Content-Type") || requestHeaders.get("Content-Type").isEmpty()){
- // content type header is required. maybe throw an exception here
- return null;
- }
-
- String contentType = requestHeaders.get("Content-Type").get(0);
-
- RequestBody okRequestBody = null;
- if(null != request.getRequestBody()){
- okRequestBody = RequestBody.create(MediaType.parse(contentType), request.getRequestBody());
- }
- final Request.Builder requestBuilder = new Request.Builder()
- .method("POST", okRequestBody).url(siteUrl);
-
- // set custom headers in request
- for (Map.Entry> pair : requestHeaders.entrySet()) {
- for(String value : pair.getValue()){
- requestBuilder.addHeader(pair.getKey(), value);
- }
- }
-
- if (!requestHeaders.containsKey("User-Agent")) {
- requestBuilder.header("User-Agent", USER_AGENT);
- }
-
- if (!TextUtils.isEmpty(mCookies)) {
- requestBuilder.addHeader("Cookie", mCookies);
- }
-
- final Request okRequest = requestBuilder.build();
- final Response response = client.newCall(okRequest).execute();
- final ResponseBody body = response.body();
-
- if (response.code() == 429) {
- throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl);
- }
-
- if (body == null) {
- response.close();
- return null;
- }
-
- return new DownloadResponse(body.string(), response.headers().toMultimap());
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java
new file mode 100644
index 00000000000..ed517f160a0
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java
@@ -0,0 +1,233 @@
+package org.schabi.newpipe;
+
+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;
+import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
+import org.schabi.newpipe.util.TLSSocketFactoryCompat;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+import okhttp3.CipherSuite;
+import okhttp3.ConnectionSpec;
+import okhttp3.OkHttpClient;
+import okhttp3.RequestBody;
+import okhttp3.ResponseBody;
+
+import static org.schabi.newpipe.MainActivity.DEBUG;
+
+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(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))
+ .build();
+ }
+
+ /**
+ * 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 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(final String cookies) {
+ mCookies = cookies;
+ }
+
+ /**
+ * Get the size of the content that the url is pointing by firing a HEAD request.
+ *
+ * @param url an url pointing to the content
+ * @return the size of the content, in bytes
+ */
+ public long getContentLength(final String url) throws IOException {
+ try {
+ final Response response = head(url);
+ return Long.parseLong(response.getHeader("Content-Length"));
+ } catch (NumberFormatException e) {
+ throw new IOException("Invalid content length", e);
+ } catch (ReCaptchaException e) {
+ throw new IOException(e);
+ }
+ }
+
+ public InputStream stream(final String siteUrl) throws IOException {
+ try {
+ final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder()
+ .method("GET", null).url(siteUrl)
+ .addHeader("User-Agent", USER_AGENT);
+
+ if (!TextUtils.isEmpty(mCookies)) {
+ requestBuilder.addHeader("Cookie", mCookies);
+ }
+
+ final okhttp3.Request request = requestBuilder.build();
+ final okhttp3.Response response = client.newCall(request).execute();
+ final ResponseBody body = response.body();
+
+ if (response.code() == 429) {
+ throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl);
+ }
+
+ if (body == null) {
+ response.close();
+ return null;
+ }
+
+ return body.byteStream();
+ } catch (ReCaptchaException e) {
+ throw new IOException(e.getMessage(), e.getCause());
+ }
+ }
+
+ @Override
+ 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();
+ final byte[] dataToSend = request.dataToSend();
+
+ RequestBody requestBody = null;
+ if (dataToSend != null) {
+ requestBody = RequestBody.create(null, dataToSend);
+ }
+
+ final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder()
+ .method(httpMethod, requestBody).url(url)
+ .addHeader("User-Agent", USER_AGENT);
+
+ if (!TextUtils.isEmpty(mCookies)) {
+ requestBuilder.addHeader("Cookie", mCookies);
+ }
+
+ for (Map.Entry> pair : headers.entrySet()) {
+ final String headerName = pair.getKey();
+ final List headerValueList = pair.getValue();
+
+ if (headerValueList.size() > 1) {
+ requestBuilder.removeHeader(headerName);
+ for (String headerValue : headerValueList) {
+ requestBuilder.addHeader(headerName, headerValue);
+ }
+ } else if (headerValueList.size() == 1) {
+ requestBuilder.header(headerName, headerValueList.get(0));
+ }
+
+ }
+
+ final okhttp3.Response response = client.newCall(requestBuilder.build()).execute();
+
+ if (response.code() == 429) {
+ response.close();
+
+ throw new ReCaptchaException("reCaptcha Challenge requested", url);
+ }
+
+ final ResponseBody body = response.body();
+ String responseBodyToReturn = null;
+
+ if (body != null) {
+ responseBodyToReturn = body.string();
+ }
+
+ final String latestUrl = response.request().url().toString();
+ 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 eb5e92e889d..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,8 +39,9 @@ public InputStream getStream(String imageUri, Object extra) throws IOException {
}
}
- protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
- final Downloader downloader = (Downloader) NewPipe.getDownloader();
+ 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 c24d77d0328..e6269dd5fb4 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -29,14 +29,18 @@
import android.os.Looper;
import android.preference.PreferenceManager;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.Menu;
-import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
+import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -47,12 +51,14 @@
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
import com.google.android.material.navigation.NavigationView;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
+import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
@@ -60,30 +66,39 @@
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.KioskTranslator;
+import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
+import org.schabi.newpipe.util.PeertubeHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver;
+import org.schabi.newpipe.util.TLSSocketFactoryCompat;
import org.schabi.newpipe.util.ThemeHelper;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
+
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
- private ActionBarDrawerToggle toggle = null;
- private DrawerLayout drawer = null;
- private NavigationView drawerItems = null;
- private TextView headerServiceView = null;
- private Button toggleServiceButton = null;
+ private ActionBarDrawerToggle toggle;
+ private DrawerLayout drawer;
+ private NavigationView drawerItems;
+ private ImageView headerServiceIcon;
+ private TextView headerServiceView;
+ private Button toggleServiceButton;
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;
@@ -94,20 +109,30 @@ 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) {
+ TLSSocketFactoryCompat.setAsDefault();
+ }
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
+ assureCorrectAppLanguage(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
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();
}
@@ -132,16 +157,18 @@ 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_whats_new)
+ .add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.rss));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
@@ -161,20 +188,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)) {
@@ -187,7 +215,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);
@@ -210,19 +238,21 @@ 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;
case ITEM_ID_FEED:
- NavigationHelper.openWhatsNewFragment(getSupportFragmentManager());
+ NavigationHelper.openFeedFragment(getSupportFragmentManager());
break;
case ITEM_ID_BOOKMARKS:
NavigationHelper.openBookmarksFragment(getSupportFragmentManager());
@@ -240,19 +270,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;
@@ -264,14 +295,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();
- });
+ 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() {
@@ -281,8 +325,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 {
@@ -294,21 +337,72 @@ private void toggleServices() {
}
private void showServices() {
- serviceArrow.setImageResource(R.drawable.ic_arrow_up_white);
+ 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)" : "");
- drawerItems.getMenu()
+ 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) {
+ enhancePeertubeMenu(s, menuItem);
+ }
}
- drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true);
+ drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this))
+ .setChecked(true);
+ }
+
+ 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);
+ List instances = PeertubeHelper.getInstanceList(this);
+ List items = new ArrayList<>();
+ int defaultSelect = 0;
+ for (PeertubeInstance instance : instances) {
+ items.add(instance.getName());
+ if (instance.getUrl().equals(currentInstace.getUrl())) {
+ defaultSelect = items.size() - 1;
+ }
+ }
+ 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(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;
+ }
+ PeertubeHelper.selectInstance(newInstance, getApplicationContext());
+ changeService(menuItem);
+ drawer.closeDrawers();
+ new Handler(Looper.getMainLooper()).postDelayed(() -> {
+ getSupportFragmentManager().popBackStack(null,
+ FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ recreate();
+ }, 300);
+ }
+
+ @Override
+ public void onNothingSelected(final AdapterView> parent) {
+
+ }
+ });
+ menuItem.setActionView(spinner);
}
private void showTabs() throws ExtractionException {
- serviceArrow.setImageResource(R.drawable.ic_arrow_down_white);
+ serviceArrow.setImageResource(R.drawable.ic_arrow_drop_down_white_24dp);
//Tabs
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
@@ -318,16 +412,17 @@ 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()
.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_whats_new)
+ .add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.rss));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
@@ -358,15 +453,22 @@ protected void onDestroy() {
@Override
protected void onResume() {
+ assureCorrectAppLanguage(this);
+ // 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 {
- String selectedServiceName = NewPipe.getService(
- ServiceHelper.getSelectedServiceId(this)).getServiceInfo().getName();
+ final int selectedServiceId = ServiceHelper.getSelectedServiceId(this);
+ final String selectedServiceName = NewPipe.getService(selectedServiceId)
+ .getServiceInfo().getName();
headerServiceView.setText(selectedServiceName);
+ headerServiceIcon.setImageResource(ServiceHelper.getIcon(selectedServiceId));
+
+ headerServiceView.post(() -> headerServiceView.setSelected(true));
toggleServiceButton.setContentDescription(
getString(R.string.drawer_header_description) + selectedServiceName);
} catch (Exception e) {
@@ -375,28 +477,42 @@ 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);
}
+
+ final boolean isHistoryEnabled = sharedPreferences.getBoolean(
+ getString(R.string.enable_watch_history_key), true);
+ drawerItems.getMenu().findItem(ITEM_ID_HISTORY).setVisible(isHistoryEnabled);
}
@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);
@@ -406,24 +522,32 @@ protected void onNewIntent(Intent intent) {
@Override
public void onBackPressed() {
- if (DEBUG) Log.d(TAG, "onBackPressed() called");
+ if (DEBUG) {
+ Log.d(TAG, "onBackPressed() called");
+ }
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;
}
}
@@ -432,7 +556,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();
}
@@ -477,8 +602,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);
@@ -487,10 +614,8 @@ public boolean onCreateOptionsMenu(Menu menu) {
}
if (!(fragment instanceof SearchFragment)) {
- findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container).setVisibility(View.GONE);
-
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.main_menu, menu);
+ findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container)
+ .setVisibility(View.GONE);
}
ActionBar actionBar = getSupportActionBar();
@@ -504,22 +629,16 @@ 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) {
case android.R.id.home:
onHomeButtonPressed();
return true;
- case R.id.action_show_downloads:
- return NavigationHelper.openDownloads(this);
- case R.id.action_history:
- NavigationHelper.openStatisticFragment(getSupportFragmentManager());
- return true;
- case R.id.action_settings:
- NavigationHelper.openSettings(this);
- return true;
default:
return super.onOptionsItemSelected(item);
}
@@ -530,11 +649,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());
+ }
}
/*//////////////////////////////////////////////////////////////////////////
@@ -542,12 +665,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) {
@@ -562,26 +687,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(),
@@ -598,7 +720,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 acb7339ab57..a43129585f2 100644
--- a/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java
+++ b/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java
@@ -1,43 +1,55 @@
package org.schabi.newpipe;
-import androidx.room.Room;
import android.content.Context;
+import android.database.Cursor;
+
import androidx.annotation.NonNull;
+import androidx.room.Room;
import org.schabi.newpipe.database.AppDatabase;
import static org.schabi.newpipe.database.AppDatabase.DATABASE_NAME;
-import static org.schabi.newpipe.database.Migrations.MIGRATION_11_12;
-import static org.schabi.newpipe.database.Migrations.MIGRATION_12_13;
+import static org.schabi.newpipe.database.Migrations.MIGRATION_1_2;
+import static org.schabi.newpipe.database.Migrations.MIGRATION_2_3;
+import static org.schabi.newpipe.database.Migrations.MIGRATION_3_4;
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_11_12, MIGRATION_12_13)
- .fallbackToDestructiveMigration()
+ .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
.build();
}
@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;
}
}
}
return result;
}
+
+ public static void checkpoint() {
+ if (databaseInstance == null) {
+ throw new IllegalStateException("database is not initialized");
+ }
+ Cursor c = databaseInstance.query("pragma wal_checkpoint(full)", null);
+ if (c.moveToFirst() && c.getInt(0) == 1) {
+ throw new RuntimeException("Checkpoint was blocked from completing");
+ }
+ }
}
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 7f6af89c18c..a8a83e13e1f 100644
--- a/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java
@@ -1,20 +1,24 @@
package org.schabi.newpipe;
-import android.app.Activity;
import android.content.Intent;
-import android.graphics.Bitmap;
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.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.app.NavUtils;
+
+import org.schabi.newpipe.util.ThemeHelper;
+
/*
* Created by beneth on 06.12.16.
*
@@ -37,126 +41,135 @@
public class ReCaptchaActivity extends AppCompatActivity {
public static final int RECAPTCHA_REQUEST = 10;
public static final String RECAPTCHA_URL_EXTRA = "recaptcha_url_extra";
-
public static final String TAG = ReCaptchaActivity.class.toString();
public static final String YT_URL = "https://www.youtube.com";
- private String url;
+ private WebView webView;
+ 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);
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
- url = getIntent().getStringExtra(RECAPTCHA_URL_EXTRA);
+ String url = getIntent().getStringExtra(RECAPTCHA_URL_EXTRA);
if (url == null || url.isEmpty()) {
url = YT_URL;
}
-
- // Set return to Cancel by default
+ // set return to Cancel by default
setResult(RESULT_CANCELED);
- Toolbar toolbar = findViewById(R.id.toolbar);
- setSupportActionBar(toolbar);
- ActionBar actionBar = getSupportActionBar();
- if (actionBar != null) {
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setTitle(R.string.reCaptcha_title);
- actionBar.setDisplayShowTitleEnabled(true);
- }
-
- WebView myWebView = findViewById(R.id.reCaptchaWebView);
+ webView = findViewById(R.id.reCaptchaWebView);
- // Enable Javascript
- WebSettings webSettings = myWebView.getSettings();
+ // enable Javascript
+ WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
- ReCaptchaWebViewClient webClient = new ReCaptchaWebViewClient(this);
- myWebView.setWebViewClient(webClient);
+ webView.setWebViewClient(new WebViewClient() {
+ @Override
+ public void onPageFinished(final WebView view, final String url) {
+ super.onPageFinished(view, url);
+ handleCookies(url);
+ }
+ });
- // Cleaning cache, history and cookies from webView
- myWebView.clearCache(true);
- myWebView.clearHistory();
+ // cleaning cache, history and cookies from webView
+ webView.clearCache(true);
+ 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();
}
- myWebView.loadUrl(url);
+ webView.loadUrl(url);
}
- private class ReCaptchaWebViewClient extends WebViewClient {
- private final Activity context;
- private String mCookies;
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ getMenuInflater().inflate(R.menu.menu_recaptcha, menu);
- ReCaptchaWebViewClient(Activity ctx) {
- context = ctx;
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(false);
+ actionBar.setTitle(R.string.title_activity_recaptcha);
+ actionBar.setSubtitle(R.string.subtitle_activity_recaptcha);
}
- @Override
- public void onPageStarted(WebView view, String url, Bitmap favicon) {
- // TODO: Start Loader
- super.onPageStarted(view, url, favicon);
+ return true;
+ }
+
+ @Override
+ public void onBackPressed() {
+ saveCookiesAndFinish();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ int id = item.getItemId();
+ switch (id) {
+ case R.id.menu_item_done:
+ saveCookiesAndFinish();
+ return true;
+ default:
+ return false;
}
+ }
- @Override
- public void onPageFinished(WebView view, String url) {
- String cookies = CookieManager.getInstance().getCookie(url);
+ private void saveCookiesAndFinish() {
+ handleCookies(webView.getUrl()); // try to get cookies of unclosed page
+ if (!foundCookies.isEmpty()) {
+ // give cookies to Downloader class
+ DownloaderImpl.getInstance().setCookies(foundCookies);
+ setResult(RESULT_OK);
+ }
- // TODO: Stop Loader
+ Intent intent = new Intent(this, org.schabi.newpipe.MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ NavUtils.navigateUpTo(this, intent);
+ }
- // find cookies : s_gl & goojf and Add cookies to Downloader
- if (find_access_cookies(cookies)) {
- // Give cookies to Downloader class
- Downloader.getInstance().setCookies(mCookies);
- // Closing activity and return to parent
- setResult(RESULT_OK);
- finish();
- }
+ private void handleCookies(final 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;
}
- private boolean find_access_cookies(String cookies) {
- boolean ret = false;
- String c_s_gl = "";
- String c_goojf = "";
-
- String[] parts = cookies.split("; ");
- for (String part : parts) {
- if (part.trim().startsWith("s_gl")) {
- c_s_gl = part.trim();
- }
- if (part.trim().startsWith("goojf")) {
- c_goojf = part.trim();
- }
- }
- if (c_s_gl.length() > 0 && c_goojf.length() > 0) {
- ret = true;
- //mCookies = c_s_gl + "; " + c_goojf;
- // Youtube seems to also need the other cookies:
- mCookies = cookies;
- }
+ addYoutubeCookies(cookies);
+ // add other methods to extract cookies here
+ }
- return ret;
+ private void addYoutubeCookies(@NonNull final String cookies) {
+ if (cookies.contains("s_gl=") || cookies.contains("goojf=")
+ || cookies.contains("VISITOR_INFO1_LIVE=")) {
+ // youtube seems to also need the other cookies:
+ addCookie(cookies);
}
}
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- int id = item.getItemId();
- switch (id) {
- case android.R.id.home: {
- Intent intent = new Intent(this, org.schabi.newpipe.MainActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- NavUtils.navigateUpTo(this, intent);
- return true;
- }
- default:
- return false;
+ private void addCookie(final String cookie) {
+ if (foundCookies.contains(cookie)) {
+ return;
+ }
+
+ if (foundCookies.isEmpty() || foundCookies.endsWith("; ")) {
+ foundCookies += cookie;
+ } else if (foundCookies.endsWith(";")) {
+ foundCookies += " " + cookie;
+ } else {
+ foundCookies += "; " + cookie;
}
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
index 1be6e096a22..bb24c9681e6 100644
--- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
@@ -9,12 +9,6 @@
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.preference.PreferenceManager;
-import androidx.annotation.DrawableRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.app.NotificationCompat;
-import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -26,6 +20,12 @@
import android.widget.RadioGroup;
import android.widget.Toast;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.NotificationCompat;
import androidx.fragment.app.FragmentManager;
import org.schabi.newpipe.download.DownloadDialog;
@@ -49,6 +49,7 @@
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
+import org.schabi.newpipe.util.urlfinder.UrlFinder;
import java.io.Serializable;
import java.util.ArrayList;
@@ -72,29 +73,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);
@@ -107,14 +110,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);
}
@@ -133,7 +136,7 @@ protected void onDestroy() {
disposables.clear();
}
- private void handleUrl(String url) {
+ private void handleUrl(final String url) {
disposables.add(Observable
.fromCallable(() -> {
if (currentServiceId == -1) {
@@ -158,13 +161,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();
@@ -176,8 +180,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);
@@ -187,7 +194,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:
@@ -205,20 +213,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) {
@@ -240,7 +254,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) -> {
@@ -251,7 +266,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();
}
};
@@ -262,7 +279,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();
@@ -271,10 +290,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;
@@ -286,18 +308,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);
@@ -318,46 +343,58 @@ private void showDialog(final List choices) {
alertDialog.show();
}
- 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);
@@ -373,21 +410,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();
}
@@ -415,7 +456,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);
@@ -428,12 +470,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);
@@ -451,7 +492,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();
@@ -463,12 +506,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;
@@ -477,10 +581,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;
@@ -493,14 +599,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() {
@@ -514,16 +616,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;
@@ -550,22 +656,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;
@@ -591,7 +702,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);
@@ -608,7 +721,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() {
@@ -616,8 +731,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,78 +742,18 @@ private NotificationCompat.Builder createNotification() {
// Utils
//////////////////////////////////////////////////////////////////////////*/
- /**
- * Removes invisible separators (\p{Z}) and punctuation characters including
- * brackets (\p{P}). See http://www.regular-expressions.info/unicode.html for
- * more details.
- */
- private final static String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]";
-
- private String getUrl(Intent intent) {
- // first gather data and find service
- String videoUrl = null;
+ @Nullable
+ private String getUrl(final Intent intent) {
+ String foundUrl = null;
if (intent.getData() != null) {
- // this means the video was called though another app
- videoUrl = intent.getData().toString();
+ // Called from another app
+ foundUrl = intent.getData().toString();
} else if (intent.getStringExtra(Intent.EXTRA_TEXT) != null) {
- //this means that vidoe was called through share menu
- String extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
- final String[] uris = getUris(extraText);
- videoUrl = uris.length > 0 ? uris[0] : null;
- }
-
- return videoUrl;
- }
-
- 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, input.length());
- }
-
- 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, output.length()).matches(REGEX_REMOVE_FROM_URL)) {
- output = output.substring(0, output.length() - 1);
- }
- return output;
+ // Called from the share menu
+ final String extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
+ foundUrl = UrlFinder.firstUrlFromInput(extraText);
}
- }
- /**
- * 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()]);
+ return foundUrl;
}
}
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 2326e795ef1..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,45 +4,65 @@
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;
-import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;
-public class AboutActivity extends AppCompatActivity {
+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", "Peter Cai", "https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL2),
- new SoftwareComponent("NewPipe Extractor", "2017", "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 - 2017", "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-2017", "Google Inc", "https://github.com/google/ExoPlayer", StandardLicenses.APACHE2),
- new SoftwareComponent("RxAndroid", "2015", "The RxAndroid authors", "https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2),
- new SoftwareComponent("RxJava", "2016-present", "RxJava Contributors", "https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2),
- new SoftwareComponent("RxBinding", "2015", "Jake Wharton", "https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2)
+ 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)
};
/**
@@ -61,9 +81,11 @@ 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);
+ this.setTitle(getString(R.string.title_activity_about));
setContentView(R.layout.activity_about);
@@ -82,28 +104,14 @@ protected void onCreate(Bundle savedInstanceState) {
tabLayout.setupWithViewPager(mViewPager);
}
-
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.menu_about, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
-
+ public boolean onOptionsItemSelected(final MenuItem item) {
int id = item.getItemId();
switch (id) {
case android.R.id.home:
finish();
return true;
- case R.id.action_settings:
- NavigationHelper.openSettings(this);
- return true;
- case R.id.action_show_downloads:
- return NavigationHelper.openDownloads(this);
}
return super.onOptionsItemSelected(item);
@@ -113,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();
@@ -135,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();
@@ -185,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 eeafc1f57b6..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,85 +2,53 @@
import android.app.Activity;
import android.content.Context;
-import android.content.DialogInterface;
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;
-public class LicenseFragmentHelper extends AsyncTask