Improve code style to be more consistent
This commit is contained in:
parent
819e52cab3
commit
fda5405e48
244 changed files with 10116 additions and 7222 deletions
|
|
@ -24,48 +24,51 @@ import android.animation.AnimatorListenerAdapter;
|
|||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.res.ColorStateList;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.FloatRange;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.FloatRange;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
|
||||
public class AnimationUtils {
|
||||
public final class AnimationUtils {
|
||||
private static final String TAG = "AnimationUtils";
|
||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||
|
||||
public enum Type {
|
||||
ALPHA,
|
||||
SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA,
|
||||
SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA
|
||||
}
|
||||
private AnimationUtils() { }
|
||||
|
||||
public static void animateView(View view, boolean enterOrExit, long duration) {
|
||||
public static void animateView(final View view, final boolean enterOrExit,
|
||||
final long duration) {
|
||||
animateView(view, Type.ALPHA, enterOrExit, duration, 0, null);
|
||||
}
|
||||
|
||||
public static void animateView(View view, boolean enterOrExit, long duration, long delay) {
|
||||
public static void animateView(final View view, final boolean enterOrExit,
|
||||
final long duration, final long delay) {
|
||||
animateView(view, Type.ALPHA, enterOrExit, duration, delay, null);
|
||||
}
|
||||
|
||||
public static void animateView(View view, boolean enterOrExit, long duration, long delay, Runnable execOnEnd) {
|
||||
public static void animateView(final View view, final boolean enterOrExit, final long duration,
|
||||
final long delay, final Runnable execOnEnd) {
|
||||
animateView(view, Type.ALPHA, enterOrExit, duration, delay, execOnEnd);
|
||||
}
|
||||
|
||||
public static void animateView(View view, Type animationType, boolean enterOrExit, long duration) {
|
||||
public static void animateView(final View view, final Type animationType,
|
||||
final boolean enterOrExit, final long duration) {
|
||||
animateView(view, animationType, enterOrExit, duration, 0, null);
|
||||
}
|
||||
|
||||
public static void animateView(View view, Type animationType, boolean enterOrExit, long duration, long delay) {
|
||||
public static void animateView(final View view, final Type animationType,
|
||||
final boolean enterOrExit, final long duration,
|
||||
final long delay) {
|
||||
animateView(view, animationType, enterOrExit, duration, delay, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate the view
|
||||
* Animate the view.
|
||||
*
|
||||
* @param view view that will be animated
|
||||
* @param animationType {@link Type} of the animation
|
||||
|
|
@ -74,7 +77,9 @@ public class AnimationUtils {
|
|||
* @param delay how long the animation will wait to start, in milliseconds
|
||||
* @param execOnEnd runnable that will be executed when the animation ends
|
||||
*/
|
||||
public static void animateView(final View view, Type animationType, boolean enterOrExit, long duration, long delay, Runnable execOnEnd) {
|
||||
public static void animateView(final View view, final Type animationType,
|
||||
final boolean enterOrExit, final long duration,
|
||||
final long delay, final Runnable execOnEnd) {
|
||||
if (DEBUG) {
|
||||
String id;
|
||||
try {
|
||||
|
|
@ -83,24 +88,33 @@ public class AnimationUtils {
|
|||
id = view.getId() + "";
|
||||
}
|
||||
|
||||
String msg = String.format("%8s → [%s:%s] [%s %s:%s] execOnEnd=%s",
|
||||
enterOrExit, view.getClass().getSimpleName(), id, animationType, duration, delay, execOnEnd);
|
||||
String msg = String.format("%8s → [%s:%s] [%s %s:%s] execOnEnd=%s", enterOrExit,
|
||||
view.getClass().getSimpleName(), id, animationType, duration, delay, execOnEnd);
|
||||
Log.d(TAG, "animateView()" + msg);
|
||||
}
|
||||
|
||||
if (view.getVisibility() == View.VISIBLE && enterOrExit) {
|
||||
if (DEBUG) Log.d(TAG, "animateView() view was already visible > view = [" + view + "]");
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "animateView() view was already visible > view = [" + view + "]");
|
||||
}
|
||||
view.animate().setListener(null).cancel();
|
||||
view.setVisibility(View.VISIBLE);
|
||||
view.setAlpha(1f);
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
if (execOnEnd != null) {
|
||||
execOnEnd.run();
|
||||
}
|
||||
return;
|
||||
} else if ((view.getVisibility() == View.GONE || view.getVisibility() == View.INVISIBLE) && !enterOrExit) {
|
||||
if (DEBUG) Log.d(TAG, "animateView() view was already gone > view = [" + view + "]");
|
||||
} else if ((view.getVisibility() == View.GONE || view.getVisibility() == View.INVISIBLE)
|
||||
&& !enterOrExit) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "animateView() view was already gone > view = [" + view + "]");
|
||||
}
|
||||
view.animate().setListener(null).cancel();
|
||||
view.setVisibility(View.GONE);
|
||||
view.setAlpha(0f);
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
if (execOnEnd != null) {
|
||||
execOnEnd.run();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -126,33 +140,44 @@ public class AnimationUtils {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Animate the background color of a view
|
||||
* Animate the background color of a view.
|
||||
*
|
||||
* @param view the view to animate
|
||||
* @param duration the duration of the animation
|
||||
* @param colorStart the background color to start with
|
||||
* @param colorEnd the background color to end with
|
||||
*/
|
||||
public static void animateBackgroundColor(final View view, long duration, @ColorInt final int colorStart, @ColorInt final int colorEnd) {
|
||||
public static void animateBackgroundColor(final View view, final long duration,
|
||||
@ColorInt final int colorStart,
|
||||
@ColorInt final int colorEnd) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "animateBackgroundColor() called with: view = [" + view + "], duration = [" + duration + "], colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]");
|
||||
Log.d(TAG, "animateBackgroundColor() called with: "
|
||||
+ "view = [" + view + "], duration = [" + duration + "], "
|
||||
+ "colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]");
|
||||
}
|
||||
|
||||
final int[][] EMPTY = new int[][]{new int[0]};
|
||||
ValueAnimator viewPropertyAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), colorStart, colorEnd);
|
||||
final int[][] empty = new int[][]{new int[0]};
|
||||
ValueAnimator viewPropertyAnimator = ValueAnimator
|
||||
.ofObject(new ArgbEvaluator(), colorStart, colorEnd);
|
||||
viewPropertyAnimator.setInterpolator(new FastOutSlowInInterpolator());
|
||||
viewPropertyAnimator.setDuration(duration);
|
||||
viewPropertyAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
ViewCompat.setBackgroundTintList(view, new ColorStateList(EMPTY, new int[]{(int) animation.getAnimatedValue()}));
|
||||
public void onAnimationUpdate(final ValueAnimator animation) {
|
||||
ViewCompat.setBackgroundTintList(view,
|
||||
new ColorStateList(empty, new int[]{(int) animation.getAnimatedValue()}));
|
||||
}
|
||||
});
|
||||
viewPropertyAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
ViewCompat.setBackgroundTintList(view, new ColorStateList(EMPTY, new int[]{colorEnd}));
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
ViewCompat.setBackgroundTintList(view,
|
||||
new ColorStateList(empty, new int[]{colorEnd}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
public void onAnimationCancel(final Animator animation) {
|
||||
onAnimationEnd(animation);
|
||||
}
|
||||
});
|
||||
|
|
@ -160,40 +185,52 @@ public class AnimationUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Animate the text color of any view that extends {@link TextView} (Buttons, EditText...)
|
||||
* Animate the text color of any view that extends {@link TextView} (Buttons, EditText...).
|
||||
*
|
||||
* @param view the text view to animate
|
||||
* @param duration the duration of the animation
|
||||
* @param colorStart the text color to start with
|
||||
* @param colorEnd the text color to end with
|
||||
*/
|
||||
public static void animateTextColor(final TextView view, long duration, @ColorInt final int colorStart, @ColorInt final int colorEnd) {
|
||||
public static void animateTextColor(final TextView view, final long duration,
|
||||
@ColorInt final int colorStart,
|
||||
@ColorInt final int colorEnd) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "animateTextColor() called with: view = [" + view + "], duration = [" + duration + "], colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]");
|
||||
Log.d(TAG, "animateTextColor() called with: "
|
||||
+ "view = [" + view + "], duration = [" + duration + "], "
|
||||
+ "colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]");
|
||||
}
|
||||
|
||||
ValueAnimator viewPropertyAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), colorStart, colorEnd);
|
||||
ValueAnimator viewPropertyAnimator = ValueAnimator
|
||||
.ofObject(new ArgbEvaluator(), colorStart, colorEnd);
|
||||
viewPropertyAnimator.setInterpolator(new FastOutSlowInInterpolator());
|
||||
viewPropertyAnimator.setDuration(duration);
|
||||
viewPropertyAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
public void onAnimationUpdate(final ValueAnimator animation) {
|
||||
view.setTextColor((int) animation.getAnimatedValue());
|
||||
}
|
||||
});
|
||||
viewPropertyAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
view.setTextColor(colorEnd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
public void onAnimationCancel(final Animator animation) {
|
||||
view.setTextColor(colorEnd);
|
||||
}
|
||||
});
|
||||
viewPropertyAnimator.start();
|
||||
}
|
||||
|
||||
public static ValueAnimator animateHeight(final View view, long duration, int targetHeight) {
|
||||
public static ValueAnimator animateHeight(final View view, final long duration,
|
||||
final int targetHeight) {
|
||||
final int height = view.getHeight();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "animateHeight: duration = [" + duration + "], from " + height + " to → " + targetHeight + " in: " + view);
|
||||
Log.d(TAG, "animateHeight: duration = [" + duration + "], "
|
||||
+ "from " + height + " to → " + targetHeight + " in: " + view);
|
||||
}
|
||||
|
||||
ValueAnimator animator = ValueAnimator.ofFloat(height, targetHeight);
|
||||
|
|
@ -206,13 +243,13 @@ public class AnimationUtils {
|
|||
});
|
||||
animator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
view.getLayoutParams().height = targetHeight;
|
||||
view.requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
public void onAnimationCancel(final Animator animation) {
|
||||
view.getLayoutParams().height = targetHeight;
|
||||
view.requestLayout();
|
||||
}
|
||||
|
|
@ -222,155 +259,211 @@ public class AnimationUtils {
|
|||
return animator;
|
||||
}
|
||||
|
||||
public static void animateRotation(final View view, long duration, int targetRotation) {
|
||||
public static void animateRotation(final View view, final long duration,
|
||||
final int targetRotation) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "animateRotation: duration = [" + duration + "], from " + view.getRotation() + " to → " + targetRotation + " in: " + view);
|
||||
Log.d(TAG, "animateRotation: duration = [" + duration + "], "
|
||||
+ "from " + view.getRotation() + " to → " + targetRotation + " in: " + view);
|
||||
}
|
||||
view.animate().setListener(null).cancel();
|
||||
|
||||
view.animate().rotation(targetRotation).setDuration(duration).setInterpolator(new FastOutSlowInInterpolator())
|
||||
view.animate()
|
||||
.rotation(targetRotation).setDuration(duration)
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
public void onAnimationCancel(final Animator animation) {
|
||||
view.setRotation(targetRotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
view.setRotation(targetRotation);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private static void animateAlpha(final View view, final boolean enterOrExit,
|
||||
final long duration, final long delay,
|
||||
final Runnable execOnEnd) {
|
||||
if (enterOrExit) {
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f)
|
||||
.setDuration(duration).setStartDelay(delay)
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
if (execOnEnd != null) {
|
||||
execOnEnd.run();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f)
|
||||
.setDuration(duration).setStartDelay(delay)
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
view.setVisibility(View.GONE);
|
||||
if (execOnEnd != null) {
|
||||
execOnEnd.run();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Internals
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private static void animateAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
|
||||
if (enterOrExit) {
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
view.setVisibility(View.GONE);
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private static void animateScaleAndAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
|
||||
private static void animateScaleAndAlpha(final View view, final boolean enterOrExit,
|
||||
final long duration, final long delay,
|
||||
final Runnable execOnEnd) {
|
||||
if (enterOrExit) {
|
||||
view.setScaleX(.8f);
|
||||
view.setScaleY(.8f);
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).scaleX(1f).scaleY(1f)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
view.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.alpha(1f).scaleX(1f).scaleY(1f)
|
||||
.setDuration(duration).setStartDelay(delay)
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
if (execOnEnd != null) {
|
||||
execOnEnd.run();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
view.setScaleX(1f);
|
||||
view.setScaleY(1f);
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f).scaleX(.8f).scaleY(.8f)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
view.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.alpha(0f).scaleX(.8f).scaleY(.8f)
|
||||
.setDuration(duration).setStartDelay(delay)
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
view.setVisibility(View.GONE);
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
if (execOnEnd != null) {
|
||||
execOnEnd.run();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private static void animateLightScaleAndAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
|
||||
private static void animateLightScaleAndAlpha(final View view, final boolean enterOrExit,
|
||||
final long duration, final long delay,
|
||||
final Runnable execOnEnd) {
|
||||
if (enterOrExit) {
|
||||
view.setAlpha(.5f);
|
||||
view.setScaleX(.95f);
|
||||
view.setScaleY(.95f);
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).scaleX(1f).scaleY(1f)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
view.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.alpha(1f).scaleX(1f).scaleY(1f)
|
||||
.setDuration(duration).setStartDelay(delay)
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
if (execOnEnd != null) {
|
||||
execOnEnd.run();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
view.setAlpha(1f);
|
||||
view.setScaleX(1f);
|
||||
view.setScaleY(1f);
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f).scaleX(.95f).scaleY(.95f)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
view.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.alpha(0f).scaleX(.95f).scaleY(.95f)
|
||||
.setDuration(duration).setStartDelay(delay)
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
view.setVisibility(View.GONE);
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
if (execOnEnd != null) {
|
||||
execOnEnd.run();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private static void animateSlideAndAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
|
||||
private static void animateSlideAndAlpha(final View view, final boolean enterOrExit,
|
||||
final long duration, final long delay,
|
||||
final Runnable execOnEnd) {
|
||||
if (enterOrExit) {
|
||||
view.setTranslationY(-view.getHeight());
|
||||
view.setAlpha(0f);
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).translationY(0)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
view.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).translationY(0)
|
||||
.setDuration(duration).setStartDelay(delay)
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
if (execOnEnd != null) {
|
||||
execOnEnd.run();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f).translationY(-view.getHeight())
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
view.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.alpha(0f).translationY(-view.getHeight())
|
||||
.setDuration(duration).setStartDelay(delay)
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
view.setVisibility(View.GONE);
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
if (execOnEnd != null) {
|
||||
execOnEnd.run();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private static void animateLightSlideAndAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
|
||||
private static void animateLightSlideAndAlpha(final View view, final boolean enterOrExit,
|
||||
final long duration, final long delay,
|
||||
final Runnable execOnEnd) {
|
||||
if (enterOrExit) {
|
||||
view.setTranslationY(-view.getHeight() / 2);
|
||||
view.setAlpha(0f);
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).translationY(0)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
view.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).translationY(0)
|
||||
.setDuration(duration).setStartDelay(delay)
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
if (execOnEnd != null) {
|
||||
execOnEnd.run();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f).translationY(-view.getHeight() / 2)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator())
|
||||
.alpha(0f).translationY(-view.getHeight() / 2)
|
||||
.setDuration(duration).setStartDelay(delay)
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
view.setVisibility(View.GONE);
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
if (execOnEnd != null) {
|
||||
execOnEnd.run();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
public static void slideUp(final View view,
|
||||
long duration,
|
||||
long delay,
|
||||
@FloatRange(from = 0.0f, to = 1.0f) float translationPercent) {
|
||||
int translationY = (int) (view.getResources().getDisplayMetrics().heightPixels *
|
||||
(translationPercent));
|
||||
public static void slideUp(final View view, final long duration, final long delay,
|
||||
@FloatRange(from = 0.0f, to = 1.0f)
|
||||
final float translationPercent) {
|
||||
int translationY = (int) (view.getResources().getDisplayMetrics().heightPixels
|
||||
* (translationPercent));
|
||||
|
||||
view.animate().setListener(null).cancel();
|
||||
view.setAlpha(0f);
|
||||
|
|
@ -384,4 +477,10 @@ public class AnimationUtils {
|
|||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.start();
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
ALPHA,
|
||||
SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA,
|
||||
SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@ import android.graphics.Bitmap;
|
|||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class BitmapUtils {
|
||||
public final class BitmapUtils {
|
||||
private BitmapUtils() { }
|
||||
|
||||
@Nullable
|
||||
public static Bitmap centerCrop(Bitmap inputBitmap, int newWidth, int newHeight) {
|
||||
public static Bitmap centerCrop(final Bitmap inputBitmap, final int newWidth,
|
||||
final int newHeight) {
|
||||
if (inputBitmap == null || inputBitmap.isRecycled()) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,14 +28,13 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
|
|||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class CommentTextOnTouchListener implements View.OnTouchListener {
|
||||
|
||||
public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener();
|
||||
|
||||
private static final Pattern timestampPattern = Pattern.compile("(.*)#timestamp=(\\d+)");
|
||||
private static final Pattern TIMESTAMP_PATTERN = Pattern.compile("(.*)#timestamp=(\\d+)");
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if(!(v instanceof TextView)){
|
||||
public boolean onTouch(final View v, final MotionEvent event) {
|
||||
if (!(v instanceof TextView)) {
|
||||
return false;
|
||||
}
|
||||
TextView widget = (TextView) v;
|
||||
|
|
@ -66,10 +65,12 @@ public class CommentTextOnTouchListener implements View.OnTouchListener {
|
|||
if (link.length != 0) {
|
||||
if (action == MotionEvent.ACTION_UP) {
|
||||
boolean handled = false;
|
||||
if(link[0] instanceof URLSpan){
|
||||
if (link[0] instanceof URLSpan) {
|
||||
handled = handleUrl(v.getContext(), (URLSpan) link[0]);
|
||||
}
|
||||
if(!handled) link[0].onClick(widget);
|
||||
if (!handled) {
|
||||
link[0].onClick(widget);
|
||||
}
|
||||
} else if (action == MotionEvent.ACTION_DOWN) {
|
||||
Selection.setSelection(buffer,
|
||||
buffer.getSpanStart(link[0]),
|
||||
|
|
@ -78,17 +79,15 @@ public class CommentTextOnTouchListener implements View.OnTouchListener {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean handleUrl(Context context, URLSpan urlSpan) {
|
||||
private boolean handleUrl(final Context context, final URLSpan urlSpan) {
|
||||
String url = urlSpan.getURL();
|
||||
int seconds = -1;
|
||||
Matcher matcher = timestampPattern.matcher(url);
|
||||
if(matcher.matches()){
|
||||
Matcher matcher = TIMESTAMP_PATTERN.matcher(url);
|
||||
if (matcher.matches()) {
|
||||
url = matcher.group(1);
|
||||
seconds = Integer.parseInt(matcher.group(2));
|
||||
}
|
||||
|
|
@ -100,18 +99,19 @@ public class CommentTextOnTouchListener implements View.OnTouchListener {
|
|||
} catch (ExtractionException e) {
|
||||
return false;
|
||||
}
|
||||
if(linkType == StreamingService.LinkType.NONE){
|
||||
if (linkType == StreamingService.LinkType.NONE) {
|
||||
return false;
|
||||
}
|
||||
if(linkType == StreamingService.LinkType.STREAM && seconds != -1){
|
||||
if (linkType == StreamingService.LinkType.STREAM && seconds != -1) {
|
||||
return playOnPopup(context, url, service, seconds);
|
||||
}else{
|
||||
} else {
|
||||
NavigationHelper.openRouterActivity(context, url);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean playOnPopup(Context context, String url, StreamingService service, int seconds) {
|
||||
private boolean playOnPopup(final Context context, final String url,
|
||||
final StreamingService service, final int seconds) {
|
||||
LinkHandlerFactory factory = service.getStreamLHFactory();
|
||||
String cleanUrl = null;
|
||||
try {
|
||||
|
|
@ -123,7 +123,7 @@ public class CommentTextOnTouchListener implements View.OnTouchListener {
|
|||
single.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(info -> {
|
||||
PlayQueue playQueue = new SinglePlayQueue((StreamInfo) info, seconds*1000);
|
||||
PlayQueue playQueue = new SinglePlayQueue((StreamInfo) info, seconds * 1000);
|
||||
NavigationHelper.playOnPopupPlayer(context, playQueue, false);
|
||||
});
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package org.schabi.newpipe.util;
|
||||
|
||||
public class Constants {
|
||||
public final class Constants {
|
||||
public static final String KEY_SERVICE_ID = "key_service_id";
|
||||
public static final String KEY_URL = "key_url";
|
||||
public static final String KEY_TITLE = "key_title";
|
||||
|
|
@ -12,4 +12,6 @@ public class Constants {
|
|||
public static final String KEY_MAIN_PAGE_CHANGE = "key_main_page_change";
|
||||
|
||||
public static final int NO_SERVICE_ID = -1;
|
||||
|
||||
private Constants() { }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,28 +61,27 @@ import io.reactivex.Single;
|
|||
|
||||
public final class ExtractorHelper {
|
||||
private static final String TAG = ExtractorHelper.class.getSimpleName();
|
||||
private static final InfoCache cache = InfoCache.getInstance();
|
||||
private static final InfoCache CACHE = InfoCache.getInstance();
|
||||
|
||||
private ExtractorHelper() {
|
||||
//no instance
|
||||
}
|
||||
|
||||
private static void checkServiceId(int serviceId) {
|
||||
private static void checkServiceId(final int serviceId) {
|
||||
if (serviceId == Constants.NO_SERVICE_ID) {
|
||||
throw new IllegalArgumentException("serviceId is NO_SERVICE_ID");
|
||||
}
|
||||
}
|
||||
|
||||
public static Single<SearchInfo> searchFor(final int serviceId,
|
||||
final String searchString,
|
||||
public static Single<SearchInfo> searchFor(final int serviceId, final String searchString,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(() ->
|
||||
SearchInfo.getInfo(NewPipe.getService(serviceId),
|
||||
NewPipe.getService(serviceId)
|
||||
.getSearchQHFactory()
|
||||
.fromQuery(searchString, contentFilter, sortFilter)));
|
||||
SearchInfo.getInfo(NewPipe.getService(serviceId),
|
||||
NewPipe.getService(serviceId)
|
||||
.getSearchQHFactory()
|
||||
.fromQuery(searchString, contentFilter, sortFilter)));
|
||||
}
|
||||
|
||||
public static Single<InfoItemsPage> getMoreSearchItems(final int serviceId,
|
||||
|
|
@ -94,14 +93,13 @@ public final class ExtractorHelper {
|
|||
return Single.fromCallable(() ->
|
||||
SearchInfo.getMoreItems(NewPipe.getService(serviceId),
|
||||
NewPipe.getService(serviceId)
|
||||
.getSearchQHFactory()
|
||||
.fromQuery(searchString, contentFilter, sortFilter),
|
||||
.getSearchQHFactory()
|
||||
.fromQuery(searchString, contentFilter, sortFilter),
|
||||
pageUrl));
|
||||
|
||||
}
|
||||
|
||||
public static Single<List<String>> suggestionsFor(final int serviceId,
|
||||
final String query) {
|
||||
public static Single<List<String>> suggestionsFor(final int serviceId, final String query) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(() -> {
|
||||
SuggestionExtractor extractor = NewPipe.getService(serviceId)
|
||||
|
|
@ -112,32 +110,30 @@ public final class ExtractorHelper {
|
|||
});
|
||||
}
|
||||
|
||||
public static Single<StreamInfo> getStreamInfo(final int serviceId,
|
||||
final String url,
|
||||
boolean forceLoad) {
|
||||
public static Single<StreamInfo> getStreamInfo(final int serviceId, final String url,
|
||||
final boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.STREAM, Single.fromCallable(() ->
|
||||
StreamInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.STREAM,
|
||||
Single.fromCallable(() -> StreamInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
}
|
||||
|
||||
public static Single<ChannelInfo> getChannelInfo(final int serviceId,
|
||||
final String url,
|
||||
boolean forceLoad) {
|
||||
public static Single<ChannelInfo> getChannelInfo(final int serviceId, final String url,
|
||||
final boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.CHANNEL, Single.fromCallable(() ->
|
||||
ChannelInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.CHANNEL,
|
||||
Single.fromCallable(() ->
|
||||
ChannelInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
}
|
||||
|
||||
public static Single<InfoItemsPage> getMoreChannelItems(final int serviceId,
|
||||
final String url,
|
||||
public static Single<InfoItemsPage> getMoreChannelItems(final int serviceId, final String url,
|
||||
final String nextStreamsUrl) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(() ->
|
||||
ChannelInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl));
|
||||
}
|
||||
|
||||
public static Single<ListInfo<StreamInfoItem>> getFeedInfoFallbackToChannelInfo(final int serviceId,
|
||||
final String url) {
|
||||
public static Single<ListInfo<StreamInfoItem>> getFeedInfoFallbackToChannelInfo(
|
||||
final int serviceId, final String url) {
|
||||
final Maybe<ListInfo<StreamInfoItem>> maybeFeedInfo = Maybe.fromCallable(() -> {
|
||||
final StreamingService service = NewPipe.getService(serviceId);
|
||||
final FeedExtractor feedExtractor = service.getFeedExtractor(url);
|
||||
|
|
@ -152,12 +148,12 @@ public final class ExtractorHelper {
|
|||
return maybeFeedInfo.switchIfEmpty(getChannelInfo(serviceId, url, true));
|
||||
}
|
||||
|
||||
public static Single<CommentsInfo> getCommentsInfo(final int serviceId,
|
||||
final String url,
|
||||
boolean forceLoad) {
|
||||
public static Single<CommentsInfo> getCommentsInfo(final int serviceId, final String url,
|
||||
final boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.COMMENT, Single.fromCallable(() ->
|
||||
CommentsInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.COMMENT,
|
||||
Single.fromCallable(() ->
|
||||
CommentsInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
}
|
||||
|
||||
public static Single<InfoItemsPage> getMoreCommentItems(final int serviceId,
|
||||
|
|
@ -168,32 +164,30 @@ public final class ExtractorHelper {
|
|||
CommentsInfo.getMoreItems(NewPipe.getService(serviceId), info, nextPageUrl));
|
||||
}
|
||||
|
||||
public static Single<PlaylistInfo> getPlaylistInfo(final int serviceId,
|
||||
final String url,
|
||||
boolean forceLoad) {
|
||||
public static Single<PlaylistInfo> getPlaylistInfo(final int serviceId, final String url,
|
||||
final boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST, Single.fromCallable(() ->
|
||||
PlaylistInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST,
|
||||
Single.fromCallable(() ->
|
||||
PlaylistInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
}
|
||||
|
||||
public static Single<InfoItemsPage> getMorePlaylistItems(final int serviceId,
|
||||
final String url,
|
||||
public static Single<InfoItemsPage> getMorePlaylistItems(final int serviceId, final String url,
|
||||
final String nextStreamsUrl) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(() ->
|
||||
PlaylistInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl));
|
||||
}
|
||||
|
||||
public static Single<KioskInfo> getKioskInfo(final int serviceId,
|
||||
final String url,
|
||||
boolean forceLoad) {
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST, Single.fromCallable(() ->
|
||||
KioskInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
public static Single<KioskInfo> getKioskInfo(final int serviceId, final String url,
|
||||
final boolean forceLoad) {
|
||||
return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST,
|
||||
Single.fromCallable(() -> KioskInfo.getInfo(NewPipe.getService(serviceId), url)));
|
||||
}
|
||||
|
||||
public static Single<InfoItemsPage> getMoreKioskItems(final int serviceId,
|
||||
final String url,
|
||||
final String nextStreamsUrl) {
|
||||
final String url,
|
||||
final String nextStreamsUrl) {
|
||||
return Single.fromCallable(() ->
|
||||
KioskInfo.getMoreItems(NewPipe.getService(serviceId),
|
||||
url, nextStreamsUrl));
|
||||
|
|
@ -207,23 +201,31 @@ public final class ExtractorHelper {
|
|||
* Check if we can load it from the cache (forceLoad parameter), if we can't,
|
||||
* load from the network (Single loadFromNetwork)
|
||||
* and put the results in the cache.
|
||||
*
|
||||
* @param <I> the item type's class that extends {@link Info}
|
||||
* @param forceLoad whether to force loading from the network instead of from the cache
|
||||
* @param serviceId the service to load from
|
||||
* @param url the URL to load
|
||||
* @param infoType the {@link InfoItem.InfoType} of the item
|
||||
* @param loadFromNetwork the {@link Single} to load the item from the network
|
||||
* @return a {@link Single} that loads the item
|
||||
*/
|
||||
private static <I extends Info> Single<I> checkCache(boolean forceLoad,
|
||||
int serviceId,
|
||||
String url,
|
||||
InfoItem.InfoType infoType,
|
||||
Single<I> loadFromNetwork) {
|
||||
private static <I extends Info> Single<I> checkCache(final boolean forceLoad,
|
||||
final int serviceId, final String url,
|
||||
final InfoItem.InfoType infoType,
|
||||
final Single<I> loadFromNetwork) {
|
||||
checkServiceId(serviceId);
|
||||
loadFromNetwork = loadFromNetwork.doOnSuccess(info -> cache.putInfo(serviceId, url, info, infoType));
|
||||
Single<I> actualLoadFromNetwork = loadFromNetwork
|
||||
.doOnSuccess(info -> CACHE.putInfo(serviceId, url, info, infoType));
|
||||
|
||||
Single<I> load;
|
||||
if (forceLoad) {
|
||||
cache.removeInfo(serviceId, url, infoType);
|
||||
load = loadFromNetwork;
|
||||
CACHE.removeInfo(serviceId, url, infoType);
|
||||
load = actualLoadFromNetwork;
|
||||
} else {
|
||||
load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url, infoType),
|
||||
loadFromNetwork.toMaybe())
|
||||
.firstElement() //Take the first valid
|
||||
actualLoadFromNetwork.toMaybe())
|
||||
.firstElement() // Take the first valid
|
||||
.toSingle();
|
||||
}
|
||||
|
||||
|
|
@ -231,14 +233,23 @@ public final class ExtractorHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* Default implementation uses the {@link InfoCache} to get cached results
|
||||
* Default implementation uses the {@link InfoCache} to get cached results.
|
||||
*
|
||||
* @param <I> the item type's class that extends {@link Info}
|
||||
* @param serviceId the service to load from
|
||||
* @param url the URL to load
|
||||
* @param infoType the {@link InfoItem.InfoType} of the item
|
||||
* @return a {@link Single} that loads the item
|
||||
*/
|
||||
public static <I extends Info> Maybe<I> loadFromCache(final int serviceId, final String url, InfoItem.InfoType infoType) {
|
||||
public static <I extends Info> Maybe<I> loadFromCache(final int serviceId, final String url,
|
||||
final InfoItem.InfoType infoType) {
|
||||
checkServiceId(serviceId);
|
||||
return Maybe.defer(() -> {
|
||||
//noinspection unchecked
|
||||
I info = (I) cache.getFromKey(serviceId, url, infoType);
|
||||
if (MainActivity.DEBUG) Log.d(TAG, "loadFromCache() called, info > " + info);
|
||||
I info = (I) CACHE.getFromKey(serviceId, url, infoType);
|
||||
if (MainActivity.DEBUG) {
|
||||
Log.d(TAG, "loadFromCache() called, info > " + info);
|
||||
}
|
||||
|
||||
// Only return info if it's not null (it is cached)
|
||||
if (info != null) {
|
||||
|
|
@ -249,14 +260,26 @@ public final class ExtractorHelper {
|
|||
});
|
||||
}
|
||||
|
||||
public static boolean isCached(final int serviceId, final String url, InfoItem.InfoType infoType) {
|
||||
public static boolean isCached(final int serviceId, final String url,
|
||||
final InfoItem.InfoType infoType) {
|
||||
return null != loadFromCache(serviceId, url, infoType).blockingGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple and general error handler that show a Toast for known exceptions, and for others, opens the report error activity with the (optional) error message.
|
||||
* A simple and general error handler that show a Toast for known exceptions,
|
||||
* and for others, opens the report error activity with the (optional) error message.
|
||||
*
|
||||
* @param context Android app context
|
||||
* @param serviceId the service the exception happened in
|
||||
* @param url the URL where the exception happened
|
||||
* @param exception the exception to be handled
|
||||
* @param userAction the action of the user that caused the exception
|
||||
* @param optionalErrorMessage the optional error message
|
||||
*/
|
||||
public static void handleGeneralException(Context context, int serviceId, String url, Throwable exception, UserAction userAction, String optionalErrorMessage) {
|
||||
public static void handleGeneralException(final Context context, final int serviceId,
|
||||
final String url, final Throwable exception,
|
||||
final UserAction userAction,
|
||||
final String optionalErrorMessage) {
|
||||
final Handler handler = new Handler(context.getMainLooper());
|
||||
|
||||
handler.post(() -> {
|
||||
|
|
@ -271,10 +294,15 @@ public final class ExtractorHelper {
|
|||
} else if (exception instanceof ContentNotAvailableException) {
|
||||
Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
int errorId = exception instanceof YoutubeStreamExtractor.DecryptException ? R.string.youtube_signature_decryption_error :
|
||||
exception instanceof ParsingException ? R.string.parsing_error : R.string.general_error;
|
||||
ErrorActivity.reportError(handler, context, exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(userAction,
|
||||
serviceId == -1 ? "none" : NewPipe.getNameOfService(serviceId), url + (optionalErrorMessage == null ? "" : optionalErrorMessage), errorId));
|
||||
int errorId = exception instanceof YoutubeStreamExtractor.DecryptException
|
||||
? R.string.youtube_signature_decryption_error
|
||||
: exception instanceof ParsingException
|
||||
? R.string.parsing_error : R.string.general_error;
|
||||
ErrorActivity.reportError(handler, context, exception, MainActivity.class, null,
|
||||
ErrorActivity.ErrorInfo.make(userAction, serviceId == -1 ? "none"
|
||||
: NewPipe.getNameOfService(serviceId),
|
||||
url + (optionalErrorMessage == null ? ""
|
||||
: optionalErrorMessage), errorId));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -283,12 +311,17 @@ public final class ExtractorHelper {
|
|||
* Check if throwable have the cause that can be assignable from the causes to check.
|
||||
*
|
||||
* @see Class#isAssignableFrom(Class)
|
||||
* @param throwable the throwable to be checked
|
||||
* @param causesToCheck the causes to check
|
||||
* @return whether the exception is an instance of a subclass of one of the causes
|
||||
* or is caused by an instance of a subclass of one of the causes
|
||||
*/
|
||||
public static boolean hasAssignableCauseThrowable(Throwable throwable,
|
||||
Class<?>... causesToCheck) {
|
||||
public static boolean hasAssignableCauseThrowable(final Throwable throwable,
|
||||
final Class<?>... causesToCheck) {
|
||||
// Check if getCause is not the same as cause (the getCause is already the root),
|
||||
// as it will cause a infinite loop if it is
|
||||
Throwable cause, getCause = throwable;
|
||||
Throwable cause;
|
||||
Throwable getCause = throwable;
|
||||
|
||||
// Check if throwable is a subclass of any of the filtered classes
|
||||
final Class throwableClass = throwable.getClass();
|
||||
|
|
@ -313,11 +346,18 @@ public final class ExtractorHelper {
|
|||
|
||||
/**
|
||||
* Check if throwable have the exact cause from one of the causes to check.
|
||||
*
|
||||
* @param throwable the throwable to be checked
|
||||
* @param causesToCheck the causes to check
|
||||
* @return whether the exception is an instance of one of the causes
|
||||
* or is caused by an instance of one of the causes
|
||||
*/
|
||||
public static boolean hasExactCauseThrowable(Throwable throwable, Class<?>... causesToCheck) {
|
||||
public static boolean hasExactCauseThrowable(final Throwable throwable,
|
||||
final Class<?>... causesToCheck) {
|
||||
// Check if getCause is not the same as cause (the getCause is already the root),
|
||||
// as it will cause a infinite loop if it is
|
||||
Throwable cause, getCause = throwable;
|
||||
Throwable cause;
|
||||
Throwable getCause = throwable;
|
||||
|
||||
for (Class<?> causesEl : causesToCheck) {
|
||||
if (throwable.getClass().equals(causesEl)) {
|
||||
|
|
@ -338,8 +378,11 @@ public final class ExtractorHelper {
|
|||
|
||||
/**
|
||||
* Check if throwable have Interrupted* exception as one of its causes.
|
||||
*
|
||||
* @param throwable the throwable to be checkes
|
||||
* @return whether the throwable is caused by an interruption
|
||||
*/
|
||||
public static boolean isInterruptedCaused(Throwable throwable) {
|
||||
public static boolean isInterruptedCaused(final Throwable throwable) {
|
||||
return ExtractorHelper.hasExactCauseThrowable(throwable,
|
||||
InterruptedIOException.class,
|
||||
InterruptedException.class);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package org.schabi.newpipe.util;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class FallbackViewHolder extends RecyclerView.ViewHolder {
|
||||
public FallbackViewHolder(View itemView) {
|
||||
public FallbackViewHolder(final View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,6 @@ import android.content.Intent;
|
|||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.recyclerview.widget.SortedList;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
|
@ -17,6 +12,12 @@ import android.view.ViewGroup;
|
|||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SortedList;
|
||||
|
||||
import com.nononsenseapps.filepicker.AbstractFilePickerFragment;
|
||||
import com.nononsenseapps.filepicker.FilePickerFragment;
|
||||
|
||||
|
|
@ -25,11 +26,36 @@ import org.schabi.newpipe.R;
|
|||
import java.io.File;
|
||||
|
||||
public class FilePickerActivityHelper extends com.nononsenseapps.filepicker.FilePickerActivity {
|
||||
|
||||
private CustomFilePickerFragment currentFragment;
|
||||
|
||||
public static Intent chooseSingleFile(@NonNull final Context context) {
|
||||
return new Intent(context, FilePickerActivityHelper.class)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, false)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_SINGLE_CLICK, true)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_MODE, FilePickerActivityHelper.MODE_FILE);
|
||||
}
|
||||
|
||||
public static Intent chooseFileToSave(@NonNull final Context context,
|
||||
@Nullable final String startPath) {
|
||||
return new Intent(context, FilePickerActivityHelper.class)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, true)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_EXISTING_FILE, true)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_START_PATH, startPath)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_MODE,
|
||||
FilePickerActivityHelper.MODE_NEW_FILE);
|
||||
}
|
||||
|
||||
public static boolean isOwnFileUri(@NonNull final Context context, @NonNull final Uri uri) {
|
||||
if (uri.getAuthority() == null) {
|
||||
return false;
|
||||
}
|
||||
return uri.getAuthority().startsWith(context.getPackageName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
if (ThemeHelper.isLightThemeSelected(this)) {
|
||||
this.setTheme(R.style.FilePickerThemeLight);
|
||||
} else {
|
||||
|
|
@ -50,33 +76,18 @@ public class FilePickerActivityHelper extends com.nononsenseapps.filepicker.File
|
|||
}
|
||||
|
||||
@Override
|
||||
protected AbstractFilePickerFragment<File> getFragment(@Nullable String startPath, int mode, boolean allowMultiple, boolean allowCreateDir, boolean allowExistingFile, boolean singleClick) {
|
||||
protected AbstractFilePickerFragment<File> getFragment(@Nullable final String startPath,
|
||||
final int mode,
|
||||
final boolean allowMultiple,
|
||||
final boolean allowCreateDir,
|
||||
final boolean allowExistingFile,
|
||||
final boolean singleClick) {
|
||||
final CustomFilePickerFragment fragment = new CustomFilePickerFragment();
|
||||
fragment.setArgs(startPath != null ? startPath : Environment.getExternalStorageDirectory().getPath(),
|
||||
fragment.setArgs(startPath != null ? startPath
|
||||
: Environment.getExternalStorageDirectory().getPath(),
|
||||
mode, allowMultiple, allowCreateDir, allowExistingFile, singleClick);
|
||||
return currentFragment = fragment;
|
||||
}
|
||||
|
||||
public static Intent chooseSingleFile(@NonNull Context context) {
|
||||
return new Intent(context, FilePickerActivityHelper.class)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, false)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_SINGLE_CLICK, true)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_MODE, FilePickerActivityHelper.MODE_FILE);
|
||||
}
|
||||
|
||||
public static Intent chooseFileToSave(@NonNull Context context, @Nullable String startPath) {
|
||||
return new Intent(context, FilePickerActivityHelper.class)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, true)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_EXISTING_FILE, true)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_START_PATH, startPath)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_MODE, FilePickerActivityHelper.MODE_NEW_FILE);
|
||||
}
|
||||
|
||||
public static boolean isOwnFileUri(@NonNull Context context, @NonNull Uri uri) {
|
||||
if (uri.getAuthority() == null) return false;
|
||||
return uri.getAuthority().startsWith(context.getPackageName());
|
||||
currentFragment = fragment;
|
||||
return currentFragment;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -84,30 +95,35 @@ public class FilePickerActivityHelper extends com.nononsenseapps.filepicker.File
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public static class CustomFilePickerFragment extends FilePickerFragment {
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
return super.onCreateView(inflater, container, savedInstanceState);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent,
|
||||
final int viewType) {
|
||||
final RecyclerView.ViewHolder viewHolder = super.onCreateViewHolder(parent, viewType);
|
||||
|
||||
final View view = viewHolder.itemView.findViewById(android.R.id.text1);
|
||||
if (view instanceof TextView) {
|
||||
((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.file_picker_items_text_size));
|
||||
((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_PX,
|
||||
getResources().getDimension(R.dimen.file_picker_items_text_size));
|
||||
}
|
||||
|
||||
return viewHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickOk(@NonNull View view) {
|
||||
public void onClickOk(@NonNull final View view) {
|
||||
if (mode == MODE_NEW_FILE && getNewFileName().isEmpty()) {
|
||||
if (mToast != null) mToast.cancel();
|
||||
mToast = Toast.makeText(getActivity(), R.string.file_name_empty_error, Toast.LENGTH_SHORT);
|
||||
if (mToast != null) {
|
||||
mToast.cancel();
|
||||
}
|
||||
mToast = Toast.makeText(getActivity(), R.string.file_name_empty_error,
|
||||
Toast.LENGTH_SHORT);
|
||||
mToast.show();
|
||||
return;
|
||||
}
|
||||
|
|
@ -116,13 +132,17 @@ public class FilePickerActivityHelper extends com.nononsenseapps.filepicker.File
|
|||
}
|
||||
|
||||
@Override
|
||||
protected boolean isItemVisible(@NonNull File file) {
|
||||
if (file.isDirectory() && file.isHidden()) return true;
|
||||
protected boolean isItemVisible(@NonNull final File file) {
|
||||
if (file.isDirectory() && file.isHidden()) {
|
||||
return true;
|
||||
}
|
||||
return super.isItemVisible(file);
|
||||
}
|
||||
|
||||
public File getBackTop() {
|
||||
if (getArguments() == null) return Environment.getExternalStorageDirectory();
|
||||
if (getArguments() == null) {
|
||||
return Environment.getExternalStorageDirectory();
|
||||
}
|
||||
|
||||
final String path = getArguments().getString(KEY_START_PATH, "/");
|
||||
if (path.contains(Environment.getExternalStorageDirectory().getPath())) {
|
||||
|
|
@ -133,11 +153,13 @@ public class FilePickerActivityHelper extends com.nononsenseapps.filepicker.File
|
|||
}
|
||||
|
||||
public boolean isBackTop() {
|
||||
return compareFiles(mCurrentPath, getBackTop()) == 0 || compareFiles(mCurrentPath, new File("/")) == 0;
|
||||
return compareFiles(mCurrentPath,
|
||||
getBackTop()) == 0 || compareFiles(mCurrentPath, new File("/")) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<SortedList<File>> loader, SortedList<File> data) {
|
||||
public void onLoadFinished(final Loader<SortedList<File>> loader,
|
||||
final SortedList<File> data) {
|
||||
super.onLoadFinished(loader, data);
|
||||
layoutManager.scrollToPosition(0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,37 +8,44 @@ import org.schabi.newpipe.R;
|
|||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class FilenameUtils {
|
||||
|
||||
public final class FilenameUtils {
|
||||
private static final String CHARSET_MOST_SPECIAL = "[\\n\\r|?*<\":\\\\>/']+";
|
||||
private static final String CHARSET_ONLY_LETTERS_AND_DIGITS = "[^\\w\\d]+";
|
||||
|
||||
private FilenameUtils() { }
|
||||
|
||||
/**
|
||||
* #143 #44 #42 #22: make sure that the filename does not contain illegal chars.
|
||||
*
|
||||
* @param context the context to retrieve strings and preferences from
|
||||
* @param title the title to create a filename from
|
||||
* @param title the title to create a filename from
|
||||
* @return the filename
|
||||
*/
|
||||
public static String createFilename(Context context, String title) {
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
public static String createFilename(final Context context, final String title) {
|
||||
SharedPreferences sharedPreferences = PreferenceManager
|
||||
.getDefaultSharedPreferences(context);
|
||||
|
||||
final String charset_ld = context.getString(R.string.charset_letters_and_digits_value);
|
||||
final String charset_ms = context.getString(R.string.charset_most_special_value);
|
||||
final String charsetLd = context.getString(R.string.charset_letters_and_digits_value);
|
||||
final String charsetMs = context.getString(R.string.charset_most_special_value);
|
||||
final String defaultCharset = context.getString(R.string.default_file_charset_value);
|
||||
|
||||
final String replacementChar = sharedPreferences.getString(context.getString(R.string.settings_file_replacement_character_key), "_");
|
||||
String selectedCharset = sharedPreferences.getString(context.getString(R.string.settings_file_charset_key), null);
|
||||
final String replacementChar = sharedPreferences.getString(
|
||||
context.getString(R.string.settings_file_replacement_character_key), "_");
|
||||
String selectedCharset = sharedPreferences.getString(
|
||||
context.getString(R.string.settings_file_charset_key), null);
|
||||
|
||||
final String charset;
|
||||
|
||||
if (selectedCharset == null || selectedCharset.isEmpty()) selectedCharset = defaultCharset;
|
||||
if (selectedCharset == null || selectedCharset.isEmpty()) {
|
||||
selectedCharset = defaultCharset;
|
||||
}
|
||||
|
||||
if (selectedCharset.equals(charset_ld)) {
|
||||
if (selectedCharset.equals(charsetLd)) {
|
||||
charset = CHARSET_ONLY_LETTERS_AND_DIGITS;
|
||||
} else if (selectedCharset.equals(charset_ms)) {
|
||||
} else if (selectedCharset.equals(charsetMs)) {
|
||||
charset = CHARSET_MOST_SPECIAL;
|
||||
} else {
|
||||
charset = selectedCharset;// ¿is the user using a custom charset?
|
||||
charset = selectedCharset; // Is the user using a custom charset?
|
||||
}
|
||||
|
||||
Pattern pattern = Pattern.compile(charset);
|
||||
|
|
@ -47,13 +54,15 @@ public class FilenameUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a valid filename
|
||||
* @param title the title to create a filename from
|
||||
* Create a valid filename.
|
||||
*
|
||||
* @param title the title to create a filename from
|
||||
* @param invalidCharacters patter matching invalid characters
|
||||
* @param replacementChar the replacement
|
||||
* @param replacementChar the replacement
|
||||
* @return the filename
|
||||
*/
|
||||
private static String createFilename(String title, Pattern invalidCharacters, String replacementChar) {
|
||||
private static String createFilename(final String title, final Pattern invalidCharacters,
|
||||
final String replacementChar) {
|
||||
return title.replaceAll(invalidCharacters.pattern(), replacementChar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ package org.schabi.newpipe.util;
|
|||
|
||||
import org.schabi.newpipe.App;
|
||||
|
||||
public class FireTvUtils {
|
||||
public static boolean isFireTv(){
|
||||
final String AMAZON_FEATURE_FIRE_TV = "amazon.hardware.fire_tv";
|
||||
return App.getApp().getPackageManager().hasSystemFeature(AMAZON_FEATURE_FIRE_TV);
|
||||
public final class FireTvUtils {
|
||||
private FireTvUtils() { }
|
||||
|
||||
public static boolean isFireTv() {
|
||||
return App.getApp().getPackageManager().hasSystemFeature("amazon.hardware.fire_tv");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
|
|||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class ImageDisplayConstants {
|
||||
public final class ImageDisplayConstants {
|
||||
private static final int BITMAP_FADE_IN_DURATION_MILLIS = 250;
|
||||
|
||||
/**
|
||||
* Base display options
|
||||
* This constant contains the base display options.
|
||||
*/
|
||||
private static final DisplayImageOptions BASE_DISPLAY_IMAGE_OPTIONS =
|
||||
new DisplayImageOptions.Builder()
|
||||
|
|
@ -55,4 +55,6 @@ public class ImageDisplayConstants {
|
|||
.showImageForEmptyUri(R.drawable.dummy_thumbnail_playlist)
|
||||
.showImageOnFail(R.drawable.dummy_thumbnail_playlist)
|
||||
.build();
|
||||
|
||||
private ImageDisplayConstants() { }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,11 @@
|
|||
|
||||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.collection.LruCache;
|
||||
import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.extractor.Info;
|
||||
|
|
@ -30,104 +31,119 @@ import org.schabi.newpipe.extractor.InfoItem;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public final class InfoCache {
|
||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||
private final String TAG = getClass().getSimpleName();
|
||||
|
||||
private static final InfoCache instance = new InfoCache();
|
||||
private static final InfoCache INSTANCE = new InfoCache();
|
||||
private static final int MAX_ITEMS_ON_CACHE = 60;
|
||||
/**
|
||||
* Trim the cache to this size
|
||||
* Trim the cache to this size.
|
||||
*/
|
||||
private static final int TRIM_CACHE_TO = 30;
|
||||
|
||||
private static final LruCache<String, CacheData> lruCache = new LruCache<>(MAX_ITEMS_ON_CACHE);
|
||||
private static final LruCache<String, CacheData> LRU_CACHE = new LruCache<>(MAX_ITEMS_ON_CACHE);
|
||||
private final String TAG = getClass().getSimpleName();
|
||||
|
||||
private InfoCache() {
|
||||
//no instance
|
||||
}
|
||||
|
||||
public static InfoCache getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Info getFromKey(int serviceId, @NonNull String url, @NonNull InfoItem.InfoType infoType) {
|
||||
if (DEBUG) Log.d(TAG, "getFromKey() called with: serviceId = [" + serviceId + "], url = [" + url + "]");
|
||||
synchronized (lruCache) {
|
||||
return getInfo(keyOf(serviceId, url, infoType));
|
||||
}
|
||||
}
|
||||
|
||||
public void putInfo(int serviceId, @NonNull String url, @NonNull Info info, @NonNull InfoItem.InfoType infoType) {
|
||||
if (DEBUG) Log.d(TAG, "putInfo() called with: info = [" + info + "]");
|
||||
|
||||
final long expirationMillis = ServiceHelper.getCacheExpirationMillis(info.getServiceId());
|
||||
synchronized (lruCache) {
|
||||
final CacheData data = new CacheData(info, expirationMillis);
|
||||
lruCache.put(keyOf(serviceId, url, infoType), data);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeInfo(int serviceId, @NonNull String url, @NonNull InfoItem.InfoType infoType) {
|
||||
if (DEBUG) Log.d(TAG, "removeInfo() called with: serviceId = [" + serviceId + "], url = [" + url + "]");
|
||||
synchronized (lruCache) {
|
||||
lruCache.remove(keyOf(serviceId, url, infoType));
|
||||
}
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
if (DEBUG) Log.d(TAG, "clearCache() called");
|
||||
synchronized (lruCache) {
|
||||
lruCache.evictAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void trimCache() {
|
||||
if (DEBUG) Log.d(TAG, "trimCache() called");
|
||||
synchronized (lruCache) {
|
||||
removeStaleCache();
|
||||
lruCache.trimToSize(TRIM_CACHE_TO);
|
||||
}
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
synchronized (lruCache) {
|
||||
return lruCache.size();
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String keyOf(final int serviceId, @NonNull final String url, @NonNull InfoItem.InfoType infoType) {
|
||||
private static String keyOf(final int serviceId, @NonNull final String url,
|
||||
@NonNull final InfoItem.InfoType infoType) {
|
||||
return serviceId + url + infoType.toString();
|
||||
}
|
||||
|
||||
private static void removeStaleCache() {
|
||||
for (Map.Entry<String, CacheData> entry : InfoCache.lruCache.snapshot().entrySet()) {
|
||||
for (Map.Entry<String, CacheData> entry : InfoCache.LRU_CACHE.snapshot().entrySet()) {
|
||||
final CacheData data = entry.getValue();
|
||||
if (data != null && data.isExpired()) {
|
||||
InfoCache.lruCache.remove(entry.getKey());
|
||||
InfoCache.LRU_CACHE.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Info getInfo(@NonNull final String key) {
|
||||
final CacheData data = InfoCache.lruCache.get(key);
|
||||
if (data == null) return null;
|
||||
final CacheData data = InfoCache.LRU_CACHE.get(key);
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.isExpired()) {
|
||||
InfoCache.lruCache.remove(key);
|
||||
InfoCache.LRU_CACHE.remove(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.info;
|
||||
}
|
||||
|
||||
final private static class CacheData {
|
||||
final private long expireTimestamp;
|
||||
final private Info info;
|
||||
@Nullable
|
||||
public Info getFromKey(final int serviceId, @NonNull final String url,
|
||||
@NonNull final InfoItem.InfoType infoType) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "getFromKey() called with: "
|
||||
+ "serviceId = [" + serviceId + "], url = [" + url + "]");
|
||||
}
|
||||
synchronized (LRU_CACHE) {
|
||||
return getInfo(keyOf(serviceId, url, infoType));
|
||||
}
|
||||
}
|
||||
|
||||
public void putInfo(final int serviceId, @NonNull final String url, @NonNull final Info info,
|
||||
@NonNull final InfoItem.InfoType infoType) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "putInfo() called with: info = [" + info + "]");
|
||||
}
|
||||
|
||||
final long expirationMillis = ServiceHelper.getCacheExpirationMillis(info.getServiceId());
|
||||
synchronized (LRU_CACHE) {
|
||||
final CacheData data = new CacheData(info, expirationMillis);
|
||||
LRU_CACHE.put(keyOf(serviceId, url, infoType), data);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeInfo(final int serviceId, @NonNull final String url,
|
||||
@NonNull final InfoItem.InfoType infoType) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "removeInfo() called with: "
|
||||
+ "serviceId = [" + serviceId + "], url = [" + url + "]");
|
||||
}
|
||||
synchronized (LRU_CACHE) {
|
||||
LRU_CACHE.remove(keyOf(serviceId, url, infoType));
|
||||
}
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "clearCache() called");
|
||||
}
|
||||
synchronized (LRU_CACHE) {
|
||||
LRU_CACHE.evictAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void trimCache() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "trimCache() called");
|
||||
}
|
||||
synchronized (LRU_CACHE) {
|
||||
removeStaleCache();
|
||||
LRU_CACHE.trimToSize(TRIM_CACHE_TO);
|
||||
}
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
synchronized (LRU_CACHE) {
|
||||
return LRU_CACHE.size();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CacheData {
|
||||
private final long expireTimestamp;
|
||||
private final Info info;
|
||||
|
||||
private CacheData(@NonNull final Info info, final long timeoutMillis) {
|
||||
this.expireTimestamp = System.currentTimeMillis() + timeoutMillis;
|
||||
|
|
|
|||
|
|
@ -7,23 +7,28 @@ import org.schabi.newpipe.R;
|
|||
/**
|
||||
* Created by Chrsitian Schabesberger on 28.09.17.
|
||||
* KioskTranslator.java is part of NewPipe.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* </p>
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* </p>
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
* </p>
|
||||
*/
|
||||
|
||||
public class KioskTranslator {
|
||||
public static String getTranslatedKioskName(String kioskId, Context c) {
|
||||
public final class KioskTranslator {
|
||||
private KioskTranslator() { }
|
||||
|
||||
public static String getTranslatedKioskName(final String kioskId, final Context c) {
|
||||
switch (kioskId) {
|
||||
case "Trending":
|
||||
return c.getString(R.string.trending);
|
||||
|
|
@ -44,13 +49,12 @@ public class KioskTranslator {
|
|||
}
|
||||
}
|
||||
|
||||
public static int getKioskIcons(String kioskId, Context c) {
|
||||
switch(kioskId) {
|
||||
public static int getKioskIcons(final String kioskId, final Context c) {
|
||||
switch (kioskId) {
|
||||
case "Trending":
|
||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot);
|
||||
case "Top 50":
|
||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot);
|
||||
case "New & hot":
|
||||
case "conferences":
|
||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot);
|
||||
case "Local":
|
||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_local);
|
||||
|
|
@ -58,8 +62,6 @@ public class KioskTranslator {
|
|||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_recent);
|
||||
case "Most liked":
|
||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.thumbs_up);
|
||||
case "conferences":
|
||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,21 +3,21 @@ package org.schabi.newpipe.util;
|
|||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
|
||||
public class KoreUtil {
|
||||
public final class KoreUtil {
|
||||
private KoreUtil() { }
|
||||
|
||||
public static void showInstallKoreDialog(final Context context) {
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setMessage(R.string.kore_not_found)
|
||||
.setPositiveButton(R.string.install,
|
||||
(DialogInterface dialog, int which) -> NavigationHelper.installKore(context))
|
||||
.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> {
|
||||
});
|
||||
.setPositiveButton(R.string.install, (DialogInterface dialog, int which) ->
|
||||
NavigationHelper.installKore(context))
|
||||
.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> {
|
||||
});
|
||||
builder.create().show();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,35 +2,38 @@ package org.schabi.newpipe.util;
|
|||
|
||||
import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class LayoutManagerSmoothScroller extends LinearLayoutManager {
|
||||
|
||||
public LayoutManagerSmoothScroller(Context context) {
|
||||
public LayoutManagerSmoothScroller(final Context context) {
|
||||
super(context, VERTICAL, false);
|
||||
}
|
||||
|
||||
public LayoutManagerSmoothScroller(Context context, int orientation, boolean reverseLayout) {
|
||||
public LayoutManagerSmoothScroller(final Context context, final int orientation,
|
||||
final boolean reverseLayout) {
|
||||
super(context, orientation, reverseLayout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
|
||||
RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext());
|
||||
public void smoothScrollToPosition(final RecyclerView recyclerView,
|
||||
final RecyclerView.State state, final int position) {
|
||||
RecyclerView.SmoothScroller smoothScroller
|
||||
= new TopSnappedSmoothScroller(recyclerView.getContext());
|
||||
smoothScroller.setTargetPosition(position);
|
||||
startSmoothScroll(smoothScroller);
|
||||
}
|
||||
|
||||
private class TopSnappedSmoothScroller extends LinearSmoothScroller {
|
||||
public TopSnappedSmoothScroller(Context context) {
|
||||
TopSnappedSmoothScroller(final Context context) {
|
||||
super(context);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||
public PointF computeScrollVectorForPosition(final int targetPosition) {
|
||||
return LayoutManagerSmoothScroller.this
|
||||
.computeScrollVectorForPosition(targetPosition);
|
||||
}
|
||||
|
|
@ -40,4 +43,4 @@ public class LayoutManagerSmoothScroller extends LinearLayoutManager {
|
|||
return SNAP_TO_START;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.content.Context;
|
|||
import android.content.SharedPreferences;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
|
@ -17,26 +18,31 @@ import java.util.Collections;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public final class ListHelper {
|
||||
|
||||
// Video format in order of quality. 0=lowest quality, n=highest quality
|
||||
private static final List<MediaFormat> VIDEO_FORMAT_QUALITY_RANKING =
|
||||
Arrays.asList(MediaFormat.v3GPP, MediaFormat.WEBM, MediaFormat.MPEG_4);
|
||||
Arrays.asList(MediaFormat.v3GPP, MediaFormat.WEBM, MediaFormat.MPEG_4);
|
||||
|
||||
// Audio format in order of quality. 0=lowest quality, n=highest quality
|
||||
private static final List<MediaFormat> AUDIO_FORMAT_QUALITY_RANKING =
|
||||
Arrays.asList(MediaFormat.MP3, MediaFormat.WEBMA, MediaFormat.M4A);
|
||||
Arrays.asList(MediaFormat.MP3, MediaFormat.WEBMA, MediaFormat.M4A);
|
||||
// Audio format in order of efficiency. 0=most efficient, n=least efficient
|
||||
private static final List<MediaFormat> AUDIO_FORMAT_EFFICIENCY_RANKING =
|
||||
Arrays.asList(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3);
|
||||
|
||||
private static final List<String> HIGH_RESOLUTION_LIST = Arrays.asList("1440p", "2160p", "1440p60", "2160p60");
|
||||
private static final List<String> HIGH_RESOLUTION_LIST
|
||||
= Arrays.asList("1440p", "2160p", "1440p60", "2160p60");
|
||||
|
||||
private ListHelper() { }
|
||||
|
||||
/**
|
||||
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||
* @param context Android app context
|
||||
* @param videoStreams list of the video streams to check
|
||||
* @return index of the video stream with the default index
|
||||
*/
|
||||
public static int getDefaultResolutionIndex(Context context, List<VideoStream> videoStreams) {
|
||||
public static int getDefaultResolutionIndex(final Context context,
|
||||
final List<VideoStream> videoStreams) {
|
||||
String defaultResolution = computeDefaultResolution(context,
|
||||
R.string.default_resolution_key, R.string.default_resolution_value);
|
||||
return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams);
|
||||
|
|
@ -44,15 +50,25 @@ public final class ListHelper {
|
|||
|
||||
/**
|
||||
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||
* @param context Android app context
|
||||
* @param videoStreams list of the video streams to check
|
||||
* @param defaultResolution the default resolution to look for
|
||||
* @return index of the video stream with the default index
|
||||
*/
|
||||
public static int getResolutionIndex(Context context, List<VideoStream> videoStreams, String defaultResolution) {
|
||||
public static int getResolutionIndex(final Context context,
|
||||
final List<VideoStream> videoStreams,
|
||||
final String defaultResolution) {
|
||||
return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||
* @param context Android app context
|
||||
* @param videoStreams list of the video streams to check
|
||||
* @return index of the video stream with the default index
|
||||
*/
|
||||
public static int getPopupDefaultResolutionIndex(Context context, List<VideoStream> videoStreams) {
|
||||
public static int getPopupDefaultResolutionIndex(final Context context,
|
||||
final List<VideoStream> videoStreams) {
|
||||
String defaultResolution = computeDefaultResolution(context,
|
||||
R.string.default_popup_resolution_key, R.string.default_popup_resolution_value);
|
||||
return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams);
|
||||
|
|
@ -60,12 +76,19 @@ public final class ListHelper {
|
|||
|
||||
/**
|
||||
* @see #getDefaultResolutionIndex(String, String, MediaFormat, List)
|
||||
* @param context Android app context
|
||||
* @param videoStreams list of the video streams to check
|
||||
* @param defaultResolution the default resolution to look for
|
||||
* @return index of the video stream with the default index
|
||||
*/
|
||||
public static int getPopupResolutionIndex(Context context, List<VideoStream> videoStreams, String defaultResolution) {
|
||||
public static int getPopupResolutionIndex(final Context context,
|
||||
final List<VideoStream> videoStreams,
|
||||
final String defaultResolution) {
|
||||
return getDefaultResolutionWithDefaultFormat(context, defaultResolution, videoStreams);
|
||||
}
|
||||
|
||||
public static int getDefaultAudioFormat(Context context, List<AudioStream> audioStreams) {
|
||||
public static int getDefaultAudioFormat(final Context context,
|
||||
final List<AudioStream> audioStreams) {
|
||||
MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_audio_format_key,
|
||||
R.string.default_audio_format_value);
|
||||
|
||||
|
|
@ -79,8 +102,8 @@ public final class ListHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* Join the two lists of video streams (video_only and normal videos), and sort them according with default format
|
||||
* chosen by the user
|
||||
* Join the two lists of video streams (video_only and normal videos),
|
||||
* and sort them according with default format chosen by the user.
|
||||
*
|
||||
* @param context context to search for the format to give preference
|
||||
* @param videoStreams normal videos list
|
||||
|
|
@ -88,20 +111,28 @@ public final class ListHelper {
|
|||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest
|
||||
* @return the sorted list
|
||||
*/
|
||||
public static List<VideoStream> getSortedStreamVideosList(Context context, List<VideoStream> videoStreams, List<VideoStream> videoOnlyStreams, boolean ascendingOrder) {
|
||||
public static List<VideoStream> getSortedStreamVideosList(final Context context,
|
||||
final List<VideoStream> videoStreams,
|
||||
final List<VideoStream>
|
||||
videoOnlyStreams,
|
||||
final boolean ascendingOrder) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
boolean showHigherResolutions = preferences.getBoolean(context.getString(R.string.show_higher_resolutions_key), false);
|
||||
MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_video_format_key, R.string.default_video_format_value);
|
||||
boolean showHigherResolutions = preferences.getBoolean(
|
||||
context.getString(R.string.show_higher_resolutions_key), false);
|
||||
MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_video_format_key,
|
||||
R.string.default_video_format_value);
|
||||
|
||||
return getSortedStreamVideosList(defaultFormat, showHigherResolutions, videoStreams, videoOnlyStreams, ascendingOrder);
|
||||
return getSortedStreamVideosList(defaultFormat, showHigherResolutions, videoStreams,
|
||||
videoOnlyStreams, ascendingOrder);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private static String computeDefaultResolution(Context context, int key, int value) {
|
||||
private static String computeDefaultResolution(final Context context, final int key,
|
||||
final int value) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
// Load the prefered resolution otherwise the best available
|
||||
|
|
@ -110,7 +141,8 @@ public final class ListHelper {
|
|||
: context.getString(R.string.best_resolution_key);
|
||||
|
||||
String maxResolution = getResolutionLimit(context);
|
||||
if (maxResolution != null && (resolution.equals(context.getString(R.string.best_resolution_key))
|
||||
if (maxResolution != null
|
||||
&& (resolution.equals(context.getString(R.string.best_resolution_key))
|
||||
|| compareVideoStreamResolution(maxResolution, resolution) < 1)) {
|
||||
resolution = maxResolution;
|
||||
}
|
||||
|
|
@ -119,20 +151,29 @@ public final class ListHelper {
|
|||
|
||||
/**
|
||||
* Return the index of the default stream in the list, based on the parameters
|
||||
* defaultResolution and defaultFormat
|
||||
* defaultResolution and defaultFormat.
|
||||
*
|
||||
* @param defaultResolution the default resolution to look for
|
||||
* @param bestResolutionKey key of the best resolution
|
||||
* @param defaultFormat the default fomat to look for
|
||||
* @param videoStreams list of the video streams to check
|
||||
* @return index of the default resolution&format
|
||||
*/
|
||||
static int getDefaultResolutionIndex(String defaultResolution, String bestResolutionKey,
|
||||
MediaFormat defaultFormat, List<VideoStream> videoStreams) {
|
||||
if (videoStreams == null || videoStreams.isEmpty()) return -1;
|
||||
static int getDefaultResolutionIndex(final String defaultResolution,
|
||||
final String bestResolutionKey,
|
||||
final MediaFormat defaultFormat,
|
||||
final List<VideoStream> videoStreams) {
|
||||
if (videoStreams == null || videoStreams.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sortStreamList(videoStreams, false);
|
||||
if (defaultResolution.equals(bestResolutionKey)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int defaultStreamIndex = getVideoStreamIndex(defaultResolution, defaultFormat, videoStreams);
|
||||
int defaultStreamIndex
|
||||
= getVideoStreamIndex(defaultResolution, defaultFormat, videoStreams);
|
||||
|
||||
// this is actually an error,
|
||||
// but maybe there is really no stream fitting to the default value.
|
||||
|
|
@ -143,39 +184,53 @@ public final class ListHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* Join the two lists of video streams (video_only and normal videos), and sort them according with default format
|
||||
* chosen by the user
|
||||
* Join the two lists of video streams (video_only and normal videos),
|
||||
* and sort them according with default format chosen by the user.
|
||||
*
|
||||
* @param defaultFormat format to give preference
|
||||
* @param defaultFormat format to give preference
|
||||
* @param showHigherResolutions show >1080p resolutions
|
||||
* @param videoStreams normal videos list
|
||||
* @param videoOnlyStreams video only stream list
|
||||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest @return the sorted list
|
||||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest
|
||||
* @return the sorted list
|
||||
*/
|
||||
static List<VideoStream> getSortedStreamVideosList(MediaFormat defaultFormat, boolean showHigherResolutions, List<VideoStream> videoStreams, List<VideoStream> videoOnlyStreams, boolean ascendingOrder) {
|
||||
static List<VideoStream> getSortedStreamVideosList(final MediaFormat defaultFormat,
|
||||
final boolean showHigherResolutions,
|
||||
final List<VideoStream> videoStreams,
|
||||
final List<VideoStream> videoOnlyStreams,
|
||||
final boolean ascendingOrder) {
|
||||
ArrayList<VideoStream> retList = new ArrayList<>();
|
||||
HashMap<String, VideoStream> hashMap = new HashMap<>();
|
||||
|
||||
if (videoOnlyStreams != null) {
|
||||
for (VideoStream stream : videoOnlyStreams) {
|
||||
if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.getResolution())) continue;
|
||||
if (!showHigherResolutions
|
||||
&& HIGH_RESOLUTION_LIST.contains(stream.getResolution())) {
|
||||
continue;
|
||||
}
|
||||
retList.add(stream);
|
||||
}
|
||||
}
|
||||
if (videoStreams != null) {
|
||||
for (VideoStream stream : videoStreams) {
|
||||
if (!showHigherResolutions && HIGH_RESOLUTION_LIST.contains(stream.getResolution())) continue;
|
||||
if (!showHigherResolutions
|
||||
&& HIGH_RESOLUTION_LIST.contains(stream.getResolution())) {
|
||||
continue;
|
||||
}
|
||||
retList.add(stream);
|
||||
}
|
||||
}
|
||||
|
||||
// Add all to the hashmap
|
||||
for (VideoStream videoStream : retList) hashMap.put(videoStream.getResolution(), videoStream);
|
||||
for (VideoStream videoStream : retList) {
|
||||
hashMap.put(videoStream.getResolution(), videoStream);
|
||||
}
|
||||
|
||||
// Override the values when the key == resolution, with the defaultFormat
|
||||
for (VideoStream videoStream : retList) {
|
||||
if (videoStream.getFormat() == defaultFormat) hashMap.put(videoStream.getResolution(), videoStream);
|
||||
if (videoStream.getFormat() == defaultFormat) {
|
||||
hashMap.put(videoStream.getResolution(), videoStream);
|
||||
}
|
||||
}
|
||||
|
||||
retList.clear();
|
||||
|
|
@ -203,7 +258,8 @@ public final class ListHelper {
|
|||
* @param videoStreams list that the sorting will be applied
|
||||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest
|
||||
*/
|
||||
private static void sortStreamList(List<VideoStream> videoStreams, final boolean ascendingOrder) {
|
||||
private static void sortStreamList(final List<VideoStream> videoStreams,
|
||||
final boolean ascendingOrder) {
|
||||
Collections.sort(videoStreams, (o1, o2) -> {
|
||||
int result = compareVideoStreamResolution(o1, o2);
|
||||
return result == 0 ? 0 : (ascendingOrder ? result : -result);
|
||||
|
|
@ -214,18 +270,21 @@ public final class ListHelper {
|
|||
* Get the audio from the list with the highest quality. Format will be ignored if it yields
|
||||
* no results.
|
||||
*
|
||||
* @param format the format to look for
|
||||
* @param audioStreams list the audio streams
|
||||
* @return index of the audio with the highest average bitrate of the default format
|
||||
*/
|
||||
static int getHighestQualityAudioIndex(MediaFormat format, List<AudioStream> audioStreams) {
|
||||
static int getHighestQualityAudioIndex(final MediaFormat format,
|
||||
final List<AudioStream> audioStreams) {
|
||||
int result = -1;
|
||||
boolean hasOneFormat = false;
|
||||
if (audioStreams != null) {
|
||||
while(result == -1) {
|
||||
while (result == -1) {
|
||||
AudioStream prevStream = null;
|
||||
for (int idx = 0; idx < audioStreams.size(); idx++) {
|
||||
AudioStream stream = audioStreams.get(idx);
|
||||
if ((format == null || stream.getFormat() == format) &&
|
||||
(prevStream == null || compareAudioStreamBitrate(prevStream, stream,
|
||||
if ((format == null || stream.getFormat() == format || hasOneFormat)
|
||||
&& (prevStream == null || compareAudioStreamBitrate(prevStream, stream,
|
||||
AUDIO_FORMAT_QUALITY_RANKING) < 0)) {
|
||||
prevStream = stream;
|
||||
result = idx;
|
||||
|
|
@ -234,7 +293,7 @@ public final class ListHelper {
|
|||
if (result == -1 && format == null) {
|
||||
break;
|
||||
}
|
||||
format = null;
|
||||
hasOneFormat = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
@ -244,19 +303,21 @@ public final class ListHelper {
|
|||
* Get the audio from the list with the lowest bitrate and efficient format. Format will be
|
||||
* ignored if it yields no results.
|
||||
*
|
||||
* @param format The target format type or null if it doesn't matter
|
||||
* @param audioStreams list the audio streams
|
||||
* @return index of the audio stream that can produce the most compact results or -1 if not found.
|
||||
* @param format The target format type or null if it doesn't matter
|
||||
* @param audioStreams List of audio streams
|
||||
* @return Index of audio stream that can produce the most compact results or -1 if not found
|
||||
*/
|
||||
static int getMostCompactAudioIndex(MediaFormat format, List<AudioStream> audioStreams) {
|
||||
static int getMostCompactAudioIndex(final MediaFormat format,
|
||||
final List<AudioStream> audioStreams) {
|
||||
int result = -1;
|
||||
boolean hasOneFormat = false;
|
||||
if (audioStreams != null) {
|
||||
while(result == -1) {
|
||||
while (result == -1) {
|
||||
AudioStream prevStream = null;
|
||||
for (int idx = 0; idx < audioStreams.size(); idx++) {
|
||||
AudioStream stream = audioStreams.get(idx);
|
||||
if ((format == null || stream.getFormat() == format) &&
|
||||
(prevStream == null || compareAudioStreamBitrate(prevStream, stream,
|
||||
if ((format == null || stream.getFormat() == format || hasOneFormat)
|
||||
&& (prevStream == null || compareAudioStreamBitrate(prevStream, stream,
|
||||
AUDIO_FORMAT_EFFICIENCY_RANKING) > 0)) {
|
||||
prevStream = stream;
|
||||
result = idx;
|
||||
|
|
@ -265,7 +326,7 @@ public final class ListHelper {
|
|||
if (result == -1 && format == null) {
|
||||
break;
|
||||
}
|
||||
format = null;
|
||||
hasOneFormat = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
@ -273,16 +334,25 @@ public final class ListHelper {
|
|||
|
||||
/**
|
||||
* Locates a possible match for the given resolution and format in the provided list.
|
||||
* In this order:
|
||||
* 1. Find a format and resolution match
|
||||
* 2. Find a format and resolution match and ignore the refresh
|
||||
* 3. Find a resolution match
|
||||
* 4. Find a resolution match and ignore the refresh
|
||||
* 5. Find a resolution just below the requested resolution and ignore the refresh
|
||||
* 6. Give up
|
||||
*
|
||||
* <p>In this order:</p>
|
||||
*
|
||||
* <ol>
|
||||
* <li>Find a format and resolution match</li>
|
||||
* <li>Find a format and resolution match and ignore the refresh</li>
|
||||
* <li>Find a resolution match</li>
|
||||
* <li>Find a resolution match and ignore the refresh</li>
|
||||
* <li>Find a resolution just below the requested resolution and ignore the refresh</li>
|
||||
* <li>Give up</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param targetResolution the resolution to look for
|
||||
* @param targetFormat the format to look for
|
||||
* @param videoStreams the available video streams
|
||||
* @return the index of the prefered video stream
|
||||
*/
|
||||
static int getVideoStreamIndex(String targetResolution, MediaFormat targetFormat,
|
||||
List<VideoStream> videoStreams) {
|
||||
static int getVideoStreamIndex(final String targetResolution, final MediaFormat targetFormat,
|
||||
final List<VideoStream> videoStreams) {
|
||||
int fullMatchIndex = -1;
|
||||
int fullMatchNoRefreshIndex = -1;
|
||||
int resMatchOnlyIndex = -1;
|
||||
|
|
@ -307,11 +377,13 @@ public final class ListHelper {
|
|||
resMatchOnlyIndex = idx;
|
||||
}
|
||||
|
||||
if (resMatchOnlyNoRefreshIndex == -1 && resolutionNoRefresh.equals(targetResolutionNoRefresh)) {
|
||||
if (resMatchOnlyNoRefreshIndex == -1
|
||||
&& resolutionNoRefresh.equals(targetResolutionNoRefresh)) {
|
||||
resMatchOnlyNoRefreshIndex = idx;
|
||||
}
|
||||
|
||||
if (lowerResMatchNoRefreshIndex == -1 && compareVideoStreamResolution(resolutionNoRefresh, targetResolutionNoRefresh) < 0) {
|
||||
if (lowerResMatchNoRefreshIndex == -1 && compareVideoStreamResolution(
|
||||
resolutionNoRefresh, targetResolutionNoRefresh) < 0) {
|
||||
lowerResMatchNoRefreshIndex = idx;
|
||||
}
|
||||
}
|
||||
|
|
@ -332,30 +404,44 @@ public final class ListHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* Fetches the desired resolution or returns the default if it is not found. The resolution
|
||||
* will be reduced if video chocking is active.
|
||||
* Fetches the desired resolution or returns the default if it is not found.
|
||||
* The resolution will be reduced if video chocking is active.
|
||||
*
|
||||
* @param context Android app context
|
||||
* @param defaultResolution the default resolution
|
||||
* @param videoStreams the list of video streams to check
|
||||
* @return the index of the prefered video stream
|
||||
*/
|
||||
private static int getDefaultResolutionWithDefaultFormat(Context context, String defaultResolution, List<VideoStream> videoStreams) {
|
||||
MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_video_format_key, R.string.default_video_format_value);
|
||||
return getDefaultResolutionIndex(defaultResolution, context.getString(R.string.best_resolution_key), defaultFormat, videoStreams);
|
||||
private static int getDefaultResolutionWithDefaultFormat(final Context context,
|
||||
final String defaultResolution,
|
||||
final List<VideoStream> videoStreams) {
|
||||
MediaFormat defaultFormat = getDefaultFormat(context, R.string.default_video_format_key,
|
||||
R.string.default_video_format_value);
|
||||
return getDefaultResolutionIndex(defaultResolution,
|
||||
context.getString(R.string.best_resolution_key), defaultFormat, videoStreams);
|
||||
}
|
||||
|
||||
private static MediaFormat getDefaultFormat(Context context, @StringRes int defaultFormatKey, @StringRes int defaultFormatValueKey) {
|
||||
private static MediaFormat getDefaultFormat(final Context context,
|
||||
@StringRes final int defaultFormatKey,
|
||||
@StringRes final int defaultFormatValueKey) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
String defaultFormat = context.getString(defaultFormatValueKey);
|
||||
String defaultFormatString = preferences.getString(context.getString(defaultFormatKey), defaultFormat);
|
||||
String defaultFormatString = preferences.getString(
|
||||
context.getString(defaultFormatKey), defaultFormat);
|
||||
|
||||
MediaFormat defaultMediaFormat = getMediaFormatFromKey(context, defaultFormatString);
|
||||
if (defaultMediaFormat == null) {
|
||||
preferences.edit().putString(context.getString(defaultFormatKey), defaultFormat).apply();
|
||||
preferences.edit().putString(context.getString(defaultFormatKey), defaultFormat)
|
||||
.apply();
|
||||
defaultMediaFormat = getMediaFormatFromKey(context, defaultFormat);
|
||||
}
|
||||
|
||||
return defaultMediaFormat;
|
||||
}
|
||||
|
||||
private static MediaFormat getMediaFormatFromKey(Context context, String formatKey) {
|
||||
private static MediaFormat getMediaFormatFromKey(final Context context,
|
||||
final String formatKey) {
|
||||
MediaFormat format = null;
|
||||
if (formatKey.equals(context.getString(R.string.video_webm_key))) {
|
||||
format = MediaFormat.WEBM;
|
||||
|
|
@ -372,8 +458,9 @@ public final class ListHelper {
|
|||
}
|
||||
|
||||
// Compares the quality of two audio streams
|
||||
private static int compareAudioStreamBitrate(AudioStream streamA, AudioStream streamB,
|
||||
List<MediaFormat> formatRanking) {
|
||||
private static int compareAudioStreamBitrate(final AudioStream streamA,
|
||||
final AudioStream streamB,
|
||||
final List<MediaFormat> formatRanking) {
|
||||
if (streamA == null) {
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -388,10 +475,11 @@ public final class ListHelper {
|
|||
}
|
||||
|
||||
// Same bitrate and format
|
||||
return formatRanking.indexOf(streamA.getFormat()) - formatRanking.indexOf(streamB.getFormat());
|
||||
return formatRanking.indexOf(streamA.getFormat())
|
||||
- formatRanking.indexOf(streamB.getFormat());
|
||||
}
|
||||
|
||||
private static int compareVideoStreamResolution(String r1, String r2) {
|
||||
private static int compareVideoStreamResolution(final String r1, final String r2) {
|
||||
int res1 = Integer.parseInt(r1.replaceAll("0p\\d+$", "1")
|
||||
.replaceAll("[^\\d.]", ""));
|
||||
int res2 = Integer.parseInt(r2.replaceAll("0p\\d+$", "1")
|
||||
|
|
@ -400,7 +488,8 @@ public final class ListHelper {
|
|||
}
|
||||
|
||||
// Compares the quality of two video streams.
|
||||
private static int compareVideoStreamResolution(VideoStream streamA, VideoStream streamB) {
|
||||
private static int compareVideoStreamResolution(final VideoStream streamA,
|
||||
final VideoStream streamB) {
|
||||
if (streamA == null) {
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -408,27 +497,29 @@ public final class ListHelper {
|
|||
return 1;
|
||||
}
|
||||
|
||||
int resComp = compareVideoStreamResolution(streamA.getResolution(), streamB.getResolution());
|
||||
int resComp = compareVideoStreamResolution(streamA.getResolution(),
|
||||
streamB.getResolution());
|
||||
if (resComp != 0) {
|
||||
return resComp;
|
||||
}
|
||||
|
||||
// Same bitrate and format
|
||||
return ListHelper.VIDEO_FORMAT_QUALITY_RANKING.indexOf(streamA.getFormat()) - ListHelper.VIDEO_FORMAT_QUALITY_RANKING.indexOf(streamB.getFormat());
|
||||
return ListHelper.VIDEO_FORMAT_QUALITY_RANKING.indexOf(streamA.getFormat())
|
||||
- ListHelper.VIDEO_FORMAT_QUALITY_RANKING.indexOf(streamB.getFormat());
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static boolean isLimitingDataUsage(Context context) {
|
||||
private static boolean isLimitingDataUsage(final Context context) {
|
||||
return getResolutionLimit(context) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum resolution allowed
|
||||
* The maximum resolution allowed.
|
||||
*
|
||||
* @param context App context
|
||||
* @return maximum resolution allowed or null if there is no maximum
|
||||
*/
|
||||
private static String getResolutionLimit(Context context) {
|
||||
private static String getResolutionLimit(final Context context) {
|
||||
String resolutionLimit = null;
|
||||
if (isMeteredNetwork(context)) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
|
@ -442,13 +533,16 @@ public final class ListHelper {
|
|||
|
||||
/**
|
||||
* The current network is metered (like mobile data)?
|
||||
*
|
||||
* @param context App context
|
||||
* @return {@code true} if connected to a metered network
|
||||
*/
|
||||
private static boolean isMeteredNetwork(Context context)
|
||||
{
|
||||
ConnectivityManager manager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (manager == null || manager.getActiveNetworkInfo() == null) return false;
|
||||
private static boolean isMeteredNetwork(final Context context) {
|
||||
ConnectivityManager manager
|
||||
= (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (manager == null || manager.getActiveNetworkInfo() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return manager.isActiveNetworkMetered();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,15 +49,14 @@ import java.util.Locale;
|
|||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class Localization {
|
||||
public final class Localization {
|
||||
|
||||
private static final String DOT_SEPARATOR = " • ";
|
||||
private static PrettyTime prettyTime;
|
||||
|
||||
private Localization() {
|
||||
}
|
||||
private Localization() { }
|
||||
|
||||
public static void init(Context context) {
|
||||
public static void init(final Context context) {
|
||||
initPrettyTime(context);
|
||||
}
|
||||
|
||||
|
|
@ -68,7 +67,9 @@ public class Localization {
|
|||
|
||||
@NonNull
|
||||
public static String concatenateStrings(final List<String> strings) {
|
||||
if (strings.isEmpty()) return "";
|
||||
if (strings.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append(strings.get(0));
|
||||
|
|
@ -83,27 +84,31 @@ public class Localization {
|
|||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
public static org.schabi.newpipe.extractor.localization.Localization getPreferredLocalization(final Context context) {
|
||||
public static org.schabi.newpipe.extractor.localization.Localization getPreferredLocalization(
|
||||
final Context context) {
|
||||
final String contentLanguage = PreferenceManager
|
||||
.getDefaultSharedPreferences(context)
|
||||
.getString(context.getString(R.string.content_language_key), context.getString(R.string.default_localization_key));
|
||||
.getString(context.getString(R.string.content_language_key),
|
||||
context.getString(R.string.default_localization_key));
|
||||
if (contentLanguage.equals(context.getString(R.string.default_localization_key))) {
|
||||
return org.schabi.newpipe.extractor.localization.Localization.fromLocale(Locale.getDefault());
|
||||
return org.schabi.newpipe.extractor.localization.Localization
|
||||
.fromLocale(Locale.getDefault());
|
||||
}
|
||||
return org.schabi.newpipe.extractor.localization.Localization.fromLocalizationCode(contentLanguage);
|
||||
return org.schabi.newpipe.extractor.localization.Localization
|
||||
.fromLocalizationCode(contentLanguage);
|
||||
}
|
||||
|
||||
public static ContentCountry getPreferredContentCountry(final Context context) {
|
||||
final String contentCountry = PreferenceManager
|
||||
.getDefaultSharedPreferences(context)
|
||||
.getString(context.getString(R.string.content_country_key), context.getString(R.string.default_localization_key));
|
||||
final String contentCountry = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getString(context.getString(R.string.content_country_key),
|
||||
context.getString(R.string.default_localization_key));
|
||||
if (contentCountry.equals(context.getString(R.string.default_localization_key))) {
|
||||
return new ContentCountry(Locale.getDefault().getCountry());
|
||||
}
|
||||
return new ContentCountry(contentCountry);
|
||||
}
|
||||
|
||||
public static Locale getPreferredLocale(Context context) {
|
||||
public static Locale getPreferredLocale(final Context context) {
|
||||
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
String languageCode = sp.getString(context.getString(R.string.content_language_key),
|
||||
|
|
@ -122,88 +127,103 @@ public class Localization {
|
|||
return Locale.getDefault();
|
||||
}
|
||||
|
||||
public static String localizeNumber(Context context, long number) {
|
||||
public static String localizeNumber(final Context context, final long number) {
|
||||
return localizeNumber(context, (double) number);
|
||||
}
|
||||
|
||||
public static String localizeNumber(Context context, double number) {
|
||||
public static String localizeNumber(final Context context, final double number) {
|
||||
NumberFormat nf = NumberFormat.getInstance(getAppLocale(context));
|
||||
return nf.format(number);
|
||||
}
|
||||
|
||||
public static String formatDate(Date date, Context context) {
|
||||
public static String formatDate(final Date date, final Context context) {
|
||||
return DateFormat.getDateInstance(DateFormat.MEDIUM, getAppLocale(context)).format(date);
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
public static String localizeUploadDate(Context context, Date date) {
|
||||
public static String localizeUploadDate(final Context context, final Date date) {
|
||||
return context.getString(R.string.upload_date_text, formatDate(date, context));
|
||||
}
|
||||
|
||||
public static String localizeViewCount(Context context, long viewCount) {
|
||||
return getQuantity(context, R.plurals.views, R.string.no_views, viewCount, localizeNumber(context, viewCount));
|
||||
public static String localizeViewCount(final Context context, final long viewCount) {
|
||||
return getQuantity(context, R.plurals.views, R.string.no_views, viewCount,
|
||||
localizeNumber(context, viewCount));
|
||||
}
|
||||
|
||||
public static String localizeStreamCount(Context context, long streamCount) {
|
||||
return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount, localizeNumber(context, streamCount));
|
||||
public static String localizeStreamCount(final Context context, final long streamCount) {
|
||||
return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount,
|
||||
localizeNumber(context, streamCount));
|
||||
}
|
||||
|
||||
public static String localizeWatchingCount(Context context, long watchingCount) {
|
||||
return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount, localizeNumber(context, watchingCount));
|
||||
public static String localizeWatchingCount(final Context context, final long watchingCount) {
|
||||
return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount,
|
||||
localizeNumber(context, watchingCount));
|
||||
}
|
||||
|
||||
public static String shortCount(Context context, long count) {
|
||||
public static String shortCount(final Context context, final long count) {
|
||||
double value = (double) count;
|
||||
if (count >= 1000000000) {
|
||||
return localizeNumber(context, round(value / 1000000000, 1)) + context.getString(R.string.short_billion);
|
||||
return localizeNumber(context, round(value / 1000000000, 1))
|
||||
+ context.getString(R.string.short_billion);
|
||||
} else if (count >= 1000000) {
|
||||
return localizeNumber(context, round(value / 1000000, 1)) + context.getString(R.string.short_million);
|
||||
return localizeNumber(context, round(value / 1000000, 1))
|
||||
+ context.getString(R.string.short_million);
|
||||
} else if (count >= 1000) {
|
||||
return localizeNumber(context, round(value / 1000, 1)) + context.getString(R.string.short_thousand);
|
||||
return localizeNumber(context, round(value / 1000, 1))
|
||||
+ context.getString(R.string.short_thousand);
|
||||
} else {
|
||||
return localizeNumber(context, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static String listeningCount(Context context, long listeningCount) {
|
||||
return getQuantity(context, R.plurals.listening, R.string.no_one_listening, listeningCount, shortCount(context, listeningCount));
|
||||
public static String listeningCount(final Context context, final long listeningCount) {
|
||||
return getQuantity(context, R.plurals.listening, R.string.no_one_listening, listeningCount,
|
||||
shortCount(context, listeningCount));
|
||||
}
|
||||
|
||||
public static String shortWatchingCount(Context context, long watchingCount) {
|
||||
return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount, shortCount(context, watchingCount));
|
||||
public static String shortWatchingCount(final Context context, final long watchingCount) {
|
||||
return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount,
|
||||
shortCount(context, watchingCount));
|
||||
}
|
||||
|
||||
public static String shortViewCount(Context context, long viewCount) {
|
||||
return getQuantity(context, R.plurals.views, R.string.no_views, viewCount, shortCount(context, viewCount));
|
||||
public static String shortViewCount(final Context context, final long viewCount) {
|
||||
return getQuantity(context, R.plurals.views, R.string.no_views, viewCount,
|
||||
shortCount(context, viewCount));
|
||||
}
|
||||
|
||||
public static String shortSubscriberCount(Context context, long subscriberCount) {
|
||||
return getQuantity(context, R.plurals.subscribers, R.string.no_subscribers, subscriberCount, shortCount(context, subscriberCount));
|
||||
public static String shortSubscriberCount(final Context context, final long subscriberCount) {
|
||||
return getQuantity(context, R.plurals.subscribers, R.string.no_subscribers, subscriberCount,
|
||||
shortCount(context, subscriberCount));
|
||||
}
|
||||
|
||||
private static String getQuantity(Context context, @PluralsRes int pluralId, @StringRes int zeroCaseStringId, long count, String formattedCount) {
|
||||
if (count == 0) return context.getString(zeroCaseStringId);
|
||||
private static String getQuantity(final Context context, @PluralsRes final int pluralId,
|
||||
@StringRes final int zeroCaseStringId, final long count,
|
||||
final String formattedCount) {
|
||||
if (count == 0) {
|
||||
return context.getString(zeroCaseStringId);
|
||||
}
|
||||
|
||||
// As we use the already formatted count, is not the responsibility of this method handle long numbers
|
||||
// (it probably will fall in the "other" category, or some language have some specific rule... then we have to change it)
|
||||
int safeCount = count > Integer.MAX_VALUE ? Integer.MAX_VALUE : count < Integer.MIN_VALUE ? Integer.MIN_VALUE : (int) count;
|
||||
// As we use the already formatted count
|
||||
// is not the responsibility of this method handle long numbers
|
||||
// (it probably will fall in the "other" category,
|
||||
// or some language have some specific rule... then we have to change it)
|
||||
int safeCount = count > Integer.MAX_VALUE ? Integer.MAX_VALUE : count < Integer.MIN_VALUE
|
||||
? Integer.MIN_VALUE : (int) count;
|
||||
return context.getResources().getQuantityString(pluralId, safeCount, formattedCount);
|
||||
}
|
||||
|
||||
public static String getDurationString(long duration) {
|
||||
if (duration < 0) {
|
||||
duration = 0;
|
||||
}
|
||||
String output;
|
||||
long days = duration / (24 * 60 * 60L); /* greater than a day */
|
||||
duration %= (24 * 60 * 60L);
|
||||
long hours = duration / (60 * 60L); /* greater than an hour */
|
||||
duration %= (60 * 60L);
|
||||
long minutes = duration / 60L;
|
||||
long seconds = duration % 60L;
|
||||
public static String getDurationString(final long duration) {
|
||||
final String output;
|
||||
|
||||
//handle days
|
||||
if (days > 0) {
|
||||
final long days = duration / (24 * 60 * 60L); /* greater than a day */
|
||||
final long hours = duration % (24 * 60 * 60L) / (60 * 60L); /* greater than an hour */
|
||||
final long minutes = duration % (24 * 60 * 60L) % (60 * 60L) / 60L;
|
||||
final long seconds = duration % 60L;
|
||||
|
||||
if (duration < 0) {
|
||||
output = "0:00";
|
||||
} else if (days > 0) {
|
||||
//handle days
|
||||
output = String.format(Locale.US, "%d:%02d:%02d:%02d", days, hours, minutes, seconds);
|
||||
} else if (hours > 0) {
|
||||
output = String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds);
|
||||
|
|
@ -219,22 +239,20 @@ public class Localization {
|
|||
* <p>The seconds will be converted to the closest whole time unit.
|
||||
* <p>For example, 60 seconds would give "1 minute", 119 would also give "1 minute".
|
||||
*
|
||||
* @param context used to get plurals resources.
|
||||
* @param context used to get plurals resources.
|
||||
* @param durationInSecs an amount of seconds.
|
||||
* @return duration in a human readable string.
|
||||
*/
|
||||
@NonNull
|
||||
public static String localizeDuration(Context context, int durationInSecs) {
|
||||
public static String localizeDuration(final Context context, final int durationInSecs) {
|
||||
if (durationInSecs < 0) {
|
||||
throw new IllegalArgumentException("duration can not be negative");
|
||||
}
|
||||
|
||||
final int days = (int) (durationInSecs / (24 * 60 * 60L)); /* greater than a day */
|
||||
durationInSecs %= (24 * 60 * 60L);
|
||||
final int hours = (int) (durationInSecs / (60 * 60L)); /* greater than an hour */
|
||||
durationInSecs %= (60 * 60L);
|
||||
final int minutes = (int) (durationInSecs / 60L);
|
||||
final int seconds = (int) (durationInSecs % 60L);
|
||||
final int days = (int) (durationInSecs / (24 * 60 * 60L));
|
||||
final int hours = (int) (durationInSecs % (24 * 60 * 60L) / (60 * 60L));
|
||||
final int minutes = (int) (durationInSecs % (24 * 60 * 60L) % (60 * 60L) / 60L);
|
||||
final int seconds = (int) (durationInSecs % (24 * 60 * 60L) % (60 * 60L) % 60L);
|
||||
|
||||
final Resources resources = context.getResources();
|
||||
|
||||
|
|
@ -253,7 +271,7 @@ public class Localization {
|
|||
// Pretty Time
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private static void initPrettyTime(Context context) {
|
||||
private static void initPrettyTime(final Context context) {
|
||||
prettyTime = new PrettyTime(getAppLocale(context));
|
||||
// Do not use decades as YouTube doesn't either.
|
||||
prettyTime.removeUnit(Decade.class);
|
||||
|
|
@ -263,20 +281,20 @@ public class Localization {
|
|||
return prettyTime;
|
||||
}
|
||||
|
||||
public static String relativeTime(Calendar calendarTime) {
|
||||
public static String relativeTime(final Calendar calendarTime) {
|
||||
String time = getPrettyTime().formatUnrounded(calendarTime);
|
||||
return time.startsWith("-") ? time.substring(1) : time;
|
||||
//workaround fix for russian showing -1 day ago, -19hrs ago…
|
||||
}
|
||||
|
||||
private static void changeAppLanguage(Locale loc, Resources res) {
|
||||
private static void changeAppLanguage(final Locale loc, final Resources res) {
|
||||
DisplayMetrics dm = res.getDisplayMetrics();
|
||||
Configuration conf = res.getConfiguration();
|
||||
conf.setLocale(loc);
|
||||
res.updateConfiguration(conf, dm);
|
||||
}
|
||||
|
||||
public static Locale getAppLocale(Context context) {
|
||||
public static Locale getAppLocale(final Context context) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
String lang = prefs.getString(context.getString(R.string.app_language_key), "en");
|
||||
Locale loc;
|
||||
|
|
@ -295,11 +313,11 @@ public class Localization {
|
|||
return loc;
|
||||
}
|
||||
|
||||
public static void assureCorrectAppLanguage(Context c) {
|
||||
public static void assureCorrectAppLanguage(final Context c) {
|
||||
changeAppLanguage(getAppLocale(c), c.getResources());
|
||||
}
|
||||
|
||||
private static double round(double value, int places) {
|
||||
private static double round(final double value, final int places) {
|
||||
return new BigDecimal(value).setScale(places, RoundingMode.HALF_UP).doubleValue();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,14 +8,15 @@ import android.content.Intent;
|
|||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
|
|
@ -58,10 +59,12 @@ import org.schabi.newpipe.settings.SettingsActivity;
|
|||
import java.util.ArrayList;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public class NavigationHelper {
|
||||
public final class NavigationHelper {
|
||||
public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag";
|
||||
public static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag";
|
||||
|
||||
private NavigationHelper() { }
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Players
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
|
@ -75,8 +78,12 @@ public class NavigationHelper {
|
|||
Intent intent = new Intent(context, targetClazz);
|
||||
|
||||
final String cacheKey = SerializedCache.getInstance().put(playQueue, PlayQueue.class);
|
||||
if (cacheKey != null) intent.putExtra(VideoPlayer.PLAY_QUEUE_KEY, cacheKey);
|
||||
if (quality != null) intent.putExtra(VideoPlayer.PLAYBACK_QUALITY, quality);
|
||||
if (cacheKey != null) {
|
||||
intent.putExtra(VideoPlayer.PLAY_QUEUE_KEY, cacheKey);
|
||||
}
|
||||
if (quality != null) {
|
||||
intent.putExtra(VideoPlayer.PLAYBACK_QUALITY, quality);
|
||||
}
|
||||
intent.putExtra(VideoPlayer.RESUME_PLAYBACK, resumePlayback);
|
||||
|
||||
return intent;
|
||||
|
|
@ -105,13 +112,11 @@ public class NavigationHelper {
|
|||
public static Intent getPlayerIntent(@NonNull final Context context,
|
||||
@NonNull final Class targetClazz,
|
||||
@NonNull final PlayQueue playQueue,
|
||||
final int repeatMode,
|
||||
final float playbackSpeed,
|
||||
final int repeatMode, final float playbackSpeed,
|
||||
final float playbackPitch,
|
||||
final boolean playbackSkipSilence,
|
||||
@Nullable final String playbackQuality,
|
||||
final boolean resumePlayback,
|
||||
final boolean startPaused,
|
||||
final boolean resumePlayback, final boolean startPaused,
|
||||
final boolean isMuted) {
|
||||
return getPlayerIntent(context, targetClazz, playQueue, playbackQuality, resumePlayback)
|
||||
.putExtra(BasePlayer.REPEAT_MODE, repeatMode)
|
||||
|
|
@ -122,50 +127,63 @@ public class NavigationHelper {
|
|||
.putExtra(BasePlayer.IS_MUTED, isMuted);
|
||||
}
|
||||
|
||||
public static void playOnMainPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) {
|
||||
final Intent playerIntent = getPlayerIntent(context, MainVideoPlayer.class, queue, resumePlayback);
|
||||
public static void playOnMainPlayer(final Context context, final PlayQueue queue,
|
||||
final boolean resumePlayback) {
|
||||
final Intent playerIntent
|
||||
= getPlayerIntent(context, MainVideoPlayer.class, queue, resumePlayback);
|
||||
playerIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(playerIntent);
|
||||
}
|
||||
|
||||
public static void playOnPopupPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) {
|
||||
public static void playOnPopupPlayer(final Context context, final PlayQueue queue,
|
||||
final boolean resumePlayback) {
|
||||
if (!PermissionHelper.isPopupEnabled(context)) {
|
||||
PermissionHelper.showPopupEnablementToast(context);
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
|
||||
startService(context, getPlayerIntent(context, PopupVideoPlayer.class, queue, resumePlayback));
|
||||
startService(context,
|
||||
getPlayerIntent(context, PopupVideoPlayer.class, queue, resumePlayback));
|
||||
}
|
||||
|
||||
public static void playOnBackgroundPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) {
|
||||
Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show();
|
||||
startService(context, getPlayerIntent(context, BackgroundPlayer.class, queue, resumePlayback));
|
||||
public static void playOnBackgroundPlayer(final Context context, final PlayQueue queue,
|
||||
final boolean resumePlayback) {
|
||||
Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
startService(context,
|
||||
getPlayerIntent(context, BackgroundPlayer.class, queue, resumePlayback));
|
||||
}
|
||||
|
||||
public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) {
|
||||
public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue,
|
||||
final boolean resumePlayback) {
|
||||
enqueueOnPopupPlayer(context, queue, false, resumePlayback);
|
||||
}
|
||||
|
||||
public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend, final boolean resumePlayback) {
|
||||
public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue,
|
||||
final boolean selectOnAppend,
|
||||
final boolean resumePlayback) {
|
||||
if (!PermissionHelper.isPopupEnabled(context)) {
|
||||
PermissionHelper.showPopupEnablementToast(context);
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(context, R.string.popup_playing_append, Toast.LENGTH_SHORT).show();
|
||||
startService(context,
|
||||
getPlayerEnqueueIntent(context, PopupVideoPlayer.class, queue, selectOnAppend, resumePlayback));
|
||||
startService(context, getPlayerEnqueueIntent(context, PopupVideoPlayer.class, queue,
|
||||
selectOnAppend, resumePlayback));
|
||||
}
|
||||
|
||||
public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) {
|
||||
public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue,
|
||||
final boolean resumePlayback) {
|
||||
enqueueOnBackgroundPlayer(context, queue, false, resumePlayback);
|
||||
}
|
||||
|
||||
public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend, final boolean resumePlayback) {
|
||||
public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue,
|
||||
final boolean selectOnAppend,
|
||||
final boolean resumePlayback) {
|
||||
Toast.makeText(context, R.string.background_player_append, Toast.LENGTH_SHORT).show();
|
||||
startService(context,
|
||||
getPlayerEnqueueIntent(context, BackgroundPlayer.class, queue, selectOnAppend, resumePlayback));
|
||||
startService(context, getPlayerEnqueueIntent(context, BackgroundPlayer.class, queue,
|
||||
selectOnAppend, resumePlayback));
|
||||
}
|
||||
|
||||
public static void startService(@NonNull final Context context, @NonNull final Intent intent) {
|
||||
|
|
@ -180,7 +198,7 @@ public class NavigationHelper {
|
|||
// External Players
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public static void playOnExternalAudioPlayer(Context context, StreamInfo info) {
|
||||
public static void playOnExternalAudioPlayer(final Context context, final StreamInfo info) {
|
||||
final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams());
|
||||
|
||||
if (index == -1) {
|
||||
|
|
@ -192,8 +210,9 @@ public class NavigationHelper {
|
|||
playOnExternalPlayer(context, info.getName(), info.getUploaderName(), audioStream);
|
||||
}
|
||||
|
||||
public static void playOnExternalVideoPlayer(Context context, StreamInfo info) {
|
||||
ArrayList<VideoStream> videoStreamsList = new ArrayList<>(ListHelper.getSortedStreamVideosList(context, info.getVideoStreams(), null, false));
|
||||
public static void playOnExternalVideoPlayer(final Context context, final StreamInfo info) {
|
||||
ArrayList<VideoStream> videoStreamsList = new ArrayList<>(
|
||||
ListHelper.getSortedStreamVideosList(context, info.getVideoStreams(), null, false));
|
||||
int index = ListHelper.getDefaultResolutionIndex(context, videoStreamsList);
|
||||
|
||||
if (index == -1) {
|
||||
|
|
@ -205,7 +224,8 @@ public class NavigationHelper {
|
|||
playOnExternalPlayer(context, info.getName(), info.getUploaderName(), videoStream);
|
||||
}
|
||||
|
||||
public static void playOnExternalPlayer(Context context, String name, String artist, Stream stream) {
|
||||
public static void playOnExternalPlayer(final Context context, final String name,
|
||||
final String artist, final Stream stream) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(Uri.parse(stream.getUrl()), stream.getFormat().getMimeType());
|
||||
|
|
@ -217,7 +237,7 @@ public class NavigationHelper {
|
|||
resolveActivityOrAskToInstall(context, intent);
|
||||
}
|
||||
|
||||
public static void resolveActivityOrAskToInstall(Context context, Intent intent) {
|
||||
public static void resolveActivityOrAskToInstall(final Context context, final Intent intent) {
|
||||
if (intent.resolveActivity(context.getPackageManager()) != null) {
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
|
|
@ -230,9 +250,12 @@ public class NavigationHelper {
|
|||
i.setData(Uri.parse(context.getString(R.string.fdroid_vlc_url)));
|
||||
context.startActivity(i);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) -> Log.i("NavigationHelper", "You unlocked a secret unicorn."))
|
||||
.setNegativeButton(R.string.cancel, (dialog, which)
|
||||
-> Log.i("NavigationHelper", "You unlocked a secret unicorn."))
|
||||
.show();
|
||||
//Log.e("NavigationHelper", "Either no Streaming player for audio was installed, or something important crashed:");
|
||||
// Log.e("NavigationHelper",
|
||||
// "Either no Streaming player for audio was installed, "
|
||||
// + "or something important crashed:");
|
||||
} else {
|
||||
Toast.makeText(context, R.string.no_player_found_toast, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
|
@ -244,19 +267,22 @@ public class NavigationHelper {
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@SuppressLint("CommitTransaction")
|
||||
private static FragmentTransaction defaultTransaction(FragmentManager fragmentManager) {
|
||||
private static FragmentTransaction defaultTransaction(final FragmentManager fragmentManager) {
|
||||
return fragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, R.animator.custom_fade_in, R.animator.custom_fade_out);
|
||||
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out,
|
||||
R.animator.custom_fade_in, R.animator.custom_fade_out);
|
||||
}
|
||||
|
||||
public static void gotoMainFragment(FragmentManager fragmentManager) {
|
||||
public static void gotoMainFragment(final FragmentManager fragmentManager) {
|
||||
ImageLoader.getInstance().clearMemoryCache();
|
||||
|
||||
boolean popped = fragmentManager.popBackStackImmediate(MAIN_FRAGMENT_TAG, 0);
|
||||
if (!popped) openMainFragment(fragmentManager);
|
||||
if (!popped) {
|
||||
openMainFragment(fragmentManager);
|
||||
}
|
||||
}
|
||||
|
||||
public static void openMainFragment(FragmentManager fragmentManager) {
|
||||
public static void openMainFragment(final FragmentManager fragmentManager) {
|
||||
InfoCache.getInstance().trimCache();
|
||||
|
||||
fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
|
|
@ -266,41 +292,45 @@ public class NavigationHelper {
|
|||
.commit();
|
||||
}
|
||||
|
||||
public static boolean tryGotoSearchFragment(FragmentManager fragmentManager) {
|
||||
public static boolean tryGotoSearchFragment(final FragmentManager fragmentManager) {
|
||||
if (MainActivity.DEBUG) {
|
||||
for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) {
|
||||
Log.d("NavigationHelper", "tryGoToSearchFragment() [" + i + "] = [" + fragmentManager.getBackStackEntryAt(i) + "]");
|
||||
Log.d("NavigationHelper", "tryGoToSearchFragment() [" + i + "]"
|
||||
+ " = [" + fragmentManager.getBackStackEntryAt(i) + "]");
|
||||
}
|
||||
}
|
||||
|
||||
return fragmentManager.popBackStackImmediate(SEARCH_FRAGMENT_TAG, 0);
|
||||
}
|
||||
|
||||
public static void openSearchFragment(FragmentManager fragmentManager,
|
||||
int serviceId,
|
||||
String searchString) {
|
||||
public static void openSearchFragment(final FragmentManager fragmentManager,
|
||||
final int serviceId, final String searchString) {
|
||||
defaultTransaction(fragmentManager)
|
||||
.replace(R.id.fragment_holder, SearchFragment.getInstance(serviceId, searchString))
|
||||
.addToBackStack(SEARCH_FRAGMENT_TAG)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void openVideoDetailFragment(FragmentManager fragmentManager, int serviceId, String url, String title) {
|
||||
public static void openVideoDetailFragment(final FragmentManager fragmentManager,
|
||||
final int serviceId, final String url,
|
||||
final String title) {
|
||||
openVideoDetailFragment(fragmentManager, serviceId, url, title, false);
|
||||
}
|
||||
|
||||
public static void openVideoDetailFragment(FragmentManager fragmentManager, int serviceId, String url, String title, boolean autoPlay) {
|
||||
public static void openVideoDetailFragment(final FragmentManager fragmentManager,
|
||||
final int serviceId, final String url,
|
||||
final String name, final boolean autoPlay) {
|
||||
Fragment fragment = fragmentManager.findFragmentById(R.id.fragment_holder);
|
||||
if (title == null) title = "";
|
||||
|
||||
if (fragment instanceof VideoDetailFragment && fragment.isVisible()) {
|
||||
VideoDetailFragment detailFragment = (VideoDetailFragment) fragment;
|
||||
detailFragment.setAutoplay(autoPlay);
|
||||
detailFragment.selectAndLoadVideo(serviceId, url, title);
|
||||
detailFragment.selectAndLoadVideo(serviceId, url, name == null ? "" : name);
|
||||
return;
|
||||
}
|
||||
|
||||
VideoDetailFragment instance = VideoDetailFragment.getInstance(serviceId, url, title);
|
||||
VideoDetailFragment instance = VideoDetailFragment.getInstance(serviceId, url,
|
||||
name == null ? "" : name);
|
||||
instance.setAutoplay(autoPlay);
|
||||
|
||||
defaultTransaction(fragmentManager)
|
||||
|
|
@ -309,89 +339,89 @@ public class NavigationHelper {
|
|||
.commit();
|
||||
}
|
||||
|
||||
public static void openChannelFragment(
|
||||
FragmentManager fragmentManager,
|
||||
int serviceId,
|
||||
String url,
|
||||
String name) {
|
||||
if (name == null) name = "";
|
||||
public static void openChannelFragment(final FragmentManager fragmentManager,
|
||||
final int serviceId, final String url,
|
||||
final String name) {
|
||||
defaultTransaction(fragmentManager)
|
||||
.replace(R.id.fragment_holder, ChannelFragment.getInstance(serviceId, url, name))
|
||||
.replace(R.id.fragment_holder, ChannelFragment.getInstance(serviceId, url,
|
||||
name == null ? "" : name))
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void openCommentsFragment(
|
||||
FragmentManager fragmentManager,
|
||||
int serviceId,
|
||||
String url,
|
||||
String name) {
|
||||
if (name == null) name = "";
|
||||
fragmentManager.beginTransaction().setCustomAnimations(R.anim.switch_service_in, R.anim.switch_service_out)
|
||||
.replace(R.id.fragment_holder, CommentsFragment.getInstance(serviceId, url, name))
|
||||
public static void openCommentsFragment(final FragmentManager fragmentManager,
|
||||
final int serviceId, final String url,
|
||||
final String name) {
|
||||
fragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.switch_service_in, R.anim.switch_service_out)
|
||||
.replace(R.id.fragment_holder, CommentsFragment.getInstance(serviceId, url,
|
||||
name == null ? "" : name))
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void openPlaylistFragment(FragmentManager fragmentManager,
|
||||
int serviceId,
|
||||
String url,
|
||||
String name) {
|
||||
if (name == null) name = "";
|
||||
public static void openPlaylistFragment(final FragmentManager fragmentManager,
|
||||
final int serviceId, final String url,
|
||||
final String name) {
|
||||
defaultTransaction(fragmentManager)
|
||||
.replace(R.id.fragment_holder, PlaylistFragment.getInstance(serviceId, url, name))
|
||||
.replace(R.id.fragment_holder, PlaylistFragment.getInstance(serviceId, url,
|
||||
name == null ? "" : name))
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void openFeedFragment(FragmentManager fragmentManager) {
|
||||
public static void openFeedFragment(final FragmentManager fragmentManager) {
|
||||
openFeedFragment(fragmentManager, FeedGroupEntity.GROUP_ALL_ID, null);
|
||||
}
|
||||
|
||||
public static void openFeedFragment(FragmentManager fragmentManager, long groupId, @Nullable String groupName) {
|
||||
public static void openFeedFragment(final FragmentManager fragmentManager, final long groupId,
|
||||
@Nullable final String groupName) {
|
||||
defaultTransaction(fragmentManager)
|
||||
.replace(R.id.fragment_holder, FeedFragment.newInstance(groupId, groupName))
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void openBookmarksFragment(FragmentManager fragmentManager) {
|
||||
public static void openBookmarksFragment(final FragmentManager fragmentManager) {
|
||||
defaultTransaction(fragmentManager)
|
||||
.replace(R.id.fragment_holder, new BookmarkFragment())
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void openSubscriptionFragment(FragmentManager fragmentManager) {
|
||||
public static void openSubscriptionFragment(final FragmentManager fragmentManager) {
|
||||
defaultTransaction(fragmentManager)
|
||||
.replace(R.id.fragment_holder, new SubscriptionFragment())
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void openKioskFragment(FragmentManager fragmentManager, int serviceId, String kioskId) throws ExtractionException {
|
||||
public static void openKioskFragment(final FragmentManager fragmentManager, final int serviceId,
|
||||
final String kioskId) throws ExtractionException {
|
||||
defaultTransaction(fragmentManager)
|
||||
.replace(R.id.fragment_holder, KioskFragment.getInstance(serviceId, kioskId))
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void openLocalPlaylistFragment(FragmentManager fragmentManager, long playlistId, String name) {
|
||||
if (name == null) name = "";
|
||||
public static void openLocalPlaylistFragment(final FragmentManager fragmentManager,
|
||||
final long playlistId, final String name) {
|
||||
defaultTransaction(fragmentManager)
|
||||
.replace(R.id.fragment_holder, LocalPlaylistFragment.getInstance(playlistId, name))
|
||||
.replace(R.id.fragment_holder, LocalPlaylistFragment.getInstance(playlistId,
|
||||
name == null ? "" : name))
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void openStatisticFragment(FragmentManager fragmentManager) {
|
||||
public static void openStatisticFragment(final FragmentManager fragmentManager) {
|
||||
defaultTransaction(fragmentManager)
|
||||
.replace(R.id.fragment_holder, new StatisticsPlaylistFragment())
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void openSubscriptionsImportFragment(FragmentManager fragmentManager, int serviceId) {
|
||||
public static void openSubscriptionsImportFragment(final FragmentManager fragmentManager,
|
||||
final int serviceId) {
|
||||
defaultTransaction(fragmentManager)
|
||||
.replace(R.id.fragment_holder, SubscriptionsImportFragment.getInstance(serviceId))
|
||||
.addToBackStack(null)
|
||||
|
|
@ -402,7 +432,8 @@ public class NavigationHelper {
|
|||
// Through Intents
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public static void openSearch(Context context, int serviceId, String searchString) {
|
||||
public static void openSearch(final Context context, final int serviceId,
|
||||
final String searchString) {
|
||||
Intent mIntent = new Intent(context, MainActivity.class);
|
||||
mIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId);
|
||||
mIntent.putExtra(Constants.KEY_SEARCH_STRING, searchString);
|
||||
|
|
@ -410,52 +441,62 @@ public class NavigationHelper {
|
|||
context.startActivity(mIntent);
|
||||
}
|
||||
|
||||
public static void openChannel(Context context, int serviceId, String url) {
|
||||
public static void openChannel(final Context context, final int serviceId, final String url) {
|
||||
openChannel(context, serviceId, url, null);
|
||||
}
|
||||
|
||||
public static void openChannel(Context context, int serviceId, String url, String name) {
|
||||
Intent openIntent = getOpenIntent(context, url, serviceId, StreamingService.LinkType.CHANNEL);
|
||||
if (name != null && !name.isEmpty()) openIntent.putExtra(Constants.KEY_TITLE, name);
|
||||
public static void openChannel(final Context context, final int serviceId,
|
||||
final String url, final String name) {
|
||||
Intent openIntent = getOpenIntent(context, url, serviceId,
|
||||
StreamingService.LinkType.CHANNEL);
|
||||
if (name != null && !name.isEmpty()) {
|
||||
openIntent.putExtra(Constants.KEY_TITLE, name);
|
||||
}
|
||||
context.startActivity(openIntent);
|
||||
}
|
||||
|
||||
public static void openVideoDetail(Context context, int serviceId, String url) {
|
||||
public static void openVideoDetail(final Context context, final int serviceId,
|
||||
final String url) {
|
||||
openVideoDetail(context, serviceId, url, null);
|
||||
}
|
||||
|
||||
public static void openVideoDetail(Context context, int serviceId, String url, String title) {
|
||||
Intent openIntent = getOpenIntent(context, url, serviceId, StreamingService.LinkType.STREAM);
|
||||
if (title != null && !title.isEmpty()) openIntent.putExtra(Constants.KEY_TITLE, title);
|
||||
public static void openVideoDetail(final Context context, final int serviceId,
|
||||
final String url, final String title) {
|
||||
Intent openIntent = getOpenIntent(context, url, serviceId,
|
||||
StreamingService.LinkType.STREAM);
|
||||
if (title != null && !title.isEmpty()) {
|
||||
openIntent.putExtra(Constants.KEY_TITLE, title);
|
||||
}
|
||||
context.startActivity(openIntent);
|
||||
}
|
||||
|
||||
public static void openMainActivity(Context context) {
|
||||
public static void openMainActivity(final Context context) {
|
||||
Intent mIntent = new Intent(context, MainActivity.class);
|
||||
mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
mIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
context.startActivity(mIntent);
|
||||
}
|
||||
|
||||
public static void openRouterActivity(Context context, String url) {
|
||||
public static void openRouterActivity(final Context context, final String url) {
|
||||
Intent mIntent = new Intent(context, RouterActivity.class);
|
||||
mIntent.setData(Uri.parse(url));
|
||||
mIntent.putExtra(RouterActivity.internalRouteKey, true);
|
||||
mIntent.putExtra(RouterActivity.INTERNAL_ROUTE_KEY, true);
|
||||
context.startActivity(mIntent);
|
||||
}
|
||||
|
||||
public static void openAbout(Context context) {
|
||||
public static void openAbout(final Context context) {
|
||||
Intent intent = new Intent(context, AboutActivity.class);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
public static void openSettings(Context context) {
|
||||
public static void openSettings(final Context context) {
|
||||
Intent intent = new Intent(context, SettingsActivity.class);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
public static boolean openDownloads(Activity activity) {
|
||||
if (!PermissionHelper.checkStoragePermissions(activity, PermissionHelper.DOWNLOADS_REQUEST_CODE)) {
|
||||
public static boolean openDownloads(final Activity activity) {
|
||||
if (!PermissionHelper.checkStoragePermissions(
|
||||
activity, PermissionHelper.DOWNLOADS_REQUEST_CODE)) {
|
||||
return false;
|
||||
}
|
||||
Intent intent = new Intent(activity, DownloadActivity.class);
|
||||
|
|
@ -483,7 +524,8 @@ public class NavigationHelper {
|
|||
// Link handling
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private static Intent getOpenIntent(Context context, String url, int serviceId, StreamingService.LinkType type) {
|
||||
private static Intent getOpenIntent(final Context context, final String url,
|
||||
final int serviceId, final StreamingService.LinkType type) {
|
||||
Intent mIntent = new Intent(context, MainActivity.class);
|
||||
mIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId);
|
||||
mIntent.putExtra(Constants.KEY_URL, url);
|
||||
|
|
@ -491,45 +533,46 @@ public class NavigationHelper {
|
|||
return mIntent;
|
||||
}
|
||||
|
||||
public static Intent getIntentByLink(Context context, String url) throws ExtractionException {
|
||||
public static Intent getIntentByLink(final Context context, final String url)
|
||||
throws ExtractionException {
|
||||
return getIntentByLink(context, NewPipe.getServiceByUrl(url), url);
|
||||
}
|
||||
|
||||
public static Intent getIntentByLink(Context context, StreamingService service, String url) throws ExtractionException {
|
||||
public static Intent getIntentByLink(final Context context, final StreamingService service,
|
||||
final String url) throws ExtractionException {
|
||||
StreamingService.LinkType linkType = service.getLinkTypeByUrl(url);
|
||||
|
||||
if (linkType == StreamingService.LinkType.NONE) {
|
||||
throw new ExtractionException("Url not known to service. service=" + service + " url=" + url);
|
||||
throw new ExtractionException("Url not known to service. service=" + service
|
||||
+ " url=" + url);
|
||||
}
|
||||
|
||||
Intent rIntent = getOpenIntent(context, url, service.getServiceId(), linkType);
|
||||
|
||||
switch (linkType) {
|
||||
case STREAM:
|
||||
rIntent.putExtra(VideoDetailFragment.AUTO_PLAY,
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean(context.getString(R.string.autoplay_through_intent_key), false));
|
||||
break;
|
||||
if (linkType == StreamingService.LinkType.STREAM) {
|
||||
rIntent.putExtra(VideoDetailFragment.AUTO_PLAY,
|
||||
PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
|
||||
context.getString(R.string.autoplay_through_intent_key), false));
|
||||
}
|
||||
|
||||
return rIntent;
|
||||
}
|
||||
|
||||
private static Uri openMarketUrl(String packageName) {
|
||||
private static Uri openMarketUrl(final String packageName) {
|
||||
return Uri.parse("market://details")
|
||||
.buildUpon()
|
||||
.appendQueryParameter("id", packageName)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Uri getGooglePlayUrl(String packageName) {
|
||||
private static Uri getGooglePlayUrl(final String packageName) {
|
||||
return Uri.parse("https://play.google.com/store/apps/details")
|
||||
.buildUpon()
|
||||
.appendQueryParameter("id", packageName)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static void installApp(Context context, String packageName) {
|
||||
private static void installApp(final Context context, final String packageName) {
|
||||
try {
|
||||
// Try market:// scheme
|
||||
context.startActivity(new Intent(Intent.ACTION_VIEW, openMarketUrl(packageName)));
|
||||
|
|
@ -540,25 +583,26 @@ public class NavigationHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* Start an activity to install Kore
|
||||
* Start an activity to install Kore.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static void installKore(Context context) {
|
||||
public static void installKore(final Context context) {
|
||||
installApp(context, context.getString(R.string.kore_package));
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Kore app to show a video on Kodi
|
||||
*
|
||||
* Start Kore app to show a video on Kodi.
|
||||
* <p>
|
||||
* For a list of supported urls see the
|
||||
* <a href="https://github.com/xbmc/Kore/blob/master/app/src/main/AndroidManifest.xml">
|
||||
* Kore source code
|
||||
* Kore source code
|
||||
* </a>.
|
||||
*
|
||||
* @param context the context to use
|
||||
* @param context the context to use
|
||||
* @param videoURL the url to the video
|
||||
*/
|
||||
public static void playWithKore(Context context, Uri videoURL) {
|
||||
public static void playWithKore(final Context context, final Uri videoURL) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setPackage(context.getString(R.string.kore_package));
|
||||
intent.setData(videoURL);
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ public abstract class OnClickGesture<T> {
|
|||
|
||||
public abstract void selected(T selectedItem);
|
||||
|
||||
public void held(T selectedItem) {
|
||||
public void held(final T selectedItem) {
|
||||
// Optional gesture
|
||||
}
|
||||
|
||||
public void drag(T selectedItem, RecyclerView.ViewHolder viewHolder) {
|
||||
public void drag(final T selectedItem, final RecyclerView.ViewHolder viewHolder) {
|
||||
// Optional gesture
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,12 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class PeertubeHelper {
|
||||
public final class PeertubeHelper {
|
||||
private PeertubeHelper() { }
|
||||
|
||||
public static List<PeertubeInstance> getInstanceList(Context context) {
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
public static List<PeertubeInstance> getInstanceList(final Context context) {
|
||||
SharedPreferences sharedPreferences = PreferenceManager
|
||||
.getDefaultSharedPreferences(context);
|
||||
String savedInstanceListKey = context.getString(R.string.peertube_instance_list_key);
|
||||
final String savedJson = sharedPreferences.getString(savedInstanceListKey, null);
|
||||
if (null == savedJson) {
|
||||
|
|
@ -47,8 +49,10 @@ public class PeertubeHelper {
|
|||
|
||||
}
|
||||
|
||||
public static PeertubeInstance selectInstance(PeertubeInstance instance, Context context) {
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
public static PeertubeInstance selectInstance(final PeertubeInstance instance,
|
||||
final Context context) {
|
||||
SharedPreferences sharedPreferences = PreferenceManager
|
||||
.getDefaultSharedPreferences(context);
|
||||
String selectedInstanceKey = context.getString(R.string.peertube_selected_instance_key);
|
||||
JsonStringWriter jsonWriter = JsonWriter.string().object();
|
||||
jsonWriter.value("name", instance.getName());
|
||||
|
|
@ -59,7 +63,7 @@ public class PeertubeHelper {
|
|||
return instance;
|
||||
}
|
||||
|
||||
public static PeertubeInstance getCurrentInstance(){
|
||||
public static PeertubeInstance getCurrentInstance() {
|
||||
return ServiceList.PeerTube.getInstance();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,28 +8,34 @@ import android.content.pm.PackageManager;
|
|||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import android.view.Gravity;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class PermissionHelper {
|
||||
public final class PermissionHelper {
|
||||
public static final int DOWNLOAD_DIALOG_REQUEST_CODE = 778;
|
||||
public static final int DOWNLOADS_REQUEST_CODE = 777;
|
||||
|
||||
public static boolean checkStoragePermissions(Activity activity, int requestCode) {
|
||||
private PermissionHelper() { }
|
||||
|
||||
public static boolean checkStoragePermissions(final Activity activity, final int requestCode) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
if (!checkReadStoragePermissions(activity, requestCode)) return false;
|
||||
if (!checkReadStoragePermissions(activity, requestCode)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return checkWriteStoragePermissions(activity, requestCode);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
|
||||
public static boolean checkReadStoragePermissions(Activity activity, int requestCode) {
|
||||
public static boolean checkReadStoragePermissions(final Activity activity,
|
||||
final int requestCode) {
|
||||
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(activity,
|
||||
|
|
@ -44,7 +50,8 @@ public class PermissionHelper {
|
|||
}
|
||||
|
||||
|
||||
public static boolean checkWriteStoragePermissions(Activity activity, int requestCode) {
|
||||
public static boolean checkWriteStoragePermissions(final Activity activity,
|
||||
final int requestCode) {
|
||||
// Here, thisActivity is the current activity
|
||||
if (ContextCompat.checkSelfPermission(activity,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
|
|
@ -75,34 +82,45 @@ public class PermissionHelper {
|
|||
|
||||
|
||||
/**
|
||||
* In order to be able to draw over other apps, the permission android.permission.SYSTEM_ALERT_WINDOW have to be granted.
|
||||
* In order to be able to draw over other apps,
|
||||
* the permission android.permission.SYSTEM_ALERT_WINDOW have to be granted.
|
||||
* <p>
|
||||
* On < API 23 (MarshMallow) the permission was granted when the user installed the application (via AndroidManifest),
|
||||
* On < API 23 (MarshMallow) the permission was granted
|
||||
* when the user installed the application (via AndroidManifest),
|
||||
* on > 23, however, it have to start a activity asking the user if he agrees.
|
||||
* </p>
|
||||
* <p>
|
||||
* This method just return if the app has permission to draw over other apps, and if it doesn't, it will try to get the permission.
|
||||
* This method just return if the app has permission to draw over other apps,
|
||||
* and if it doesn't, it will try to get the permission.
|
||||
* </p>
|
||||
*
|
||||
* @return returns {@link Settings#canDrawOverlays(Context)}
|
||||
* @param context {@link Context}
|
||||
* @return {@link Settings#canDrawOverlays(Context)}
|
||||
**/
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public static boolean checkSystemAlertWindowPermission(Context context) {
|
||||
public static boolean checkSystemAlertWindowPermission(final Context context) {
|
||||
if (!Settings.canDrawOverlays(context)) {
|
||||
Intent i = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getPackageName()));
|
||||
Intent i = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||
Uri.parse("package:" + context.getPackageName()));
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(i);
|
||||
return false;
|
||||
} else return true;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isPopupEnabled(Context context) {
|
||||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
|
||||
PermissionHelper.checkSystemAlertWindowPermission(context);
|
||||
public static boolean isPopupEnabled(final Context context) {
|
||||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M
|
||||
|| PermissionHelper.checkSystemAlertWindowPermission(context);
|
||||
}
|
||||
|
||||
public static void showPopupEnablementToast(Context context) {
|
||||
public static void showPopupEnablementToast(final Context context) {
|
||||
Toast toast = Toast.makeText(context, R.string.msg_popup_permission, Toast.LENGTH_LONG);
|
||||
TextView messageView = toast.getView().findViewById(android.R.id.message);
|
||||
if (messageView != null) messageView.setGravity(Gravity.CENTER);
|
||||
if (messageView != null) {
|
||||
messageView.setGravity(Gravity.CENTER);
|
||||
}
|
||||
toast.show();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,15 +14,18 @@ public class RelatedStreamInfo extends ListInfo<InfoItem> {
|
|||
|
||||
private StreamInfoItem nextStream;
|
||||
|
||||
public RelatedStreamInfo(int serviceId, ListLinkHandler listUrlIdHandler, String name) {
|
||||
public RelatedStreamInfo(final int serviceId, final ListLinkHandler listUrlIdHandler,
|
||||
final String name) {
|
||||
super(serviceId, listUrlIdHandler, name);
|
||||
}
|
||||
|
||||
public static RelatedStreamInfo getInfo(StreamInfo info) {
|
||||
ListLinkHandler handler = new ListLinkHandler(info.getOriginalUrl(), info.getUrl(), info.getId(), Collections.emptyList(), null);
|
||||
RelatedStreamInfo relatedStreamInfo = new RelatedStreamInfo(info.getServiceId(), handler, info.getName());
|
||||
public static RelatedStreamInfo getInfo(final StreamInfo info) {
|
||||
ListLinkHandler handler = new ListLinkHandler(
|
||||
info.getOriginalUrl(), info.getUrl(), info.getId(), Collections.emptyList(), null);
|
||||
RelatedStreamInfo relatedStreamInfo = new RelatedStreamInfo(
|
||||
info.getServiceId(), handler, info.getName());
|
||||
List<InfoItem> streams = new ArrayList<>();
|
||||
if(info.getNextVideo() != null){
|
||||
if (info.getNextVideo() != null) {
|
||||
streams.add(info.getNextVideo());
|
||||
}
|
||||
streams.addAll(info.getRelatedStreams());
|
||||
|
|
@ -35,7 +38,7 @@ public class RelatedStreamInfo extends ListInfo<InfoItem> {
|
|||
return nextStream;
|
||||
}
|
||||
|
||||
public void setNextStream(StreamInfoItem nextStream) {
|
||||
public void setNextStream(final StreamInfoItem nextStream) {
|
||||
this.nextStream = nextStream;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,28 +14,23 @@ public class SecondaryStreamHelper<T extends Stream> {
|
|||
private final int position;
|
||||
private final StreamSizeWrapper<T> streams;
|
||||
|
||||
public SecondaryStreamHelper(StreamSizeWrapper<T> streams, T selectedStream) {
|
||||
public SecondaryStreamHelper(final StreamSizeWrapper<T> streams, final T selectedStream) {
|
||||
this.streams = streams;
|
||||
this.position = streams.getStreamsList().indexOf(selectedStream);
|
||||
if (this.position < 0) throw new RuntimeException("selected stream not found");
|
||||
}
|
||||
|
||||
public T getStream() {
|
||||
return streams.getStreamsList().get(position);
|
||||
}
|
||||
|
||||
public long getSizeInBytes() {
|
||||
return streams.getSizeInBytes(position);
|
||||
if (this.position < 0) {
|
||||
throw new RuntimeException("selected stream not found");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* find the correct audio stream for the desired video stream
|
||||
* Find the correct audio stream for the desired video stream.
|
||||
*
|
||||
* @param audioStreams list of audio streams
|
||||
* @param videoStream desired video ONLY stream
|
||||
* @return selected audio stream or null if a candidate was not found
|
||||
*/
|
||||
public static AudioStream getAudioStreamFor(@NonNull List<AudioStream> audioStreams, @NonNull VideoStream videoStream) {
|
||||
public static AudioStream getAudioStreamFor(@NonNull final List<AudioStream> audioStreams,
|
||||
@NonNull final VideoStream videoStream) {
|
||||
switch (videoStream.getFormat()) {
|
||||
case WEBM:
|
||||
case MPEG_4:// ¿is mpeg-4 DASH?
|
||||
|
|
@ -52,7 +47,9 @@ public class SecondaryStreamHelper<T extends Stream> {
|
|||
}
|
||||
}
|
||||
|
||||
if (m4v) return null;
|
||||
if (m4v) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// retry, but this time in reverse order
|
||||
for (int i = audioStreams.size() - 1; i >= 0; i--) {
|
||||
|
|
@ -64,4 +61,12 @@ public class SecondaryStreamHelper<T extends Stream> {
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
public T getStream() {
|
||||
return streams.getStreamsList().get(position);
|
||||
}
|
||||
|
||||
public long getSizeInBytes() {
|
||||
return streams.getSizeInBytes(position);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.collection.LruCache;
|
||||
import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
|
||||
|
|
@ -14,53 +15,58 @@ import java.io.ObjectOutputStream;
|
|||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SerializedCache {
|
||||
public final class SerializedCache {
|
||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||
private final String TAG = getClass().getSimpleName();
|
||||
|
||||
private static final SerializedCache instance = new SerializedCache();
|
||||
private static final SerializedCache INSTANCE = new SerializedCache();
|
||||
private static final int MAX_ITEMS_ON_CACHE = 5;
|
||||
|
||||
private static final LruCache<String, CacheData> lruCache =
|
||||
private static final LruCache<String, CacheData> LRU_CACHE =
|
||||
new LruCache<>(MAX_ITEMS_ON_CACHE);
|
||||
private static final String TAG = "SerializedCache";
|
||||
|
||||
private SerializedCache() {
|
||||
//no instance
|
||||
}
|
||||
|
||||
public static SerializedCache getInstance() {
|
||||
return instance;
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T> T take(@NonNull final String key, @NonNull final Class<T> type) {
|
||||
if (DEBUG) Log.d(TAG, "take() called with: key = [" + key + "]");
|
||||
synchronized (lruCache) {
|
||||
return lruCache.get(key) != null ? getItem(lruCache.remove(key), type) : null;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "take() called with: key = [" + key + "]");
|
||||
}
|
||||
synchronized (LRU_CACHE) {
|
||||
return LRU_CACHE.get(key) != null ? getItem(LRU_CACHE.remove(key), type) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T> T get(@NonNull final String key, @NonNull final Class<T> type) {
|
||||
if (DEBUG) Log.d(TAG, "get() called with: key = [" + key + "]");
|
||||
synchronized (lruCache) {
|
||||
final CacheData data = lruCache.get(key);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "get() called with: key = [" + key + "]");
|
||||
}
|
||||
synchronized (LRU_CACHE) {
|
||||
final CacheData data = LRU_CACHE.get(key);
|
||||
return data != null ? getItem(data, type) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T extends Serializable> String put(@NonNull T item, @NonNull final Class<T> type) {
|
||||
public <T extends Serializable> String put(@NonNull final T item,
|
||||
@NonNull final Class<T> type) {
|
||||
final String key = UUID.randomUUID().toString();
|
||||
return put(key, item, type) ? key : null;
|
||||
}
|
||||
|
||||
public <T extends Serializable> boolean put(@NonNull final String key, @NonNull T item,
|
||||
public <T extends Serializable> boolean put(@NonNull final String key, @NonNull final T item,
|
||||
@NonNull final Class<T> type) {
|
||||
if (DEBUG) Log.d(TAG, "put() called with: key = [" + key + "], item = [" + item + "]");
|
||||
synchronized (lruCache) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "put() called with: key = [" + key + "], item = [" + item + "]");
|
||||
}
|
||||
synchronized (LRU_CACHE) {
|
||||
try {
|
||||
lruCache.put(key, new CacheData<>(clone(item, type), type));
|
||||
LRU_CACHE.put(key, new CacheData<>(clone(item, type), type));
|
||||
return true;
|
||||
} catch (final Exception error) {
|
||||
Log.e(TAG, "Serialization failed for: ", error);
|
||||
|
|
@ -70,15 +76,17 @@ public class SerializedCache {
|
|||
}
|
||||
|
||||
public void clear() {
|
||||
if (DEBUG) Log.d(TAG, "clear() called");
|
||||
synchronized (lruCache) {
|
||||
lruCache.evictAll();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "clear() called");
|
||||
}
|
||||
synchronized (LRU_CACHE) {
|
||||
LRU_CACHE.evictAll();
|
||||
}
|
||||
}
|
||||
|
||||
public long size() {
|
||||
synchronized (lruCache) {
|
||||
return lruCache.size();
|
||||
synchronized (LRU_CACHE) {
|
||||
return LRU_CACHE.size();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -88,10 +96,10 @@ public class SerializedCache {
|
|||
}
|
||||
|
||||
@NonNull
|
||||
private <T extends Serializable> T clone(@NonNull T item,
|
||||
private <T extends Serializable> T clone(@NonNull final T item,
|
||||
@NonNull final Class<T> type) throws Exception {
|
||||
final ByteArrayOutputStream bytesOutput = new ByteArrayOutputStream();
|
||||
try (final ObjectOutputStream objectOutput = new ObjectOutputStream(bytesOutput)) {
|
||||
try (ObjectOutputStream objectOutput = new ObjectOutputStream(bytesOutput)) {
|
||||
objectOutput.writeObject(item);
|
||||
objectOutput.flush();
|
||||
}
|
||||
|
|
@ -100,11 +108,11 @@ public class SerializedCache {
|
|||
return type.cast(clone);
|
||||
}
|
||||
|
||||
final private static class CacheData<T> {
|
||||
private static final class CacheData<T> {
|
||||
private final T item;
|
||||
private final Class<T> type;
|
||||
|
||||
private CacheData(@NonNull final T item, @NonNull Class<T> type) {
|
||||
private CacheData(@NonNull final T item, @NonNull final Class<T> type) {
|
||||
this.item = item;
|
||||
this.type = type;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,11 +22,13 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
|
||||
|
||||
public class ServiceHelper {
|
||||
public final class ServiceHelper {
|
||||
private static final StreamingService DEFAULT_FALLBACK_SERVICE = ServiceList.YouTube;
|
||||
|
||||
private ServiceHelper() { }
|
||||
|
||||
@DrawableRes
|
||||
public static int getIcon(int serviceId) {
|
||||
public static int getIcon(final int serviceId) {
|
||||
switch (serviceId) {
|
||||
case 0:
|
||||
return R.drawable.place_holder_youtube;
|
||||
|
|
@ -41,27 +43,37 @@ public class ServiceHelper {
|
|||
}
|
||||
}
|
||||
|
||||
public static String getTranslatedFilterString(String filter, Context c) {
|
||||
public static String getTranslatedFilterString(final String filter, final Context c) {
|
||||
switch (filter) {
|
||||
case "all": return c.getString(R.string.all);
|
||||
case "videos": return c.getString(R.string.videos_string);
|
||||
case "channels": return c.getString(R.string.channels);
|
||||
case "playlists": return c.getString(R.string.playlists);
|
||||
case "tracks": return c.getString(R.string.tracks);
|
||||
case "users": return c.getString(R.string.users);
|
||||
case "conferences" : return c.getString(R.string.conferences);
|
||||
case "events" : return c.getString(R.string.events);
|
||||
default: return filter;
|
||||
case "all":
|
||||
return c.getString(R.string.all);
|
||||
case "videos":
|
||||
return c.getString(R.string.videos_string);
|
||||
case "channels":
|
||||
return c.getString(R.string.channels);
|
||||
case "playlists":
|
||||
return c.getString(R.string.playlists);
|
||||
case "tracks":
|
||||
return c.getString(R.string.tracks);
|
||||
case "users":
|
||||
return c.getString(R.string.users);
|
||||
case "conferences":
|
||||
return c.getString(R.string.conferences);
|
||||
case "events":
|
||||
return c.getString(R.string.events);
|
||||
default:
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a resource string with instructions for importing subscriptions for each service.
|
||||
*
|
||||
* @param serviceId service to get the instructions for
|
||||
* @return the string resource containing the instructions or -1 if the service don't support it
|
||||
*/
|
||||
@StringRes
|
||||
public static int getImportInstructions(int serviceId) {
|
||||
public static int getImportInstructions(final int serviceId) {
|
||||
switch (serviceId) {
|
||||
case 0:
|
||||
return R.string.import_youtube_instructions;
|
||||
|
|
@ -76,10 +88,11 @@ public class ServiceHelper {
|
|||
* For services that support importing from a channel url, return a hint that will
|
||||
* be used in the EditText that the user will type in his channel url.
|
||||
*
|
||||
* @param serviceId service to get the hint for
|
||||
* @return the hint's string resource or -1 if the service don't support it
|
||||
*/
|
||||
@StringRes
|
||||
public static int getImportInstructionsHint(int serviceId) {
|
||||
public static int getImportInstructionsHint(final int serviceId) {
|
||||
switch (serviceId) {
|
||||
case 1:
|
||||
return R.string.import_soundcloud_instructions_hint;
|
||||
|
|
@ -88,10 +101,10 @@ public class ServiceHelper {
|
|||
}
|
||||
}
|
||||
|
||||
public static int getSelectedServiceId(Context context) {
|
||||
|
||||
public static int getSelectedServiceId(final Context context) {
|
||||
final String serviceName = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getString(context.getString(R.string.current_service_key), context.getString(R.string.default_service_value));
|
||||
.getString(context.getString(R.string.current_service_key),
|
||||
context.getString(R.string.default_service_value));
|
||||
|
||||
int serviceId;
|
||||
try {
|
||||
|
|
@ -103,7 +116,7 @@ public class ServiceHelper {
|
|||
return serviceId;
|
||||
}
|
||||
|
||||
public static void setSelectedServiceId(Context context, int serviceId) {
|
||||
public static void setSelectedServiceId(final Context context, final int serviceId) {
|
||||
String serviceName;
|
||||
try {
|
||||
serviceName = NewPipe.getService(serviceId).getServiceInfo().getName();
|
||||
|
|
@ -114,14 +127,18 @@ public class ServiceHelper {
|
|||
setSelectedServicePreferences(context, serviceName);
|
||||
}
|
||||
|
||||
public static void setSelectedServiceId(Context context, String serviceName) {
|
||||
public static void setSelectedServiceId(final Context context, final String serviceName) {
|
||||
int serviceId = NewPipe.getIdOfService(serviceName);
|
||||
if (serviceId == -1) serviceName = DEFAULT_FALLBACK_SERVICE.getServiceInfo().getName();
|
||||
|
||||
setSelectedServicePreferences(context, serviceName);
|
||||
if (serviceId == -1) {
|
||||
setSelectedServicePreferences(context,
|
||||
DEFAULT_FALLBACK_SERVICE.getServiceInfo().getName());
|
||||
} else {
|
||||
setSelectedServicePreferences(context, serviceName);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setSelectedServicePreferences(Context context, String serviceName) {
|
||||
private static void setSelectedServicePreferences(final Context context,
|
||||
final String serviceName) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().
|
||||
putString(context.getString(R.string.current_service_key), serviceName).apply();
|
||||
}
|
||||
|
|
@ -136,15 +153,19 @@ public class ServiceHelper {
|
|||
|
||||
public static boolean isBeta(final StreamingService s) {
|
||||
switch (s.getServiceInfo().getName()) {
|
||||
case "YouTube": return false;
|
||||
default: return true;
|
||||
case "YouTube":
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void initService(Context context, int serviceId) {
|
||||
public static void initService(final Context context, final int serviceId) {
|
||||
if (serviceId == ServiceList.PeerTube.getServiceId()) {
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
String json = sharedPreferences.getString(context.getString(R.string.peertube_selected_instance_key), null);
|
||||
SharedPreferences sharedPreferences = PreferenceManager
|
||||
.getDefaultSharedPreferences(context);
|
||||
String json = sharedPreferences.getString(context.getString(
|
||||
R.string.peertube_selected_instance_key), null);
|
||||
if (null == json) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -162,7 +183,7 @@ public class ServiceHelper {
|
|||
}
|
||||
}
|
||||
|
||||
public static void initServices(Context context) {
|
||||
public static void initServices(final Context context) {
|
||||
for (StreamingService s : ServiceList.all()) {
|
||||
initService(context, s.getServiceId());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,17 +6,21 @@ import android.net.Uri;
|
|||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class ShareUtils {
|
||||
public static void openUrlInBrowser(Context context, String url) {
|
||||
public final class ShareUtils {
|
||||
private ShareUtils() { }
|
||||
|
||||
public static void openUrlInBrowser(final Context context, final String url) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
context.startActivity(Intent.createChooser(intent, context.getString(R.string.share_dialog_title)));
|
||||
context.startActivity(Intent.createChooser(
|
||||
intent, context.getString(R.string.share_dialog_title)));
|
||||
}
|
||||
|
||||
public static void shareUrl(Context context, String subject, String url) {
|
||||
public static void shareUrl(final Context context, final String subject, final String url) {
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, url);
|
||||
context.startActivity(Intent.createChooser(intent, context.getString(R.string.share_dialog_title)));
|
||||
context.startActivity(Intent.createChooser(
|
||||
intent, context.getString(R.string.share_dialog_title)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,21 @@ package org.schabi.newpipe.util;
|
|||
public interface SliderStrategy {
|
||||
/**
|
||||
* Converts from zeroed double with a minimum offset to the nearest rounded slider
|
||||
* equivalent integer
|
||||
* */
|
||||
int progressOf(final double value);
|
||||
* equivalent integer.
|
||||
*
|
||||
* @param value the value to convert
|
||||
* @return the converted value
|
||||
*/
|
||||
int progressOf(double value);
|
||||
|
||||
/**
|
||||
* Converts from slider integer value to an equivalent double value with a given
|
||||
* minimum offset
|
||||
* */
|
||||
double valueOf(final int progress);
|
||||
* minimum offset.
|
||||
*
|
||||
* @param progress the value to convert
|
||||
* @return the converted value
|
||||
*/
|
||||
double valueOf(int progress);
|
||||
|
||||
// TODO: also implement linear strategy when needed
|
||||
|
||||
|
|
@ -27,18 +33,19 @@ public interface SliderStrategy {
|
|||
* progress is from the center of the slider. The further away from the center,
|
||||
* the faster the interpreted value changes, and vice versa.
|
||||
*
|
||||
* @param minimum the minimum value of the interpreted value of the slider.
|
||||
* @param maximum the maximum value of the interpreted value of the slider.
|
||||
* @param center center of the interpreted value between the minimum and maximum, which
|
||||
* will be used as the center value on the slider progress. Doesn't need
|
||||
* to be the average of the minimum and maximum values, but must be in
|
||||
* between the two.
|
||||
* @param minimum the minimum value of the interpreted value of the slider.
|
||||
* @param maximum the maximum value of the interpreted value of the slider.
|
||||
* @param center center of the interpreted value between the minimum and maximum, which
|
||||
* will be used as the center value on the slider progress. Doesn't need
|
||||
* to be the average of the minimum and maximum values, but must be in
|
||||
* between the two.
|
||||
* @param maxProgress the maximum possible progress of the slider, this is the
|
||||
* value that is shown for the UI and controls the granularity of
|
||||
* the slider. Should be as large as possible to avoid floating
|
||||
* point round-off error. Using odd number is recommended.
|
||||
* */
|
||||
public Quadratic(double minimum, double maximum, double center, int maxProgress) {
|
||||
*/
|
||||
public Quadratic(final double minimum, final double maximum, final double center,
|
||||
final int maxProgress) {
|
||||
if (center < minimum || center > maximum) {
|
||||
throw new IllegalArgumentException("Center must be in between minimum and maximum");
|
||||
}
|
||||
|
|
@ -51,18 +58,17 @@ public interface SliderStrategy {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int progressOf(double value) {
|
||||
public int progressOf(final double value) {
|
||||
final double difference = value - center;
|
||||
final double root = difference >= 0 ?
|
||||
Math.sqrt(difference / rightGap) :
|
||||
-Math.sqrt(Math.abs(difference / leftGap));
|
||||
final double root = difference >= 0 ? Math.sqrt(difference / rightGap)
|
||||
: -Math.sqrt(Math.abs(difference / leftGap));
|
||||
final double offset = Math.round(root * centerProgress);
|
||||
|
||||
return (int) (centerProgress + offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double valueOf(int progress) {
|
||||
public double valueOf(final int progress) {
|
||||
final int offset = progress - centerProgress;
|
||||
final double square = Math.pow(((double) offset) / ((double) centerProgress), 2);
|
||||
final double difference = square * (offset >= 0 ? rightGap : leftGap);
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.util.SparseArray;
|
||||
|
||||
public abstract class SparseArrayUtils {
|
||||
|
||||
public static <T> void shiftItemsDown(SparseArray<T> sparseArray, int lower, int upper) {
|
||||
for (int i = lower + 1; i <= upper; i++) {
|
||||
final T o = sparseArray.get(i);
|
||||
sparseArray.put(i - 1, o);
|
||||
sparseArray.remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void shiftItemsUp(SparseArray<T> sparseArray, int lower, int upper) {
|
||||
for (int i = upper - 1; i >= lower; i--) {
|
||||
final T o = sparseArray.get(i);
|
||||
sparseArray.put(i + 1, o);
|
||||
sparseArray.remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> int[] getKeys(SparseArray<T> sparseArray) {
|
||||
final int[] result = new int[sparseArray.size()];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = sparseArray.keyAt(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -24,11 +24,12 @@ import android.content.Context;
|
|||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
|
||||
|
|
@ -44,14 +45,15 @@ import java.util.Queue;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* A way to save state to disk or in a in-memory map if it's just changing configurations (i.e. rotating the phone).
|
||||
* A way to save state to disk or in a in-memory map
|
||||
* if it's just changing configurations (i.e. rotating the phone).
|
||||
*/
|
||||
public class StateSaver {
|
||||
private static final ConcurrentHashMap<String, Queue<Object>> stateObjectsHolder = new ConcurrentHashMap<>();
|
||||
public final class StateSaver {
|
||||
public static final String KEY_SAVED_STATE = "key_saved_state";
|
||||
private static final ConcurrentHashMap<String, Queue<Object>> STATE_OBJECTS_HOLDER
|
||||
= new ConcurrentHashMap<>();
|
||||
private static final String TAG = "StateSaver";
|
||||
private static final String CACHE_DIR_NAME = "state_cache";
|
||||
|
||||
public static final String KEY_SAVED_STATE = "key_saved_state";
|
||||
private static String cacheDirPath;
|
||||
|
||||
private StateSaver() {
|
||||
|
|
@ -59,78 +61,70 @@ public class StateSaver {
|
|||
}
|
||||
|
||||
/**
|
||||
* Initialize the StateSaver, usually you want to call this in the Application class
|
||||
* Initialize the StateSaver, usually you want to call this in the Application class.
|
||||
*
|
||||
* @param context used to get the available cache dir
|
||||
*/
|
||||
public static void init(Context context) {
|
||||
public static void init(final Context context) {
|
||||
File externalCacheDir = context.getExternalCacheDir();
|
||||
if (externalCacheDir != null) cacheDirPath = externalCacheDir.getAbsolutePath();
|
||||
if (TextUtils.isEmpty(cacheDirPath)) cacheDirPath = context.getCacheDir().getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for describe how to save/read the objects.
|
||||
* <p>
|
||||
* Queue was chosen by its FIFO property.
|
||||
*/
|
||||
public interface WriteRead {
|
||||
/**
|
||||
* Generate a changing suffix that will name the cache file,
|
||||
* and be used to identify if it changed (thus reducing useless reading/saving).
|
||||
*
|
||||
* @return a unique value
|
||||
*/
|
||||
String generateSuffix();
|
||||
|
||||
/**
|
||||
* Add to this queue objects that you want to save.
|
||||
*/
|
||||
void writeTo(Queue<Object> objectsToSave);
|
||||
|
||||
/**
|
||||
* Poll saved objects from the queue in the order they were written.
|
||||
*
|
||||
* @param savedObjects queue of objects returned by {@link #writeTo(Queue)}
|
||||
*/
|
||||
void readFrom(@NonNull Queue<Object> savedObjects) throws Exception;
|
||||
if (externalCacheDir != null) {
|
||||
cacheDirPath = externalCacheDir.getAbsolutePath();
|
||||
}
|
||||
if (TextUtils.isEmpty(cacheDirPath)) {
|
||||
cacheDirPath = context.getCacheDir().getAbsolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #tryToRestore(SavedState, WriteRead)
|
||||
* @param outState
|
||||
* @param writeRead
|
||||
* @return the saved state
|
||||
*/
|
||||
public static SavedState tryToRestore(Bundle outState, WriteRead writeRead) {
|
||||
if (outState == null || writeRead == null) return null;
|
||||
public static SavedState tryToRestore(final Bundle outState, final WriteRead writeRead) {
|
||||
if (outState == null || writeRead == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SavedState savedState = outState.getParcelable(KEY_SAVED_STATE);
|
||||
if (savedState == null) return null;
|
||||
if (savedState == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return tryToRestore(savedState, writeRead);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to restore the state from memory and disk, using the {@link StateSaver.WriteRead#readFrom(Queue)} from the writeRead.
|
||||
* Try to restore the state from memory and disk,
|
||||
* using the {@link StateSaver.WriteRead#readFrom(Queue)} from the writeRead.
|
||||
* @param savedState
|
||||
* @param writeRead
|
||||
* @return the saved state
|
||||
*/
|
||||
@Nullable
|
||||
private static SavedState tryToRestore(@NonNull SavedState savedState, @NonNull WriteRead writeRead) {
|
||||
private static SavedState tryToRestore(@NonNull final SavedState savedState,
|
||||
@NonNull final WriteRead writeRead) {
|
||||
if (MainActivity.DEBUG) {
|
||||
Log.d(TAG, "tryToRestore() called with: savedState = [" + savedState + "], writeRead = [" + writeRead + "]");
|
||||
Log.d(TAG, "tryToRestore() called with: savedState = [" + savedState + "], "
|
||||
+ "writeRead = [" + writeRead + "]");
|
||||
}
|
||||
|
||||
FileInputStream fileInputStream = null;
|
||||
try {
|
||||
Queue<Object> savedObjects = stateObjectsHolder.remove(savedState.getPrefixFileSaved());
|
||||
Queue<Object> savedObjects
|
||||
= STATE_OBJECTS_HOLDER.remove(savedState.getPrefixFileSaved());
|
||||
if (savedObjects != null) {
|
||||
writeRead.readFrom(savedObjects);
|
||||
if (MainActivity.DEBUG) {
|
||||
Log.d(TAG, "tryToSave: reading objects from holder > " + savedObjects + ", stateObjectsHolder > " + stateObjectsHolder);
|
||||
Log.d(TAG, "tryToSave: reading objects from holder > " + savedObjects
|
||||
+ ", stateObjectsHolder > " + STATE_OBJECTS_HOLDER);
|
||||
}
|
||||
return savedState;
|
||||
}
|
||||
|
||||
File file = new File(savedState.getPathFileSaved());
|
||||
if (!file.exists()) {
|
||||
if(MainActivity.DEBUG) {
|
||||
if (MainActivity.DEBUG) {
|
||||
Log.d(TAG, "Cache file doesn't exist: " + file.getAbsolutePath());
|
||||
}
|
||||
return null;
|
||||
|
|
@ -160,9 +154,16 @@ public class StateSaver {
|
|||
|
||||
/**
|
||||
* @see #tryToSave(boolean, String, String, WriteRead)
|
||||
* @param isChangingConfig
|
||||
* @param savedState
|
||||
* @param outState
|
||||
* @param writeRead
|
||||
* @return the saved state or {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
public static SavedState tryToSave(boolean isChangingConfig, @Nullable SavedState savedState, Bundle outState, WriteRead writeRead) {
|
||||
public static SavedState tryToSave(final boolean isChangingConfig,
|
||||
@Nullable final SavedState savedState, final Bundle outState,
|
||||
final WriteRead writeRead) {
|
||||
@NonNull
|
||||
String currentSavedPrefix;
|
||||
if (savedState == null || TextUtils.isEmpty(savedState.getPrefixFileSaved())) {
|
||||
|
|
@ -173,34 +174,45 @@ public class StateSaver {
|
|||
currentSavedPrefix = savedState.getPrefixFileSaved();
|
||||
}
|
||||
|
||||
savedState = tryToSave(isChangingConfig, currentSavedPrefix, writeRead.generateSuffix(), writeRead);
|
||||
if (savedState != null) {
|
||||
outState.putParcelable(StateSaver.KEY_SAVED_STATE, savedState);
|
||||
return savedState;
|
||||
final SavedState newSavedState = tryToSave(isChangingConfig, currentSavedPrefix,
|
||||
writeRead.generateSuffix(), writeRead);
|
||||
if (newSavedState != null) {
|
||||
outState.putParcelable(StateSaver.KEY_SAVED_STATE, newSavedState);
|
||||
return newSavedState;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If it's not changing configuration (i.e. rotating screen), try to write the state from {@link StateSaver.WriteRead#writeTo(Queue)}
|
||||
* to the file with the name of prefixFileName + suffixFileName, in a cache folder got from the {@link #init(Context)}.
|
||||
* If it's not changing configuration (i.e. rotating screen),
|
||||
* try to write the state from {@link StateSaver.WriteRead#writeTo(Queue)}
|
||||
* to the file with the name of prefixFileName + suffixFileName,
|
||||
* in a cache folder got from the {@link #init(Context)}.
|
||||
* <p>
|
||||
* It checks if the file already exists and if it does, just return the path, so a good way to save is:
|
||||
* It checks if the file already exists and if it does, just return the path,
|
||||
* so a good way to save is:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li> A fixed prefix for the file</li>
|
||||
* <li> A changing suffix</li>
|
||||
* <li>A fixed prefix for the file</li>
|
||||
* <li>A changing suffix</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param isChangingConfig
|
||||
* @param prefixFileName
|
||||
* @param suffixFileName
|
||||
* @param writeRead
|
||||
* @return the saved state or {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
private static SavedState tryToSave(boolean isChangingConfig, final String prefixFileName, String suffixFileName, WriteRead writeRead) {
|
||||
private static SavedState tryToSave(final boolean isChangingConfig, final String prefixFileName,
|
||||
final String suffixFileName, final WriteRead writeRead) {
|
||||
if (MainActivity.DEBUG) {
|
||||
Log.d(TAG, "tryToSave() called with: isChangingConfig = [" + isChangingConfig + "], prefixFileName = [" + prefixFileName + "], suffixFileName = [" + suffixFileName + "], writeRead = [" + writeRead + "]");
|
||||
Log.d(TAG, "tryToSave() called with: "
|
||||
+ "isChangingConfig = [" + isChangingConfig + "], "
|
||||
+ "prefixFileName = [" + prefixFileName + "], "
|
||||
+ "suffixFileName = [" + suffixFileName + "], "
|
||||
+ "writeRead = [" + writeRead + "]");
|
||||
}
|
||||
|
||||
LinkedList<Object> savedObjects = new LinkedList<>();
|
||||
|
|
@ -208,10 +220,12 @@ public class StateSaver {
|
|||
|
||||
if (isChangingConfig) {
|
||||
if (savedObjects.size() > 0) {
|
||||
stateObjectsHolder.put(prefixFileName, savedObjects);
|
||||
STATE_OBJECTS_HOLDER.put(prefixFileName, savedObjects);
|
||||
return new SavedState(prefixFileName, "");
|
||||
} else {
|
||||
if(MainActivity.DEBUG) Log.d(TAG, "Nothing to save");
|
||||
if (MainActivity.DEBUG) {
|
||||
Log.d(TAG, "Nothing to save");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -219,19 +233,22 @@ public class StateSaver {
|
|||
FileOutputStream fileOutputStream = null;
|
||||
try {
|
||||
File cacheDir = new File(cacheDirPath);
|
||||
if (!cacheDir.exists()) throw new RuntimeException("Cache dir does not exist > " + cacheDirPath);
|
||||
if (!cacheDir.exists()) {
|
||||
throw new RuntimeException("Cache dir does not exist > " + cacheDirPath);
|
||||
}
|
||||
cacheDir = new File(cacheDir, CACHE_DIR_NAME);
|
||||
if (!cacheDir.exists()) {
|
||||
if(!cacheDir.mkdir()) {
|
||||
if(BuildConfig.DEBUG) {
|
||||
Log.e(TAG, "Failed to create cache directory " + cacheDir.getAbsolutePath());
|
||||
if (!cacheDir.mkdir()) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.e(TAG,
|
||||
"Failed to create cache directory " + cacheDir.getAbsolutePath());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(suffixFileName)) suffixFileName = ".cache";
|
||||
File file = new File(cacheDir, prefixFileName + suffixFileName);
|
||||
File file = new File(cacheDir, prefixFileName
|
||||
+ (TextUtils.isEmpty(suffixFileName) ? ".cache" : suffixFileName));
|
||||
if (file.exists() && file.length() > 0) {
|
||||
// If the file already exists, just return it
|
||||
return new SavedState(prefixFileName, file.getAbsolutePath());
|
||||
|
|
@ -239,7 +256,7 @@ public class StateSaver {
|
|||
// Delete any file that contains the prefix
|
||||
File[] files = cacheDir.listFiles(new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
public boolean accept(final File dir, final String name) {
|
||||
return name.contains(prefixFileName);
|
||||
}
|
||||
});
|
||||
|
|
@ -259,21 +276,25 @@ public class StateSaver {
|
|||
if (fileOutputStream != null) {
|
||||
try {
|
||||
fileOutputStream.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
} catch (IOException ignored) { }
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the cache file contained in the savedState and remove any possible-existing value in the memory-cache.
|
||||
* Delete the cache file contained in the savedState.
|
||||
* Also remove any possible-existing value in the memory-cache.
|
||||
*
|
||||
* @param savedState the saved state to delete
|
||||
*/
|
||||
public static void onDestroy(SavedState savedState) {
|
||||
if (MainActivity.DEBUG) Log.d(TAG, "onDestroy() called with: savedState = [" + savedState + "]");
|
||||
public static void onDestroy(final SavedState savedState) {
|
||||
if (MainActivity.DEBUG) {
|
||||
Log.d(TAG, "onDestroy() called with: savedState = [" + savedState + "]");
|
||||
}
|
||||
|
||||
if (savedState != null && !TextUtils.isEmpty(savedState.getPathFileSaved())) {
|
||||
stateObjectsHolder.remove(savedState.getPrefixFileSaved());
|
||||
STATE_OBJECTS_HOLDER.remove(savedState.getPrefixFileSaved());
|
||||
try {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
new File(savedState.getPathFileSaved()).delete();
|
||||
|
|
@ -286,35 +307,83 @@ public class StateSaver {
|
|||
* Clear all the files in cache (in memory and disk).
|
||||
*/
|
||||
public static void clearStateFiles() {
|
||||
if (MainActivity.DEBUG) Log.d(TAG, "clearStateFiles() called");
|
||||
if (MainActivity.DEBUG) {
|
||||
Log.d(TAG, "clearStateFiles() called");
|
||||
}
|
||||
|
||||
stateObjectsHolder.clear();
|
||||
STATE_OBJECTS_HOLDER.clear();
|
||||
File cacheDir = new File(cacheDirPath);
|
||||
if (!cacheDir.exists()) return;
|
||||
if (!cacheDir.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
cacheDir = new File(cacheDir, CACHE_DIR_NAME);
|
||||
if (cacheDir.exists()) {
|
||||
for (File file : cacheDir.listFiles()) file.delete();
|
||||
for (File file : cacheDir.listFiles()) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for describe how to save/read the objects.
|
||||
* <p>
|
||||
* Queue was chosen by its FIFO property.
|
||||
*/
|
||||
public interface WriteRead {
|
||||
/**
|
||||
* Generate a changing suffix that will name the cache file,
|
||||
* and be used to identify if it changed (thus reducing useless reading/saving).
|
||||
*
|
||||
* @return a unique value
|
||||
*/
|
||||
String generateSuffix();
|
||||
|
||||
/**
|
||||
* Add to this queue objects that you want to save.
|
||||
*
|
||||
* @param objectsToSave the objects to save
|
||||
*/
|
||||
void writeTo(Queue<Object> objectsToSave);
|
||||
|
||||
/**
|
||||
* Poll saved objects from the queue in the order they were written.
|
||||
*
|
||||
* @param savedObjects queue of objects returned by {@link #writeTo(Queue)}
|
||||
*/
|
||||
void readFrom(@NonNull Queue<Object> savedObjects) throws Exception;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Inner
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* Information about the saved state on the disk
|
||||
* Information about the saved state on the disk.
|
||||
*/
|
||||
public static class SavedState implements Parcelable {
|
||||
@SuppressWarnings("unused")
|
||||
public static final Parcelable.Creator<SavedState> CREATOR
|
||||
= new Parcelable.Creator<SavedState>() {
|
||||
@Override
|
||||
public SavedState createFromParcel(final Parcel in) {
|
||||
return new SavedState(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SavedState[] newArray(final int size) {
|
||||
return new SavedState[size];
|
||||
}
|
||||
};
|
||||
private final String prefixFileSaved;
|
||||
private final String pathFileSaved;
|
||||
|
||||
public SavedState(String prefixFileSaved, String pathFileSaved) {
|
||||
public SavedState(final String prefixFileSaved, final String pathFileSaved) {
|
||||
this.prefixFileSaved = prefixFileSaved;
|
||||
this.pathFileSaved = pathFileSaved;
|
||||
}
|
||||
|
||||
protected SavedState(Parcel in) {
|
||||
protected SavedState(final Parcel in) {
|
||||
prefixFileSaved = in.readString();
|
||||
pathFileSaved = in.readString();
|
||||
}
|
||||
|
|
@ -330,26 +399,14 @@ public class StateSaver {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
public void writeToParcel(final Parcel dest, final int flags) {
|
||||
dest.writeString(prefixFileSaved);
|
||||
dest.writeString(pathFileSaved);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
|
||||
@Override
|
||||
public SavedState createFromParcel(Parcel in) {
|
||||
return new SavedState(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SavedState[] newArray(int size) {
|
||||
return new SavedState[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the prefix of the saved file
|
||||
* Get the prefix of the saved file.
|
||||
*
|
||||
* @return the file prefix
|
||||
*/
|
||||
public String getPrefixFileSaved() {
|
||||
|
|
@ -357,7 +414,8 @@ public class StateSaver {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the path to the saved file
|
||||
* Get the path to the saved file.
|
||||
*
|
||||
* @return the path to the saved file
|
||||
*/
|
||||
public String getPathFileSaved() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
|
@ -16,26 +17,33 @@ public enum StreamDialogEntry {
|
|||
//////////////////////////////////////
|
||||
|
||||
enqueue_on_background(R.string.enqueue_on_background, (fragment, item) ->
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(fragment.getContext(), new SinglePlayQueue(item), false)),
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(fragment.getContext(),
|
||||
new SinglePlayQueue(item), false)),
|
||||
|
||||
enqueue_on_popup(R.string.enqueue_on_popup, (fragment, item) ->
|
||||
NavigationHelper.enqueueOnPopupPlayer(fragment.getContext(), new SinglePlayQueue(item), false)),
|
||||
NavigationHelper.enqueueOnPopupPlayer(fragment.getContext(),
|
||||
new SinglePlayQueue(item), false)),
|
||||
|
||||
start_here_on_background(R.string.start_here_on_background, (fragment, item) ->
|
||||
NavigationHelper.playOnBackgroundPlayer(fragment.getContext(), new SinglePlayQueue(item), true)),
|
||||
NavigationHelper.playOnBackgroundPlayer(fragment.getContext(),
|
||||
new SinglePlayQueue(item), true)),
|
||||
|
||||
start_here_on_popup(R.string.start_here_on_popup, (fragment, item) ->
|
||||
NavigationHelper.playOnPopupPlayer(fragment.getContext(), new SinglePlayQueue(item), true)),
|
||||
NavigationHelper.playOnPopupPlayer(fragment.getContext(),
|
||||
new SinglePlayQueue(item), true)),
|
||||
|
||||
set_as_playlist_thumbnail(R.string.set_as_playlist_thumbnail, (fragment, item) -> {}), // has to be set manually
|
||||
set_as_playlist_thumbnail(R.string.set_as_playlist_thumbnail, (fragment, item) -> {
|
||||
}), // has to be set manually
|
||||
|
||||
delete(R.string.delete, (fragment, item) -> {}), // has to be set manually
|
||||
delete(R.string.delete, (fragment, item) -> {
|
||||
}), // has to be set manually
|
||||
|
||||
append_playlist(R.string.append_playlist, (fragment, item) -> {
|
||||
if (fragment.getFragmentManager() != null) {
|
||||
PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item))
|
||||
.show(fragment.getFragmentManager(), "StreamDialogEntry@append_playlist");
|
||||
}}),
|
||||
}
|
||||
}),
|
||||
|
||||
share(R.string.share, (fragment, item) ->
|
||||
ShareUtils.shareUrl(fragment.getContext(), item.getName(), item.getUrl()));
|
||||
|
|
@ -45,43 +53,28 @@ public enum StreamDialogEntry {
|
|||
// variables //
|
||||
///////////////
|
||||
|
||||
public interface StreamDialogEntryAction {
|
||||
void onClick(Fragment fragment, final StreamInfoItem infoItem);
|
||||
}
|
||||
|
||||
private static StreamDialogEntry[] enabledEntries;
|
||||
private final int resource;
|
||||
private final StreamDialogEntryAction defaultAction;
|
||||
private StreamDialogEntryAction customAction;
|
||||
|
||||
private static StreamDialogEntry[] enabledEntries;
|
||||
StreamDialogEntry(final int resource, final StreamDialogEntryAction defaultAction) {
|
||||
this.resource = resource;
|
||||
this.defaultAction = defaultAction;
|
||||
this.customAction = null;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// non-static methods to initialize and edit entries //
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
StreamDialogEntry(final int resource, StreamDialogEntryAction defaultAction) {
|
||||
this.resource = resource;
|
||||
this.defaultAction = defaultAction;
|
||||
this.customAction = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used after {@link #setEnabledEntries(StreamDialogEntry...)} has been called
|
||||
* To be called before using {@link #setCustomAction(StreamDialogEntryAction)}.
|
||||
*
|
||||
* @param entries the entries to be enabled
|
||||
*/
|
||||
public void setCustomAction(StreamDialogEntryAction action) {
|
||||
this.customAction = action;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// static methods that act on enabled entries //
|
||||
////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* To be called before using {@link #setCustomAction(StreamDialogEntryAction)}
|
||||
*/
|
||||
public static void setEnabledEntries(StreamDialogEntry... entries) {
|
||||
public static void setEnabledEntries(final StreamDialogEntry... entries) {
|
||||
// cleanup from last time StreamDialogEntry was used
|
||||
for (StreamDialogEntry streamDialogEntry : values()) {
|
||||
streamDialogEntry.customAction = null;
|
||||
|
|
@ -90,7 +83,7 @@ public enum StreamDialogEntry {
|
|||
enabledEntries = entries;
|
||||
}
|
||||
|
||||
public static String[] getCommands(Context context) {
|
||||
public static String[] getCommands(final Context context) {
|
||||
String[] commands = new String[enabledEntries.length];
|
||||
for (int i = 0; i != enabledEntries.length; ++i) {
|
||||
commands[i] = context.getResources().getString(enabledEntries[i].resource);
|
||||
|
|
@ -99,11 +92,30 @@ public enum StreamDialogEntry {
|
|||
return commands;
|
||||
}
|
||||
|
||||
public static void clickOn(int which, Fragment fragment, StreamInfoItem infoItem) {
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// static methods that act on enabled entries //
|
||||
////////////////////////////////////////////////
|
||||
|
||||
public static void clickOn(final int which, final Fragment fragment,
|
||||
final StreamInfoItem infoItem) {
|
||||
if (enabledEntries[which].customAction == null) {
|
||||
enabledEntries[which].defaultAction.onClick(fragment, infoItem);
|
||||
} else {
|
||||
enabledEntries[which].customAction.onClick(fragment, infoItem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used after {@link #setEnabledEntries(StreamDialogEntry...)} has been called.
|
||||
*
|
||||
* @param action the action to be set
|
||||
*/
|
||||
public void setCustomAction(final StreamDialogEntryAction action) {
|
||||
this.customAction = action;
|
||||
}
|
||||
|
||||
public interface StreamDialogEntryAction {
|
||||
void onClick(Fragment fragment, StreamInfoItem infoItem);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
|||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
|
@ -28,8 +29,11 @@ import io.reactivex.schedulers.Schedulers;
|
|||
import us.shandian.giga.util.Utility;
|
||||
|
||||
/**
|
||||
* A list adapter for a list of {@link Stream streams},
|
||||
* currently supporting {@link VideoStream}, {@link AudioStream} and {@link SubtitlesStream}
|
||||
* A list adapter for a list of {@link Stream streams}.
|
||||
* It currently supports {@link VideoStream}, {@link AudioStream} and {@link SubtitlesStream}.
|
||||
*
|
||||
* @param <T> the primary stream type's class extending {@link Stream}
|
||||
* @param <U> the secondary stream type's class extending {@link Stream}
|
||||
*/
|
||||
public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseAdapter {
|
||||
private final Context context;
|
||||
|
|
@ -37,17 +41,19 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||
private final StreamSizeWrapper<T> streamsWrapper;
|
||||
private final SparseArray<SecondaryStreamHelper<U>> secondaryStreams;
|
||||
|
||||
public StreamItemAdapter(Context context, StreamSizeWrapper<T> streamsWrapper, SparseArray<SecondaryStreamHelper<U>> secondaryStreams) {
|
||||
public StreamItemAdapter(final Context context, final StreamSizeWrapper<T> streamsWrapper,
|
||||
final SparseArray<SecondaryStreamHelper<U>> secondaryStreams) {
|
||||
this.context = context;
|
||||
this.streamsWrapper = streamsWrapper;
|
||||
this.secondaryStreams = secondaryStreams;
|
||||
}
|
||||
|
||||
public StreamItemAdapter(Context context, StreamSizeWrapper<T> streamsWrapper, boolean showIconNoAudio) {
|
||||
public StreamItemAdapter(final Context context, final StreamSizeWrapper<T> streamsWrapper,
|
||||
final boolean showIconNoAudio) {
|
||||
this(context, streamsWrapper, showIconNoAudio ? new SparseArray<>() : null);
|
||||
}
|
||||
|
||||
public StreamItemAdapter(Context context, StreamSizeWrapper<T> streamsWrapper) {
|
||||
public StreamItemAdapter(final Context context, final StreamSizeWrapper<T> streamsWrapper) {
|
||||
this(context, streamsWrapper, null);
|
||||
}
|
||||
|
||||
|
|
@ -65,28 +71,33 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||
}
|
||||
|
||||
@Override
|
||||
public T getItem(int position) {
|
||||
public T getItem(final int position) {
|
||||
return streamsWrapper.getStreamsList().get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
public long getItemId(final int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, View convertView, ViewGroup parent) {
|
||||
public View getDropDownView(final int position, final View convertView,
|
||||
final ViewGroup parent) {
|
||||
return getCustomView(position, convertView, parent, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
return getCustomView(((Spinner) parent).getSelectedItemPosition(), convertView, parent, false);
|
||||
public View getView(final int position, final View convertView, final ViewGroup parent) {
|
||||
return getCustomView(((Spinner) parent).getSelectedItemPosition(),
|
||||
convertView, parent, false);
|
||||
}
|
||||
|
||||
private View getCustomView(int position, View convertView, ViewGroup parent, boolean isDropdownItem) {
|
||||
private View getCustomView(final int position, final View view, final ViewGroup parent,
|
||||
final boolean isDropdownItem) {
|
||||
View convertView = view;
|
||||
if (convertView == null) {
|
||||
convertView = LayoutInflater.from(context).inflate(R.layout.stream_quality_item, parent, false);
|
||||
convertView = LayoutInflater.from(context).inflate(
|
||||
R.layout.stream_quality_item, parent, false);
|
||||
}
|
||||
|
||||
final ImageView woSoundIconView = convertView.findViewById(R.id.wo_sound_icon);
|
||||
|
|
@ -105,7 +116,8 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||
|
||||
if (secondaryStreams != null) {
|
||||
if (videoStream.isVideoOnly()) {
|
||||
woSoundIconVisibility = secondaryStreams.get(position) == null ? View.VISIBLE : View.INVISIBLE;
|
||||
woSoundIconVisibility = secondaryStreams.get(position) == null ? View.VISIBLE
|
||||
: View.INVISIBLE;
|
||||
} else if (isDropdownItem) {
|
||||
woSoundIconVisibility = View.INVISIBLE;
|
||||
}
|
||||
|
|
@ -125,7 +137,8 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||
}
|
||||
|
||||
if (streamsWrapper.getSizeInBytes(position) > 0) {
|
||||
SecondaryStreamHelper secondary = secondaryStreams == null ? null : secondaryStreams.get(position);
|
||||
SecondaryStreamHelper secondary = secondaryStreams == null ? null
|
||||
: secondaryStreams.get(position);
|
||||
if (secondary != null) {
|
||||
long size = secondary.getSizeInBytes() + streamsWrapper.getSizeInBytes(position);
|
||||
sizeView.setText(Utility.formatBytes(size));
|
||||
|
|
@ -159,30 +172,36 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||
|
||||
/**
|
||||
* A wrapper class that includes a way of storing the stream sizes.
|
||||
*
|
||||
* @param <T> the stream type's class extending {@link Stream}
|
||||
*/
|
||||
public static class StreamSizeWrapper<T extends Stream> implements Serializable {
|
||||
private static final StreamSizeWrapper<Stream> EMPTY = new StreamSizeWrapper<>(Collections.emptyList(), null);
|
||||
private static final StreamSizeWrapper<Stream> EMPTY = new StreamSizeWrapper<>(
|
||||
Collections.emptyList(), null);
|
||||
private final List<T> streamsList;
|
||||
private final long[] streamSizes;
|
||||
private final String unknownSize;
|
||||
|
||||
public StreamSizeWrapper(List<T> sL, Context context) {
|
||||
public StreamSizeWrapper(final List<T> sL, final Context context) {
|
||||
this.streamsList = sL != null
|
||||
? sL
|
||||
: Collections.emptyList();
|
||||
this.streamSizes = new long[streamsList.size()];
|
||||
this.unknownSize = context == null ? "--.-" : context.getString(R.string.unknown_content);
|
||||
this.unknownSize = context == null
|
||||
? "--.-" : context.getString(R.string.unknown_content);
|
||||
|
||||
for (int i = 0; i < streamSizes.length; i++) streamSizes[i] = -2;
|
||||
Arrays.fill(streamSizes, -2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to fetch the sizes of all the streams in a wrapper.
|
||||
*
|
||||
* @param <X> the stream type's class extending {@link Stream}
|
||||
* @param streamsWrapper the wrapper
|
||||
* @return a {@link Single} that returns a boolean indicating if any elements were changed
|
||||
*/
|
||||
public static <X extends Stream> Single<Boolean> fetchSizeForWrapper(StreamSizeWrapper<X> streamsWrapper) {
|
||||
public static <X extends Stream> Single<Boolean> fetchSizeForWrapper(
|
||||
final StreamSizeWrapper<X> streamsWrapper) {
|
||||
final Callable<Boolean> fetchAndSet = () -> {
|
||||
boolean hasChanged = false;
|
||||
for (X stream : streamsWrapper.getStreamsList()) {
|
||||
|
|
@ -190,7 +209,8 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||
continue;
|
||||
}
|
||||
|
||||
final long contentLength = DownloaderImpl.getInstance().getContentLength(stream.getUrl());
|
||||
final long contentLength = DownloaderImpl.getInstance().getContentLength(
|
||||
stream.getUrl());
|
||||
streamsWrapper.setSize(stream, contentLength);
|
||||
hasChanged = true;
|
||||
}
|
||||
|
|
@ -203,44 +223,44 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||
.onErrorReturnItem(true);
|
||||
}
|
||||
|
||||
public static <X extends Stream> StreamSizeWrapper<X> empty() {
|
||||
//noinspection unchecked
|
||||
return (StreamSizeWrapper<X>) EMPTY;
|
||||
}
|
||||
|
||||
public List<T> getStreamsList() {
|
||||
return streamsList;
|
||||
}
|
||||
|
||||
public long getSizeInBytes(int streamIndex) {
|
||||
public long getSizeInBytes(final int streamIndex) {
|
||||
return streamSizes[streamIndex];
|
||||
}
|
||||
|
||||
public long getSizeInBytes(T stream) {
|
||||
public long getSizeInBytes(final T stream) {
|
||||
return streamSizes[streamsList.indexOf(stream)];
|
||||
}
|
||||
|
||||
public String getFormattedSize(int streamIndex) {
|
||||
public String getFormattedSize(final int streamIndex) {
|
||||
return formatSize(getSizeInBytes(streamIndex));
|
||||
}
|
||||
|
||||
public String getFormattedSize(T stream) {
|
||||
public String getFormattedSize(final T stream) {
|
||||
return formatSize(getSizeInBytes(stream));
|
||||
}
|
||||
|
||||
private String formatSize(long size) {
|
||||
private String formatSize(final long size) {
|
||||
if (size > -1) {
|
||||
return Utility.formatBytes(size);
|
||||
}
|
||||
return unknownSize;
|
||||
}
|
||||
|
||||
public void setSize(int streamIndex, long sizeInBytes) {
|
||||
public void setSize(final int streamIndex, final long sizeInBytes) {
|
||||
streamSizes[streamIndex] = sizeInBytes;
|
||||
}
|
||||
|
||||
public void setSize(T stream, long sizeInBytes) {
|
||||
public void setSize(final T stream, final long sizeInBytes) {
|
||||
streamSizes[streamsList.indexOf(stream)] = sizeInBytes;
|
||||
}
|
||||
|
||||
public static <X extends Stream> StreamSizeWrapper<X> empty() {
|
||||
//noinspection unchecked
|
||||
return (StreamSizeWrapper<X>) EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package org.schabi.newpipe.util;
|
|||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
|
|
@ -27,31 +26,36 @@ public class TLSSocketFactoryCompat extends SSLSocketFactory {
|
|||
|
||||
private SSLSocketFactory internalSSLSocketFactory;
|
||||
|
||||
public static TLSSocketFactoryCompat getInstance() throws NoSuchAlgorithmException, KeyManagementException {
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
return instance = new TLSSocketFactoryCompat();
|
||||
}
|
||||
|
||||
|
||||
public TLSSocketFactoryCompat() throws KeyManagementException, NoSuchAlgorithmException {
|
||||
SSLContext context = SSLContext.getInstance("TLS");
|
||||
context.init(null, null, null);
|
||||
internalSSLSocketFactory = context.getSocketFactory();
|
||||
}
|
||||
|
||||
public TLSSocketFactoryCompat(TrustManager[] tm) throws KeyManagementException, NoSuchAlgorithmException {
|
||||
|
||||
public TLSSocketFactoryCompat(final TrustManager[] tm)
|
||||
throws KeyManagementException, NoSuchAlgorithmException {
|
||||
SSLContext context = SSLContext.getInstance("TLS");
|
||||
context.init(null, tm, new java.security.SecureRandom());
|
||||
internalSSLSocketFactory = context.getSocketFactory();
|
||||
}
|
||||
|
||||
public static TLSSocketFactoryCompat getInstance()
|
||||
throws NoSuchAlgorithmException, KeyManagementException {
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
instance = new TLSSocketFactoryCompat();
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static void setAsDefault() {
|
||||
try {
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(getInstance());
|
||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||
if (DEBUG) e.printStackTrace();
|
||||
if (DEBUG) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,34 +75,40 @@ public class TLSSocketFactoryCompat extends SSLSocketFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
|
||||
public Socket createSocket(final Socket s, final String host, final int port,
|
||||
final boolean autoClose) throws IOException {
|
||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
|
||||
public Socket createSocket(final String host, final int port) throws IOException {
|
||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
|
||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
|
||||
public Socket createSocket(final String host, final int port, final InetAddress localHost,
|
||||
final int localPort) throws IOException {
|
||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(
|
||||
host, port, localHost, localPort));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
public Socket createSocket(final InetAddress host, final int port) throws IOException {
|
||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
|
||||
public Socket createSocket(final InetAddress address, final int port,
|
||||
final InetAddress localAddress, final int localPort)
|
||||
throws IOException {
|
||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(
|
||||
address, port, localAddress, localPort));
|
||||
}
|
||||
|
||||
private Socket enableTLSOnSocket(Socket socket) {
|
||||
private Socket enableTLSOnSocket(final Socket socket) {
|
||||
if (socket != null && (socket instanceof SSLSocket)) {
|
||||
((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.1", "TLSv1.2"});
|
||||
}
|
||||
return socket;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,18 +22,20 @@ package org.schabi.newpipe.util;
|
|||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ContextThemeWrapper;
|
||||
|
||||
import androidx.annotation.AttrRes;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ContextThemeWrapper;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
|
||||
public class ThemeHelper {
|
||||
public final class ThemeHelper {
|
||||
private ThemeHelper() { }
|
||||
|
||||
/**
|
||||
* Apply the selected theme (on NewPipe settings) in the context
|
||||
|
|
@ -41,7 +43,7 @@ public class ThemeHelper {
|
|||
*
|
||||
* @param context context that the theme will be applied
|
||||
*/
|
||||
public static void setTheme(Context context) {
|
||||
public static void setTheme(final Context context) {
|
||||
setTheme(context, -1);
|
||||
}
|
||||
|
||||
|
|
@ -53,17 +55,19 @@ public class ThemeHelper {
|
|||
* @param serviceId the theme will be styled to the service with this id,
|
||||
* pass -1 to get the default style
|
||||
*/
|
||||
public static void setTheme(Context context, int serviceId) {
|
||||
public static void setTheme(final Context context, final int serviceId) {
|
||||
context.setTheme(getThemeForService(context, serviceId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the selected theme (on NewPipe settings) is the Light theme
|
||||
* Return true if the selected theme (on NewPipe settings) is the Light theme.
|
||||
*
|
||||
* @param context context to get the preference
|
||||
* @return whether the light theme is selected
|
||||
*/
|
||||
public static boolean isLightThemeSelected(Context context) {
|
||||
return getSelectedThemeString(context).equals(context.getResources().getString(R.string.light_theme_key));
|
||||
public static boolean isLightThemeSelected(final Context context) {
|
||||
return getSelectedThemeString(context).equals(context.getResources()
|
||||
.getString(R.string.light_theme_key));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -73,18 +77,19 @@ public class ThemeHelper {
|
|||
* @param baseContext the base context for the wrapper
|
||||
* @return a wrapped-styled context
|
||||
*/
|
||||
public static Context getThemedContext(Context baseContext) {
|
||||
public static Context getThemedContext(final Context baseContext) {
|
||||
return new ContextThemeWrapper(baseContext, getThemeForService(baseContext, -1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the selected theme without being styled to any service (see {@link #getThemeForService(Context, int)}).
|
||||
* Return the selected theme without being styled to any service.
|
||||
* See {@link #getThemeForService(Context, int)}.
|
||||
*
|
||||
* @param context context to get the selected theme
|
||||
* @return the selected style (the default one)
|
||||
*/
|
||||
@StyleRes
|
||||
public static int getDefaultTheme(Context context) {
|
||||
public static int getDefaultTheme(final Context context) {
|
||||
return getThemeForService(context, -1);
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +100,7 @@ public class ThemeHelper {
|
|||
* @return the dialog style (the default one)
|
||||
*/
|
||||
@StyleRes
|
||||
public static int getDialogTheme(Context context) {
|
||||
public static int getDialogTheme(final Context context) {
|
||||
return isLightThemeSelected(context) ? R.style.LightDialogTheme : R.style.DarkDialogTheme;
|
||||
}
|
||||
|
||||
|
|
@ -106,8 +111,9 @@ public class ThemeHelper {
|
|||
* @return the dialog style (the default one)
|
||||
*/
|
||||
@StyleRes
|
||||
public static int getMinWidthDialogTheme(Context context) {
|
||||
return isLightThemeSelected(context) ? R.style.LightDialogMinWidthTheme : R.style.DarkDialogMinWidthTheme;
|
||||
public static int getMinWidthDialogTheme(final Context context) {
|
||||
return isLightThemeSelected(context) ? R.style.LightDialogMinWidthTheme
|
||||
: R.style.DarkDialogMinWidthTheme;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -119,7 +125,7 @@ public class ThemeHelper {
|
|||
* @return the selected style (styled)
|
||||
*/
|
||||
@StyleRes
|
||||
public static int getThemeForService(Context context, int serviceId) {
|
||||
public static int getThemeForService(final Context context, final int serviceId) {
|
||||
String lightTheme = context.getResources().getString(R.string.light_theme_key);
|
||||
String darkTheme = context.getResources().getString(R.string.dark_theme_key);
|
||||
String blackTheme = context.getResources().getString(R.string.black_theme_key);
|
||||
|
|
@ -127,9 +133,13 @@ public class ThemeHelper {
|
|||
String selectedTheme = getSelectedThemeString(context);
|
||||
|
||||
int defaultTheme = R.style.DarkTheme;
|
||||
if (selectedTheme.equals(lightTheme)) defaultTheme = R.style.LightTheme;
|
||||
else if (selectedTheme.equals(blackTheme)) defaultTheme = R.style.BlackTheme;
|
||||
else if (selectedTheme.equals(darkTheme)) defaultTheme = R.style.DarkTheme;
|
||||
if (selectedTheme.equals(lightTheme)) {
|
||||
defaultTheme = R.style.LightTheme;
|
||||
} else if (selectedTheme.equals(blackTheme)) {
|
||||
defaultTheme = R.style.BlackTheme;
|
||||
} else if (selectedTheme.equals(darkTheme)) {
|
||||
defaultTheme = R.style.DarkTheme;
|
||||
}
|
||||
|
||||
if (serviceId <= -1) {
|
||||
return defaultTheme;
|
||||
|
|
@ -143,9 +153,13 @@ public class ThemeHelper {
|
|||
}
|
||||
|
||||
String themeName = "DarkTheme";
|
||||
if (selectedTheme.equals(lightTheme)) themeName = "LightTheme";
|
||||
else if (selectedTheme.equals(blackTheme)) themeName = "BlackTheme";
|
||||
else if (selectedTheme.equals(darkTheme)) themeName = "DarkTheme";
|
||||
if (selectedTheme.equals(lightTheme)) {
|
||||
themeName = "LightTheme";
|
||||
} else if (selectedTheme.equals(blackTheme)) {
|
||||
themeName = "BlackTheme";
|
||||
} else if (selectedTheme.equals(darkTheme)) {
|
||||
themeName = "DarkTheme";
|
||||
}
|
||||
|
||||
themeName += "." + service.getServiceInfo().getName();
|
||||
int resourceId = context
|
||||
|
|
@ -160,24 +174,33 @@ public class ThemeHelper {
|
|||
}
|
||||
|
||||
@StyleRes
|
||||
public static int getSettingsThemeStyle(Context context) {
|
||||
public static int getSettingsThemeStyle(final Context context) {
|
||||
String lightTheme = context.getResources().getString(R.string.light_theme_key);
|
||||
String darkTheme = context.getResources().getString(R.string.dark_theme_key);
|
||||
String blackTheme = context.getResources().getString(R.string.black_theme_key);
|
||||
|
||||
String selectedTheme = getSelectedThemeString(context);
|
||||
|
||||
if (selectedTheme.equals(lightTheme)) return R.style.LightSettingsTheme;
|
||||
else if (selectedTheme.equals(blackTheme)) return R.style.BlackSettingsTheme;
|
||||
else if (selectedTheme.equals(darkTheme)) return R.style.DarkSettingsTheme;
|
||||
if (selectedTheme.equals(lightTheme)) {
|
||||
return R.style.LightSettingsTheme;
|
||||
} else if (selectedTheme.equals(blackTheme)) {
|
||||
return R.style.BlackSettingsTheme;
|
||||
} else if (selectedTheme.equals(darkTheme)) {
|
||||
return R.style.DarkSettingsTheme;
|
||||
} else {
|
||||
// Fallback
|
||||
else return R.style.DarkSettingsTheme;
|
||||
return R.style.DarkSettingsTheme;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a resource id from a resource styled according to the the context's theme.
|
||||
*
|
||||
* @param context Android app context
|
||||
* @param attr attribute reference of the resource
|
||||
* @return resource ID
|
||||
*/
|
||||
public static int resolveResourceIdFromAttr(Context context, @AttrRes int attr) {
|
||||
public static int resolveResourceIdFromAttr(final Context context, @AttrRes final int attr) {
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
|
||||
int attributeResourceId = a.getResourceId(0, 0);
|
||||
a.recycle();
|
||||
|
|
@ -186,8 +209,12 @@ public class ThemeHelper {
|
|||
|
||||
/**
|
||||
* Get a color from an attr styled according to the the context's theme.
|
||||
*
|
||||
* @param context Android app context
|
||||
* @param attrColor attribute reference of the resource
|
||||
* @return the color
|
||||
*/
|
||||
public static int resolveColorFromAttr(Context context, @AttrRes int attrColor) {
|
||||
public static int resolveColorFromAttr(final Context context, @AttrRes final int attrColor) {
|
||||
final TypedValue value = new TypedValue();
|
||||
context.getTheme().resolveAttribute(attrColor, value, true);
|
||||
|
||||
|
|
@ -198,21 +225,22 @@ public class ThemeHelper {
|
|||
return value.data;
|
||||
}
|
||||
|
||||
private static String getSelectedThemeString(Context context) {
|
||||
private static String getSelectedThemeString(final Context context) {
|
||||
String themeKey = context.getString(R.string.theme_key);
|
||||
String defaultTheme = context.getResources().getString(R.string.default_theme_value);
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getString(themeKey, defaultTheme);
|
||||
return PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getString(themeKey, defaultTheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will get the R.drawable.* resource to which attr is currently pointing to.
|
||||
*
|
||||
* @param attr a R.attribute.* resource value
|
||||
* @param attr a R.attribute.* resource value
|
||||
* @param context the context to use
|
||||
* @return a R.drawable.* resource value
|
||||
*/
|
||||
public static int getIconByAttr(final int attr, final Context context) {
|
||||
return context.obtainStyledAttributes(new int[] {attr})
|
||||
return context.obtainStyledAttributes(new int[]{attr})
|
||||
.getResourceId(0, -1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,42 +12,45 @@ import java.util.zip.ZipOutputStream;
|
|||
* Created by Christian Schabesberger on 28.01.18.
|
||||
* Copyright 2018 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ZipHelper.java is part of NewPipe
|
||||
*
|
||||
* <p>
|
||||
* License: GPL-3.0+
|
||||
* This program 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.
|
||||
*
|
||||
* <p>
|
||||
* This program 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.
|
||||
*
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class ZipHelper {
|
||||
public final class ZipHelper {
|
||||
private ZipHelper() { }
|
||||
|
||||
private static final int BUFFER_SIZE = 2048;
|
||||
|
||||
/**
|
||||
* This function helps to create zip files.
|
||||
* Caution this will override the original file.
|
||||
*
|
||||
* @param outZip The ZipOutputStream where the data should be stored in
|
||||
* @param file The path of the file that should be added to zip.
|
||||
* @param name The path of the file inside the zip.
|
||||
* @param file The path of the file that should be added to zip.
|
||||
* @param name The path of the file inside the zip.
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void addFileToZip(ZipOutputStream outZip, String file, String name) throws Exception {
|
||||
byte data[] = new byte[BUFFER_SIZE];
|
||||
public static void addFileToZip(final ZipOutputStream outZip, final String file,
|
||||
final String name) throws Exception {
|
||||
byte[] data = new byte[BUFFER_SIZE];
|
||||
FileInputStream fi = new FileInputStream(file);
|
||||
BufferedInputStream inputStream = new BufferedInputStream(fi, BUFFER_SIZE);
|
||||
ZipEntry entry = new ZipEntry(name);
|
||||
outZip.putNextEntry(entry);
|
||||
int count;
|
||||
while((count = inputStream.read(data, 0, BUFFER_SIZE)) != -1) {
|
||||
while ((count = inputStream.read(data, 0, BUFFER_SIZE)) != -1) {
|
||||
outZip.write(data, 0, count);
|
||||
}
|
||||
inputStream.close();
|
||||
|
|
@ -56,36 +59,39 @@ public class ZipHelper {
|
|||
/**
|
||||
* This will extract data from Zipfiles.
|
||||
* Caution this will override the original file.
|
||||
*
|
||||
* @param filePath The path of the zip
|
||||
* @param file The path of the file on the disk where the data should be extracted to.
|
||||
* @param name The path of the file inside the zip.
|
||||
* @return will return true if the file was found within the zip file
|
||||
* @throws Exception
|
||||
*/
|
||||
public static boolean extractFileFromZip(String filePath, String file, String name) throws Exception {
|
||||
public static boolean extractFileFromZip(final String filePath, final String file,
|
||||
final String name) throws Exception {
|
||||
|
||||
ZipInputStream inZip = new ZipInputStream(
|
||||
new BufferedInputStream(
|
||||
new FileInputStream(filePath)));
|
||||
|
||||
byte data[] = new byte[BUFFER_SIZE];
|
||||
byte[] data = new byte[BUFFER_SIZE];
|
||||
|
||||
boolean found = false;
|
||||
|
||||
ZipEntry ze;
|
||||
while((ze = inZip.getNextEntry()) != null) {
|
||||
if(ze.getName().equals(name)) {
|
||||
while ((ze = inZip.getNextEntry()) != null) {
|
||||
if (ze.getName().equals(name)) {
|
||||
found = true;
|
||||
// delete old file first
|
||||
File oldFile = new File(file);
|
||||
if(oldFile.exists()) {
|
||||
if(!oldFile.delete()) {
|
||||
if (oldFile.exists()) {
|
||||
if (!oldFile.delete()) {
|
||||
throw new Exception("Could not delete " + file);
|
||||
}
|
||||
}
|
||||
|
||||
FileOutputStream outFile = new FileOutputStream(file);
|
||||
int count = 0;
|
||||
while((count = inZip.read(data)) != -1) {
|
||||
while ((count = inZip.read(data)) != -1) {
|
||||
outFile.write(data, 0, count);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,122 +35,139 @@ public final class PatternsCompat {
|
|||
* http://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
||||
* This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py
|
||||
*/
|
||||
static final String IANA_TOP_LEVEL_DOMAINS =
|
||||
"(?:"
|
||||
+ "(?:aaa|aarp|abb|abbott|abogado|academy|accenture|accountant|accountants|aco|active"
|
||||
+ "|actor|ads|adult|aeg|aero|afl|agency|aig|airforce|airtel|allfinanz|alsace|amica|amsterdam"
|
||||
+ "|android|apartments|app|apple|aquarelle|aramco|archi|army|arpa|arte|asia|associates"
|
||||
+ "|attorney|auction|audio|auto|autos|axa|azure|a[cdefgilmoqrstuwxz])"
|
||||
+ "|(?:band|bank|bar|barcelona|barclaycard|barclays|bargains|bauhaus|bayern|bbc|bbva"
|
||||
+ "|bcn|beats|beer|bentley|berlin|best|bet|bharti|bible|bid|bike|bing|bingo|bio|biz|black"
|
||||
+ "|blackfriday|bloomberg|blue|bms|bmw|bnl|bnpparibas|boats|bom|bond|boo|boots|boutique"
|
||||
+ "|bradesco|bridgestone|broadway|broker|brother|brussels|budapest|build|builders|business"
|
||||
+ "|buzz|bzh|b[abdefghijmnorstvwyz])"
|
||||
+ "|(?:cab|cafe|cal|camera|camp|cancerresearch|canon|capetown|capital|car|caravan|cards"
|
||||
+ "|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|ceb|center|ceo"
|
||||
+ "|cern|cfa|cfd|chanel|channel|chat|cheap|chloe|christmas|chrome|church|cipriani|cisco"
|
||||
+ "|citic|city|cityeats|claims|cleaning|click|clinic|clothing|cloud|club|clubmed|coach"
|
||||
+ "|codes|coffee|college|cologne|com|commbank|community|company|computer|comsec|condos"
|
||||
+ "|construction|consulting|contractors|cooking|cool|coop|corsica|country|coupons|courses"
|
||||
+ "|credit|creditcard|creditunion|cricket|crown|crs|cruises|csc|cuisinella|cymru|cyou|c[acdfghiklmnoruvwxyz])"
|
||||
+ "|(?:dabur|dad|dance|date|dating|datsun|day|dclk|deals|degree|delivery|dell|delta"
|
||||
+ "|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory|discount"
|
||||
+ "|dnp|docs|dog|doha|domains|doosan|download|drive|durban|dvag|d[ejkmoz])"
|
||||
+ "|(?:earth|eat|edu|education|email|emerck|energy|engineer|engineering|enterprises"
|
||||
+ "|epson|equipment|erni|esq|estate|eurovision|eus|events|everbank|exchange|expert|exposed"
|
||||
+ "|express|e[cegrstu])"
|
||||
+ "|(?:fage|fail|fairwinds|faith|family|fan|fans|farm|fashion|feedback|ferrero|film"
|
||||
+ "|final|finance|financial|firmdale|fish|fishing|fit|fitness|flights|florist|flowers|flsmidth"
|
||||
+ "|fly|foo|football|forex|forsale|forum|foundation|frl|frogans|fund|furniture|futbol|fyi"
|
||||
+ "|f[ijkmor])"
|
||||
+ "|(?:gal|gallery|game|garden|gbiz|gdn|gea|gent|genting|ggee|gift|gifts|gives|giving"
|
||||
+ "|glass|gle|global|globo|gmail|gmo|gmx|gold|goldpoint|golf|goo|goog|google|gop|gov|grainger"
|
||||
+ "|graphics|gratis|green|gripe|group|gucci|guge|guide|guitars|guru|g[abdefghilmnpqrstuwy])"
|
||||
+ "|(?:hamburg|hangout|haus|healthcare|help|here|hermes|hiphop|hitachi|hiv|hockey|holdings"
|
||||
+ "|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house|how|hsbc|hyundai"
|
||||
+ "|h[kmnrtu])"
|
||||
+ "|(?:ibm|icbc|ice|icu|ifm|iinet|immo|immobilien|industries|infiniti|info|ing|ink|institute"
|
||||
+ "|insure|int|international|investments|ipiranga|irish|ist|istanbul|itau|iwc|i[delmnoqrst])"
|
||||
+ "|(?:jaguar|java|jcb|jetzt|jewelry|jlc|jll|jobs|joburg|jprs|juegos|j[emop])"
|
||||
+ "|(?:kaufen|kddi|kia|kim|kinder|kitchen|kiwi|koeln|komatsu|krd|kred|kyoto|k[eghimnprwyz])"
|
||||
+ "|(?:lacaixa|lancaster|land|landrover|lasalle|lat|latrobe|law|lawyer|lds|lease|leclerc"
|
||||
+ "|legal|lexus|lgbt|liaison|lidl|life|lifestyle|lighting|limited|limo|linde|link|live"
|
||||
+ "|lixil|loan|loans|lol|london|lotte|lotto|love|ltd|ltda|lupin|luxe|luxury|l[abcikrstuvy])"
|
||||
+ "|(?:madrid|maif|maison|man|management|mango|market|marketing|markets|marriott|mba"
|
||||
+ "|media|meet|melbourne|meme|memorial|men|menu|meo|miami|microsoft|mil|mini|mma|mobi|moda"
|
||||
+ "|moe|moi|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov|movie|movistar"
|
||||
+ "|mtn|mtpc|mtr|museum|mutuelle|m[acdeghklmnopqrstuvwxyz])"
|
||||
+ "|(?:nadex|nagoya|name|navy|nec|net|netbank|network|neustar|new|news|nexus|ngo|nhk"
|
||||
+ "|nico|ninja|nissan|nokia|nra|nrw|ntt|nyc|n[acefgilopruz])"
|
||||
+ "|(?:obi|office|okinawa|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|osaka"
|
||||
+ "|otsuka|ovh|om)"
|
||||
+ "|(?:page|panerai|paris|partners|parts|party|pet|pharmacy|philips|photo|photography"
|
||||
+ "|photos|physio|piaget|pics|pictet|pictures|ping|pink|pizza|place|play|playstation|plumbing"
|
||||
+ "|plus|pohl|poker|porn|post|praxi|press|pro|prod|productions|prof|properties|property"
|
||||
+ "|protection|pub|p[aefghklmnrstwy])"
|
||||
+ "|(?:qpon|quebec|qa)"
|
||||
+ "|(?:racing|realtor|realty|recipes|red|redstone|rehab|reise|reisen|reit|ren|rent|rentals"
|
||||
+ "|repair|report|republican|rest|restaurant|review|reviews|rich|ricoh|rio|rip|rocher|rocks"
|
||||
+ "|rodeo|rsvp|ruhr|run|rwe|ryukyu|r[eosuw])"
|
||||
+ "|(?:saarland|sakura|sale|samsung|sandvik|sandvikcoromant|sanofi|sap|sapo|sarl|saxo"
|
||||
+ "|sbs|sca|scb|schmidt|scholarships|school|schule|schwarz|science|scor|scot|seat|security"
|
||||
+ "|seek|sener|services|seven|sew|sex|sexy|shiksha|shoes|show|shriram|singles|site|ski"
|
||||
+ "|sky|skype|sncf|soccer|social|software|sohu|solar|solutions|sony|soy|space|spiegel|spreadbetting"
|
||||
+ "|srl|stada|starhub|statoil|stc|stcgroup|stockholm|studio|study|style|sucks|supplies"
|
||||
+ "|supply|support|surf|surgery|suzuki|swatch|swiss|sydney|systems|s[abcdeghijklmnortuvxyz])"
|
||||
+ "|(?:tab|taipei|tatamotors|tatar|tattoo|tax|taxi|team|tech|technology|tel|telefonica"
|
||||
+ "|temasek|tennis|thd|theater|theatre|tickets|tienda|tips|tires|tirol|today|tokyo|tools"
|
||||
+ "|top|toray|toshiba|tours|town|toyota|toys|trade|trading|training|travel|trust|tui|t[cdfghjklmnortvwz])"
|
||||
+ "|(?:ubs|university|uno|uol|u[agksyz])"
|
||||
+ "|(?:vacations|vana|vegas|ventures|versicherung|vet|viajes|video|villas|vin|virgin"
|
||||
+ "|vision|vista|vistaprint|viva|vlaanderen|vodka|vote|voting|voto|voyage|v[aceginu])"
|
||||
+ "|(?:wales|walter|wang|watch|webcam|website|wed|wedding|weir|whoswho|wien|wiki|williamhill"
|
||||
+ "|win|windows|wine|wme|work|works|world|wtc|wtf|w[fs])"
|
||||
+ "|(?:\u03b5\u03bb|\u0431\u0435\u043b|\u0434\u0435\u0442\u0438|\u043a\u043e\u043c|\u043c\u043a\u0434"
|
||||
+ "|\u043c\u043e\u043d|\u043c\u043e\u0441\u043a\u0432\u0430|\u043e\u043d\u043b\u0430\u0439\u043d"
|
||||
+ "|\u043e\u0440\u0433|\u0440\u0443\u0441|\u0440\u0444|\u0441\u0430\u0439\u0442|\u0441\u0440\u0431"
|
||||
+ "|\u0443\u043a\u0440|\u049b\u0430\u0437|\u0570\u0561\u0575|\u05e7\u05d5\u05dd|\u0627\u0631\u0627\u0645\u0643\u0648"
|
||||
+ "|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629"
|
||||
+ "|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a|\u0627\u06cc\u0631\u0627\u0646"
|
||||
+ "|\u0628\u0627\u0632\u0627\u0631|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633"
|
||||
+ "|\u0633\u0648\u062f\u0627\u0646|\u0633\u0648\u0631\u064a\u0629|\u0634\u0628\u0643\u0629"
|
||||
+ "|\u0639\u0631\u0627\u0642|\u0639\u0645\u0627\u0646|\u0641\u0644\u0633\u0637\u064a\u0646"
|
||||
+ "|\u0642\u0637\u0631|\u0643\u0648\u0645|\u0645\u0635\u0631|\u0645\u0644\u064a\u0633\u064a\u0627"
|
||||
+ "|\u0645\u0648\u0642\u0639|\u0915\u0949\u092e|\u0928\u0947\u091f|\u092d\u093e\u0930\u0924"
|
||||
+ "|\u0938\u0902\u0917\u0920\u0928|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24|\u0aad\u0abe\u0ab0\u0aa4"
|
||||
+ "|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd"
|
||||
+ "|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e04\u0e2d\u0e21|\u0e44\u0e17\u0e22"
|
||||
+ "|\u10d2\u10d4|\u307f\u3093\u306a|\u30b0\u30fc\u30b0\u30eb|\u30b3\u30e0|\u4e16\u754c"
|
||||
+ "|\u4e2d\u4fe1|\u4e2d\u56fd|\u4e2d\u570b|\u4e2d\u6587\u7f51|\u4f01\u4e1a|\u4f5b\u5c71"
|
||||
+ "|\u4fe1\u606f|\u5065\u5eb7|\u516b\u5366|\u516c\u53f8|\u516c\u76ca|\u53f0\u6e7e|\u53f0\u7063"
|
||||
+ "|\u5546\u57ce|\u5546\u5e97|\u5546\u6807|\u5728\u7ebf|\u5927\u62ff|\u5a31\u4e50|\u5de5\u884c"
|
||||
+ "|\u5e7f\u4e1c|\u6148\u5584|\u6211\u7231\u4f60|\u624b\u673a|\u653f\u52a1|\u653f\u5e9c"
|
||||
+ "|\u65b0\u52a0\u5761|\u65b0\u95fb|\u65f6\u5c1a|\u673a\u6784|\u6de1\u9a6c\u9521|\u6e38\u620f"
|
||||
+ "|\u70b9\u770b|\u79fb\u52a8|\u7ec4\u7ec7\u673a\u6784|\u7f51\u5740|\u7f51\u5e97|\u7f51\u7edc"
|
||||
+ "|\u8c37\u6b4c|\u96c6\u56e2|\u98de\u5229\u6d66|\u9910\u5385|\u9999\u6e2f|\ub2f7\ub137"
|
||||
+ "|\ub2f7\ucef4|\uc0bc\uc131|\ud55c\uad6d|xbox"
|
||||
+ "|xerox|xin|xn\\-\\-11b4c3d|xn\\-\\-1qqw23a|xn\\-\\-30rr7y|xn\\-\\-3bst00m|xn\\-\\-3ds443g"
|
||||
+ "|xn\\-\\-3e0b707e|xn\\-\\-3pxu8k|xn\\-\\-42c2d9a|xn\\-\\-45brj9c|xn\\-\\-45q11c|xn\\-\\-4gbrim"
|
||||
+ "|xn\\-\\-55qw42g|xn\\-\\-55qx5d|xn\\-\\-6frz82g|xn\\-\\-6qq986b3xl|xn\\-\\-80adxhks"
|
||||
+ "|xn\\-\\-80ao21a|xn\\-\\-80asehdb|xn\\-\\-80aswg|xn\\-\\-90a3ac|xn\\-\\-90ais|xn\\-\\-9dbq2a"
|
||||
+ "|xn\\-\\-9et52u|xn\\-\\-b4w605ferd|xn\\-\\-c1avg|xn\\-\\-c2br7g|xn\\-\\-cg4bki|xn\\-\\-clchc0ea0b2g2a9gcd"
|
||||
+ "|xn\\-\\-czr694b|xn\\-\\-czrs0t|xn\\-\\-czru2d|xn\\-\\-d1acj3b|xn\\-\\-d1alf|xn\\-\\-efvy88h"
|
||||
+ "|xn\\-\\-estv75g|xn\\-\\-fhbei|xn\\-\\-fiq228c5hs|xn\\-\\-fiq64b|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s"
|
||||
+ "|xn\\-\\-fjq720a|xn\\-\\-flw351e|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-gecrj9c"
|
||||
+ "|xn\\-\\-h2brj9c|xn\\-\\-hxt814e|xn\\-\\-i1b6b1a6a2e|xn\\-\\-imr513n|xn\\-\\-io0a7i"
|
||||
+ "|xn\\-\\-j1aef|xn\\-\\-j1amh|xn\\-\\-j6w193g|xn\\-\\-kcrx77d1x4a|xn\\-\\-kprw13d|xn\\-\\-kpry57d"
|
||||
+ "|xn\\-\\-kput3i|xn\\-\\-l1acc|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgb9awbf|xn\\-\\-mgba3a3ejt"
|
||||
+ "|xn\\-\\-mgba3a4f16a|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbab2bd|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e"
|
||||
+ "|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-mgbpl2fh|xn\\-\\-mgbtx2b|xn\\-\\-mgbx4cd0ab"
|
||||
+ "|xn\\-\\-mk1bu44c|xn\\-\\-mxtq1m|xn\\-\\-ngbc5azd|xn\\-\\-node|xn\\-\\-nqv7f|xn\\-\\-nqv7fs00ema"
|
||||
+ "|xn\\-\\-nyqy26a|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1acf|xn\\-\\-p1ai|xn\\-\\-pgbs0dh"
|
||||
+ "|xn\\-\\-pssy2u|xn\\-\\-q9jyb4c|xn\\-\\-qcka1pmc|xn\\-\\-qxam|xn\\-\\-rhqv96g|xn\\-\\-s9brj9c"
|
||||
+ "|xn\\-\\-ses554g|xn\\-\\-t60b56a|xn\\-\\-tckwe|xn\\-\\-unup4y|xn\\-\\-vermgensberater\\-ctb"
|
||||
+ "|xn\\-\\-vermgensberatung\\-pwb|xn\\-\\-vhquv|xn\\-\\-vuq861b|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a"
|
||||
+ "|xn\\-\\-xhq521b|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-y9a3aq|xn\\-\\-yfro4i67o"
|
||||
+ "|xn\\-\\-ygbi2ammx|xn\\-\\-zfr164b|xperia|xxx|xyz)"
|
||||
+ "|(?:yachts|yamaxun|yandex|yodobashi|yoga|yokohama|youtube|y[et])"
|
||||
+ "|(?:zara|zip|zone|zuerich|z[amw]))";
|
||||
static final String IANA_TOP_LEVEL_DOMAINS = "(?:"
|
||||
+ "(?:aaa|aarp|abb|abbott|abogado|academy|accenture|accountant|accountants|aco|active"
|
||||
+ "|actor|ads|adult|aeg|aero|afl|agency|aig|airforce|airtel|allfinanz|alsace|amica"
|
||||
+ "|amsterdam|android|apartments|app|apple|aquarelle|aramco|archi|army|arpa|arte|asia"
|
||||
+ "|associates|attorney|auction|audio|auto|autos|axa|azure|a[cdefgilmoqrstuwxz])"
|
||||
+ "|(?:band|bank|bar|barcelona|barclaycard|barclays|bargains|bauhaus|bayern|bbc|bbva"
|
||||
+ "|bcn|beats|beer|bentley|berlin|best|bet|bharti|bible|bid|bike|bing|bingo|bio|biz"
|
||||
+ "|black|blackfriday|bloomberg|blue|bms|bmw|bnl|bnpparibas|boats|bom|bond|boo|boots"
|
||||
+ "|boutique|bradesco|bridgestone|broadway|broker|brother|brussels|budapest|build"
|
||||
+ "|builders|business|buzz|bzh|b[abdefghijmnorstvwyz])"
|
||||
+ "|(?:cab|cafe|cal|camera|camp|cancerresearch|canon|capetown|capital|car|caravan|cards"
|
||||
+ "|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|ceb|center"
|
||||
+ "|ceo|cern|cfa|cfd|chanel|channel|chat|cheap|chloe|christmas|chrome|church|cipriani"
|
||||
+ "|cisco|citic|city|cityeats|claims|cleaning|click|clinic|clothing|cloud|club|clubmed"
|
||||
+ "|coach|codes|coffee|college|cologne|com|commbank|community|company|computer|comsec"
|
||||
+ "|condos|construction|consulting|contractors|cooking|cool|coop|corsica|country"
|
||||
+ "|coupons|courses|credit|creditcard|creditunion|cricket|crown|crs|cruises|csc"
|
||||
+ "|cuisinella|cymru|cyou|c[acdfghiklmnoruvwxyz])"
|
||||
+ "|(?:dabur|dad|dance|date|dating|datsun|day|dclk|deals|degree|delivery|dell|delta"
|
||||
+ "|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory"
|
||||
+ "|discount|dnp|docs|dog|doha|domains|doosan|download|drive|durban|dvag|d[ejkmoz])"
|
||||
+ "|(?:earth|eat|edu|education|email|emerck|energy|engineer|engineering|enterprises"
|
||||
+ "|epson|equipment|erni|esq|estate|eurovision|eus|events|everbank|exchange|expert"
|
||||
+ "|exposed|express|e[cegrstu])"
|
||||
+ "|(?:fage|fail|fairwinds|faith|family|fan|fans|farm"
|
||||
+ "|fashion|feedback|ferrero|film|final|finance|financial|firmdale|fish|fishing|fit"
|
||||
+ "|fitness|flights|florist|flowers|flsmidth|fly|foo|football|forex|forsale|forum"
|
||||
+ "|foundation|frl|frogans|fund|furniture|futbol|fyi|f[ijkmor])"
|
||||
+ "|(?:gal|gallery|game|garden|gbiz|gdn|gea|gent|genting|ggee|gift|gifts|gives|giving"
|
||||
+ "|glass|gle|global|globo|gmail|gmo|gmx|gold|goldpoint|golf|goo|goog|google|gop|gov"
|
||||
+ "|grainger|graphics|gratis|green|gripe|group|gucci|guge|guide|guitars|guru"
|
||||
+ "|g[abdefghilmnpqrstuwy])"
|
||||
+ "|(?:hamburg|hangout|haus|healthcare|help|here|hermes|hiphop|hitachi|hiv|hockey"
|
||||
+ "|holdings|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house"
|
||||
+ "|how|hsbc|hyundai|h[kmnrtu])"
|
||||
+ "|(?:ibm|icbc|ice|icu|ifm|iinet|immo|immobilien|industries|infiniti|info|ing|ink"
|
||||
+ "|institute|insure|int|international|investments|ipiranga|irish|ist|istanbul|itau"
|
||||
+ "|iwc|i[delmnoqrst])"
|
||||
+ "|(?:jaguar|java|jcb|jetzt|jewelry|jlc|jll|jobs|joburg|jprs|juegos|j[emop])"
|
||||
+ "|(?:kaufen|kddi|kia|kim|kinder|kitchen|kiwi|koeln|komatsu|krd|kred|kyoto"
|
||||
+ "|k[eghimnprwyz])"
|
||||
+ "|(?:lacaixa|lancaster|land|landrover|lasalle|lat|latrobe|law|lawyer|lds|lease"
|
||||
+ "|leclerc|legal|lexus|lgbt|liaison|lidl|life|lifestyle|lighting|limited|limo|linde"
|
||||
+ "|link|live|lixil|loan|loans|lol|london|lotte|lotto|love|ltd|ltda|lupin|luxe|luxury"
|
||||
+ "|l[abcikrstuvy])"
|
||||
+ "|(?:madrid|maif|maison|man|management|mango|market|marketing|markets|marriott|mba"
|
||||
+ "|media|meet|melbourne|meme|memorial|men|menu|meo|miami|microsoft|mil|mini|mma|mobi"
|
||||
+ "|moda|moe|moi|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov"
|
||||
+ "|movie|movistar|mtn|mtpc|mtr|museum|mutuelle|m[acdeghklmnopqrstuvwxyz])"
|
||||
+ "|(?:nadex|nagoya|name|navy|nec|net|netbank|network|neustar|new|news|nexus|ngo|nhk"
|
||||
+ "|nico|ninja|nissan|nokia|nra|nrw|ntt|nyc|n[acefgilopruz])"
|
||||
+ "|(?:obi|office|okinawa|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|osaka"
|
||||
+ "|otsuka|ovh|om)"
|
||||
+ "|(?:page|panerai|paris|partners|parts|party|pet|pharmacy|philips|photo|photography"
|
||||
+ "|photos|physio|piaget|pics|pictet|pictures|ping|pink|pizza|place|play|playstation"
|
||||
+ "|plumbing|plus|pohl|poker|porn|post|praxi|press|pro|prod|productions|prof|properties"
|
||||
+ "|property|protection|pub|p[aefghklmnrstwy])"
|
||||
+ "|(?:qpon|quebec|qa)"
|
||||
+ "|(?:racing|realtor|realty|recipes|red|redstone|rehab|reise|reisen|reit|ren|rent"
|
||||
+ "|rentals|repair|report|republican|rest|restaurant|review|reviews|rich|ricoh|rio|rip"
|
||||
+ "|rocher|rocks|rodeo|rsvp|ruhr|run|rwe|ryukyu|r[eosuw])"
|
||||
+ "|(?:saarland|sakura|sale|samsung|sandvik|sandvikcoromant|sanofi|sap|sapo|sarl|saxo"
|
||||
+ "|sbs|sca|scb|schmidt|scholarships|school|schule|schwarz|science|scor|scot|seat"
|
||||
+ "|security|seek|sener|services|seven|sew|sex|sexy|shiksha|shoes|show|shriram|singles"
|
||||
+ "|site|ski|sky|skype|sncf|soccer|social|software|sohu|solar|solutions|sony|soy|space"
|
||||
+ "|spiegel|spreadbetting|srl|stada|starhub|statoil|stc|stcgroup|stockholm|studio|study"
|
||||
+ "|style|sucks|supplies|supply|support|surf|surgery|suzuki|swatch|swiss|sydney|systems"
|
||||
+ "|s[abcdeghijklmnortuvxyz])"
|
||||
+ "|(?:tab|taipei|tatamotors|tatar|tattoo|tax|taxi|team|tech|technology|tel|telefonica"
|
||||
+ "|temasek|tennis|thd|theater|theatre|tickets|tienda|tips|tires|tirol|today|tokyo"
|
||||
+ "|tools|top|toray|toshiba|tours|town|toyota|toys|trade|trading|training|travel|trust"
|
||||
+ "|tui|t[cdfghjklmnortvwz])"
|
||||
+ "|(?:ubs|university|uno|uol|u[agksyz])"
|
||||
+ "|(?:vacations|vana|vegas|ventures|versicherung|vet|viajes|video|villas|vin|virgin"
|
||||
+ "|vision|vista|vistaprint|viva|vlaanderen|vodka|vote|voting|voto|voyage|v[aceginu])"
|
||||
+ "|(?:wales|walter|wang|watch|webcam|website|wed|wedding|weir|whoswho|wien|wiki"
|
||||
+ "|williamhill|win|windows|wine|wme|work|works|world|wtc|wtf|w[fs])"
|
||||
+ "|(?:\u03b5\u03bb|\u0431\u0435\u043b|\u0434\u0435\u0442\u0438|\u043a\u043e\u043c"
|
||||
+ "|\u043c\u043a\u0434|\u043c\u043e\u043d|\u043c\u043e\u0441\u043a\u0432\u0430"
|
||||
+ "|\u043e\u043d\u043b\u0430\u0439\u043d|\u043e\u0440\u0433|\u0440\u0443\u0441"
|
||||
+ "|\u0440\u0444|\u0441\u0430\u0439\u0442|\u0441\u0440\u0431|\u0443\u043a\u0440"
|
||||
+ "|\u049b\u0430\u0437|\u0570\u0561\u0575|\u05e7\u05d5\u05dd"
|
||||
+ "|\u0627\u0631\u0627\u0645\u0643\u0648|\u0627\u0644\u0627\u0631\u062f\u0646"
|
||||
+ "|\u0627\u0644\u062c\u0632\u0627\u0626\u0631"
|
||||
+ "|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629"
|
||||
+ "|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a"
|
||||
+ "|\u0627\u06cc\u0631\u0627\u0646|\u0628\u0627\u0632\u0627\u0631"
|
||||
+ "|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633"
|
||||
+ "|\u0633\u0648\u062f\u0627\u0646|\u0633\u0648\u0631\u064a\u0629"
|
||||
+ "|\u0634\u0628\u0643\u0629|\u0639\u0631\u0627\u0642|\u0639\u0645\u0627\u0646"
|
||||
+ "|\u0641\u0644\u0633\u0637\u064a\u0646|\u0642\u0637\u0631|\u0643\u0648\u0645"
|
||||
+ "|\u0645\u0635\u0631|\u0645\u0644\u064a\u0633\u064a\u0627|\u0645\u0648\u0642\u0639"
|
||||
+ "|\u0915\u0949\u092e|\u0928\u0947\u091f|\u092d\u093e\u0930\u0924"
|
||||
+ "|\u0938\u0902\u0917\u0920\u0928|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24"
|
||||
+ "|\u0aad\u0abe\u0ab0\u0aa4|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe"
|
||||
+ "|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8"
|
||||
+ "|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd"
|
||||
+ "|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e04\u0e2d\u0e21"
|
||||
+ "|\u0e44\u0e17\u0e22|\u10d2\u10d4|\u307f\u3093\u306a|\u30b0\u30fc\u30b0\u30eb"
|
||||
+ "|\u30b3\u30e0|\u4e16\u754c|\u4e2d\u4fe1|\u4e2d\u56fd|\u4e2d\u570b|\u4e2d\u6587\u7f51"
|
||||
+ "|\u4f01\u4e1a|\u4f5b\u5c71|\u4fe1\u606f|\u5065\u5eb7|\u516b\u5366|\u516c\u53f8"
|
||||
+ "|\u516c\u76ca|\u53f0\u6e7e|\u53f0\u7063|\u5546\u57ce|\u5546\u5e97|\u5546\u6807"
|
||||
+ "|\u5728\u7ebf|\u5927\u62ff|\u5a31\u4e50|\u5de5\u884c|\u5e7f\u4e1c|\u6148\u5584"
|
||||
+ "|\u6211\u7231\u4f60|\u624b\u673a|\u653f\u52a1|\u653f\u5e9c|\u65b0\u52a0\u5761"
|
||||
+ "|\u65b0\u95fb|\u65f6\u5c1a|\u673a\u6784|\u6de1\u9a6c\u9521|\u6e38\u620f|\u70b9\u770b"
|
||||
+ "|\u79fb\u52a8|\u7ec4\u7ec7\u673a\u6784|\u7f51\u5740|\u7f51\u5e97|\u7f51\u7edc"
|
||||
+ "|\u8c37\u6b4c|\u96c6\u56e2|\u98de\u5229\u6d66|\u9910\u5385|\u9999\u6e2f|\ub2f7\ub137"
|
||||
+ "|\ub2f7\ucef4|\uc0bc\uc131|\ud55c\uad6d|xbox|xerox|xin|xn\\-\\-11b4c3d"
|
||||
+ "|xn\\-\\-1qqw23a|xn\\-\\-30rr7y|xn\\-\\-3bst00m|xn\\-\\-3ds443g|xn\\-\\-3e0b707e"
|
||||
+ "|xn\\-\\-3pxu8k|xn\\-\\-42c2d9a|xn\\-\\-45brj9c|xn\\-\\-45q11c|xn\\-\\-4gbrim"
|
||||
+ "|xn\\-\\-55qw42g|xn\\-\\-55qx5d|xn\\-\\-6frz82g|xn\\-\\-6qq986b3xl|xn\\-\\-80adxhks"
|
||||
+ "|xn\\-\\-80ao21a|xn\\-\\-80asehdb|xn\\-\\-80aswg|xn\\-\\-90a3ac|xn\\-\\-90ais"
|
||||
+ "|xn\\-\\-9dbq2a|xn\\-\\-9et52u|xn\\-\\-b4w605ferd|xn\\-\\-c1avg|xn\\-\\-c2br7g"
|
||||
+ "|xn\\-\\-cg4bki|xn\\-\\-clchc0ea0b2g2a9gcd|xn\\-\\-czr694b|xn\\-\\-czrs0t"
|
||||
+ "|xn\\-\\-czru2d|xn\\-\\-d1acj3b|xn\\-\\-d1alf|xn\\-\\-efvy88h|xn\\-\\-estv75g"
|
||||
+ "|xn\\-\\-fhbei|xn\\-\\-fiq228c5hs|xn\\-\\-fiq64b|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s"
|
||||
+ "|xn\\-\\-fjq720a|xn\\-\\-flw351e|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-gecrj9c"
|
||||
+ "|xn\\-\\-h2brj9c|xn\\-\\-hxt814e|xn\\-\\-i1b6b1a6a2e|xn\\-\\-imr513n|xn\\-\\-io0a7i"
|
||||
+ "|xn\\-\\-j1aef|xn\\-\\-j1amh|xn\\-\\-j6w193g|xn\\-\\-kcrx77d1x4a|xn\\-\\-kprw13d"
|
||||
+ "|xn\\-\\-kpry57d|xn\\-\\-kput3i|xn\\-\\-l1acc|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgb9awbf"
|
||||
+ "|xn\\-\\-mgba3a3ejt|xn\\-\\-mgba3a4f16a|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbab2bd"
|
||||
+ "|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar"
|
||||
+ "|xn\\-\\-mgbpl2fh|xn\\-\\-mgbtx2b|xn\\-\\-mgbx4cd0ab|xn\\-\\-mk1bu44c|xn\\-\\-mxtq1m"
|
||||
+ "|xn\\-\\-ngbc5azd|xn\\-\\-node|xn\\-\\-nqv7f|xn\\-\\-nqv7fs00ema|xn\\-\\-nyqy26a"
|
||||
+ "|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1acf|xn\\-\\-p1ai|xn\\-\\-pgbs0dh"
|
||||
+ "|xn\\-\\-pssy2u|xn\\-\\-q9jyb4c|xn\\-\\-qcka1pmc|xn\\-\\-qxam|xn\\-\\-rhqv96g"
|
||||
+ "|xn\\-\\-s9brj9c|xn\\-\\-ses554g|xn\\-\\-t60b56a|xn\\-\\-tckwe|xn\\-\\-unup4y"
|
||||
+ "|xn\\-\\-vermgensberater\\-ctb|xn\\-\\-vermgensberatung\\-pwb|xn\\-\\-vhquv"
|
||||
+ "|xn\\-\\-vuq861b|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xhq521b|xn\\-\\-xkc2al3hye2a"
|
||||
+ "|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-y9a3aq|xn\\-\\-yfro4i67o|xn\\-\\-ygbi2ammx"
|
||||
+ "|xn\\-\\-zfr164b|xperia|xxx|xyz)"
|
||||
+ "|(?:yachts|yamaxun|yandex|yodobashi|yoga|yokohama|youtube|y[et])"
|
||||
+ "|(?:zara|zip|zone|zuerich|z[amw]))";
|
||||
|
||||
public static final Pattern IP_ADDRESS
|
||||
= Pattern.compile(
|
||||
|
|
@ -162,25 +179,25 @@ public final class PatternsCompat {
|
|||
/**
|
||||
* Valid UCS characters defined in RFC 3987. Excludes space characters.
|
||||
*/
|
||||
private static final String UCS_CHAR = "[" +
|
||||
"\u00A0-\uD7FF" +
|
||||
"\uF900-\uFDCF" +
|
||||
"\uFDF0-\uFFEF" +
|
||||
"\uD800\uDC00-\uD83F\uDFFD" +
|
||||
"\uD840\uDC00-\uD87F\uDFFD" +
|
||||
"\uD880\uDC00-\uD8BF\uDFFD" +
|
||||
"\uD8C0\uDC00-\uD8FF\uDFFD" +
|
||||
"\uD900\uDC00-\uD93F\uDFFD" +
|
||||
"\uD940\uDC00-\uD97F\uDFFD" +
|
||||
"\uD980\uDC00-\uD9BF\uDFFD" +
|
||||
"\uD9C0\uDC00-\uD9FF\uDFFD" +
|
||||
"\uDA00\uDC00-\uDA3F\uDFFD" +
|
||||
"\uDA40\uDC00-\uDA7F\uDFFD" +
|
||||
"\uDA80\uDC00-\uDABF\uDFFD" +
|
||||
"\uDAC0\uDC00-\uDAFF\uDFFD" +
|
||||
"\uDB00\uDC00-\uDB3F\uDFFD" +
|
||||
"\uDB44\uDC00-\uDB7F\uDFFD" +
|
||||
"&&[^\u00A0[\u2000-\u200A]\u2028\u2029\u202F\u3000]]";
|
||||
private static final String UCS_CHAR = "["
|
||||
+ "\u00A0-\uD7FF"
|
||||
+ "\uF900-\uFDCF"
|
||||
+ "\uFDF0-\uFFEF"
|
||||
+ "\uD800\uDC00-\uD83F\uDFFD"
|
||||
+ "\uD840\uDC00-\uD87F\uDFFD"
|
||||
+ "\uD880\uDC00-\uD8BF\uDFFD"
|
||||
+ "\uD8C0\uDC00-\uD8FF\uDFFD"
|
||||
+ "\uD900\uDC00-\uD93F\uDFFD"
|
||||
+ "\uD940\uDC00-\uD97F\uDFFD"
|
||||
+ "\uD980\uDC00-\uD9BF\uDFFD"
|
||||
+ "\uD9C0\uDC00-\uD9FF\uDFFD"
|
||||
+ "\uDA00\uDC00-\uDA3F\uDFFD"
|
||||
+ "\uDA40\uDC00-\uDA7F\uDFFD"
|
||||
+ "\uDA80\uDC00-\uDABF\uDFFD"
|
||||
+ "\uDAC0\uDC00-\uDAFF\uDFFD"
|
||||
+ "\uDB00\uDC00-\uDB3F\uDFFD"
|
||||
+ "\uDB44\uDC00-\uDB7F\uDFFD"
|
||||
+ "&&[^\u00A0[\u2000-\u200A]\u2028\u2029\u202F\u3000]]";
|
||||
|
||||
/**
|
||||
* Valid characters for IRI label defined in RFC 3987.
|
||||
|
|
@ -195,15 +212,15 @@ public final class PatternsCompat {
|
|||
/**
|
||||
* RFC 1035 Section 2.3.4 limits the labels to a maximum 63 octets.
|
||||
*/
|
||||
private static final String IRI_LABEL =
|
||||
"[" + LABEL_CHAR + "](?:[" + LABEL_CHAR + "_\\-]{0,61}[" + LABEL_CHAR + "]){0,1}";
|
||||
private static final String IRI_LABEL
|
||||
= "[" + LABEL_CHAR + "](?:[" + LABEL_CHAR + "_\\-]{0,61}[" + LABEL_CHAR + "]){0,1}";
|
||||
|
||||
/**
|
||||
* RFC 3492 references RFC 1034 and limits Punycode algorithm output to 63 characters.
|
||||
*/
|
||||
private static final String PUNYCODE_TLD = "xn\\-\\-[\\w\\-]{0,58}\\w";
|
||||
|
||||
private static final String TLD = "(" + PUNYCODE_TLD + "|" + "[" + TLD_CHAR + "]{2,63}" +")";
|
||||
private static final String TLD = "(" + PUNYCODE_TLD + "|" + "[" + TLD_CHAR + "]{2,63}" + ")";
|
||||
|
||||
private static final String HOST_NAME = "(" + IRI_LABEL + "\\.)+" + TLD;
|
||||
|
||||
|
|
@ -243,29 +260,29 @@ public final class PatternsCompat {
|
|||
+ ")");
|
||||
|
||||
/**
|
||||
* Regular expression that matches known TLDs and punycode TLDs
|
||||
* Regular expression that matches known TLDs and punycode TLDs.
|
||||
*/
|
||||
private static final String STRICT_TLD = "(?:" +
|
||||
IANA_TOP_LEVEL_DOMAINS + "|" + PUNYCODE_TLD + ")";
|
||||
private static final String STRICT_TLD = "(?:"
|
||||
+ IANA_TOP_LEVEL_DOMAINS + "|" + PUNYCODE_TLD + ")";
|
||||
|
||||
/**
|
||||
* Regular expression that matches host names using {@link #STRICT_TLD}
|
||||
* Regular expression that matches host names using {@link #STRICT_TLD}.
|
||||
*/
|
||||
private static final String STRICT_HOST_NAME = "(?:(?:" + IRI_LABEL + "\\.)+"
|
||||
+ STRICT_TLD + ")";
|
||||
|
||||
/**
|
||||
* Regular expression that matches domain names using either {@link #STRICT_HOST_NAME} or
|
||||
* {@link #IP_ADDRESS}
|
||||
* {@link #IP_ADDRESS}.
|
||||
*/
|
||||
private static final Pattern STRICT_DOMAIN_NAME
|
||||
= Pattern.compile("(?:" + STRICT_HOST_NAME + "|" + IP_ADDRESS + ")");
|
||||
|
||||
/**
|
||||
* Regular expression that matches domain names without a TLD
|
||||
* Regular expression that matches domain names without a TLD.
|
||||
*/
|
||||
private static final String RELAXED_DOMAIN_NAME =
|
||||
"(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" +"?)+" + "|" + IP_ADDRESS + ")";
|
||||
private static final String RELAXED_DOMAIN_NAME
|
||||
= "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" + "?)+" + "|" + IP_ADDRESS + ")";
|
||||
|
||||
/**
|
||||
* Regular expression to match strings that do not start with a supported protocol. The TLDs
|
||||
|
|
@ -321,15 +338,15 @@ public final class PatternsCompat {
|
|||
* Regular expression for local part of an email address. RFC5321 section 4.5.3.1.1 limits
|
||||
* the local part to be at most 64 octets.
|
||||
*/
|
||||
private static final String EMAIL_ADDRESS_LOCAL_PART =
|
||||
"[" + EMAIL_CHAR + "]" + "(?:[" + EMAIL_CHAR + "\\.]{0,62}[" + EMAIL_CHAR + "])?";
|
||||
private static final String EMAIL_ADDRESS_LOCAL_PART
|
||||
= "[" + EMAIL_CHAR + "]" + "(?:[" + EMAIL_CHAR + "\\.]{0,62}[" + EMAIL_CHAR + "])?";
|
||||
|
||||
/**
|
||||
* Regular expression for the domain part of an email address. RFC5321 section 4.5.3.1.2 limits
|
||||
* the domain to be at most 255 octets.
|
||||
*/
|
||||
private static final String EMAIL_ADDRESS_DOMAIN =
|
||||
"(?=.{1,255}(?:\\s|$|^))" + HOST_NAME;
|
||||
private static final String EMAIL_ADDRESS_DOMAIN
|
||||
= "(?=.{1,255}(?:\\s|$|^))" + HOST_NAME;
|
||||
|
||||
/**
|
||||
* Regular expression pattern to match email addresses. It excludes double quoted local parts
|
||||
|
|
@ -337,24 +354,24 @@ public final class PatternsCompat {
|
|||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP_PREFIX)
|
||||
public static final Pattern AUTOLINK_EMAIL_ADDRESS = Pattern.compile("(" + WORD_BOUNDARY +
|
||||
"(?:" + EMAIL_ADDRESS_LOCAL_PART + "@" + EMAIL_ADDRESS_DOMAIN + ")" +
|
||||
WORD_BOUNDARY + ")"
|
||||
public static final Pattern AUTOLINK_EMAIL_ADDRESS = Pattern.compile("(" + WORD_BOUNDARY
|
||||
+ "(?:" + EMAIL_ADDRESS_LOCAL_PART + "@" + EMAIL_ADDRESS_DOMAIN + ")"
|
||||
+ WORD_BOUNDARY + ")"
|
||||
);
|
||||
|
||||
public static final Pattern EMAIL_ADDRESS
|
||||
= Pattern.compile(
|
||||
"[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
|
||||
"\\@" +
|
||||
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
|
||||
"(" +
|
||||
"\\." +
|
||||
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
|
||||
")+"
|
||||
"[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}"
|
||||
+ "\\@"
|
||||
+ "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}"
|
||||
+ "("
|
||||
+ "\\."
|
||||
+ "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}"
|
||||
+ ")+"
|
||||
);
|
||||
|
||||
/**
|
||||
* Do not create this static utility class.
|
||||
*/
|
||||
private PatternsCompat() {}
|
||||
private PatternsCompat() { }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue