1
0
mirror of https://github.com/TeamNewPipe/NewPipe.git synced 2024-11-22 02:53:09 +01:00

Merge branch 'dev' into bumpSomeLibraries

This commit is contained in:
litetex 2022-03-15 21:34:41 +01:00 committed by GitHub
commit 2e7503ff78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
91 changed files with 364 additions and 426 deletions

View File

@ -220,6 +220,7 @@ dependencies {
// https://developer.android.com/jetpack/androidx/releases/viewpager2#1.1.0-alpha01
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.webkit:webkit:1.4.0'
implementation 'androidx.work:work-runtime:2.7.1'
implementation 'com.google.android.material:material:1.5.0'
/** Third-party libraries **/

View File

@ -381,9 +381,6 @@
<service
android:name=".RouterActivity$FetcherService"
android:exported="false" />
<service
android:name=".CheckForNewAppVersion"
android:exported="false" />
<!-- opting out of sending metrics to Google in Android System WebView -->
<meta-data android:name="android.webkit.WebView.MetricsOptOut" android:value="true" />

View File

@ -1,264 +0,0 @@
package org.schabi.newpipe;
import android.app.Application;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.pm.PackageInfoCompat;
import androidx.preference.PreferenceManager;
import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonParser;
import com.grack.nanojson.JsonParserException;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.downloader.Response;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.List;
public final class CheckForNewAppVersion extends IntentService {
public CheckForNewAppVersion() {
super("CheckForNewAppVersion");
}
private static final boolean DEBUG = MainActivity.DEBUG;
private static final String TAG = CheckForNewAppVersion.class.getSimpleName();
// Public key of the certificate that is used in NewPipe release versions
private static final String RELEASE_CERT_PUBLIC_KEY_SHA1
= "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
private static final String NEWPIPE_API_URL = "https://newpipe.net/api/data.json";
/**
* Method to get the APK's SHA1 key. See https://stackoverflow.com/questions/9293019/#22506133.
*
* @param application The application
* @return String with the APK's SHA1 fingerprint in hexadecimal
*/
@NonNull
private static String getCertificateSHA1Fingerprint(@NonNull final Application application) {
final List<Signature> signatures;
try {
signatures = PackageInfoCompat.getSignatures(application.getPackageManager(),
application.getPackageName());
} catch (final PackageManager.NameNotFoundException e) {
ErrorUtil.createNotification(application, new ErrorInfo(e,
UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not find package info"));
return "";
}
if (signatures.isEmpty()) {
return "";
}
final X509Certificate c;
try {
final byte[] cert = signatures.get(0).toByteArray();
final InputStream input = new ByteArrayInputStream(cert);
final CertificateFactory cf = CertificateFactory.getInstance("X509");
c = (X509Certificate) cf.generateCertificate(input);
} catch (final CertificateException e) {
ErrorUtil.createNotification(application, new ErrorInfo(e,
UserAction.CHECK_FOR_NEW_APP_VERSION, "Certificate error"));
return "";
}
try {
final MessageDigest md = MessageDigest.getInstance("SHA1");
final byte[] publicKey = md.digest(c.getEncoded());
return byte2HexFormatted(publicKey);
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
ErrorUtil.createNotification(application, new ErrorInfo(e,
UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not retrieve SHA1 key"));
return "";
}
}
private static String byte2HexFormatted(final byte[] arr) {
final StringBuilder str = new StringBuilder(arr.length * 2);
for (int i = 0; i < arr.length; i++) {
String h = Integer.toHexString(arr[i]);
final int l = h.length();
if (l == 1) {
h = "0" + h;
}
if (l > 2) {
h = h.substring(l - 2, l);
}
str.append(h.toUpperCase());
if (i < (arr.length - 1)) {
str.append(':');
}
}
return str.toString();
}
/**
* Method to compare the current and latest available app version.
* If a newer version is available, we show the update notification.
*
* @param application The application
* @param versionName Name of new version
* @param apkLocationUrl Url with the new apk
* @param versionCode Code of new version
*/
private static void compareAppVersionAndShowNotification(@NonNull final Application application,
final String versionName,
final String apkLocationUrl,
final int versionCode) {
if (BuildConfig.VERSION_CODE >= versionCode) {
return;
}
// A pending intent to open the apk location url in the browser.
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final PendingIntent pendingIntent
= PendingIntent.getActivity(application, 0, intent, 0);
final String channelId = application
.getString(R.string.app_update_notification_channel_id);
final NotificationCompat.Builder notificationBuilder
= new NotificationCompat.Builder(application, channelId)
.setSmallIcon(R.drawable.ic_newpipe_update)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setContentTitle(application
.getString(R.string.app_update_notification_content_title))
.setContentText(application
.getString(R.string.app_update_notification_content_text)
+ " " + versionName);
final NotificationManagerCompat notificationManager
= NotificationManagerCompat.from(application);
notificationManager.notify(2000, notificationBuilder.build());
}
public static boolean isReleaseApk(@NonNull final App app) {
return getCertificateSHA1Fingerprint(app).equals(RELEASE_CERT_PUBLIC_KEY_SHA1);
}
private void checkNewVersion() throws IOException, ReCaptchaException {
final App app = App.getApp();
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
final NewVersionManager manager = new NewVersionManager();
// Check if the current apk is a github one or not.
if (!isReleaseApk(app)) {
return;
}
// Check if the last request has happened a certain time ago
// to reduce the number of API requests.
final long expiry = prefs.getLong(app.getString(R.string.update_expiry_key), 0);
if (!manager.isExpired(expiry)) {
return;
}
// Make a network request to get latest NewPipe data.
final Response response = DownloaderImpl.getInstance().get(NEWPIPE_API_URL);
handleResponse(response, manager, prefs, app);
}
private void handleResponse(@NonNull final Response response,
@NonNull final NewVersionManager manager,
@NonNull final SharedPreferences prefs,
@NonNull final App app) {
try {
// Store a timestamp which needs to be exceeded,
// before a new request to the API is made.
final long newExpiry = manager
.coerceExpiry(response.getHeader("expires"));
prefs.edit()
.putLong(app.getString(R.string.update_expiry_key), newExpiry)
.apply();
} catch (final Exception e) {
if (DEBUG) {
Log.w(TAG, "Could not extract and save new expiry date", e);
}
}
// Parse the json from the response.
try {
final JsonObject githubStableObject = JsonParser.object()
.from(response.responseBody()).getObject("flavors")
.getObject("github").getObject("stable");
final String versionName = githubStableObject
.getString("version");
final int versionCode = githubStableObject
.getInt("version_code");
final String apkLocationUrl = githubStableObject
.getString("apk");
compareAppVersionAndShowNotification(app, versionName,
apkLocationUrl, versionCode);
} catch (final JsonParserException e) {
// Most likely something is wrong in data received from NEWPIPE_API_URL.
// Do not alarm user and fail silently.
if (DEBUG) {
Log.w(TAG, "Could not get NewPipe API: invalid json", e);
}
}
}
/**
* Start a new service which
* checks if all conditions for performing a version check are met,
* fetches the API endpoint {@link #NEWPIPE_API_URL} containing info
* about the latest NewPipe version
* and displays a notification about ana available update.
* <br>
* Following conditions need to be met, before data is request from the server:
* <ul>
* <li> The app is signed with the correct signing key (by TeamNewPipe / schabi).
* If the signing key differs from the one used upstream, the update cannot be installed.</li>
* <li>The user enabled searching for and notifying about updates in the settings.</li>
* <li>The app did not recently check for updates.
* We do not want to make unnecessary connections and DOS our servers.</li>
* </ul>
* <b>Must not be executed</b> when the app is in background.
*/
public static void startNewVersionCheckService() {
final Intent intent = new Intent(App.getApp().getApplicationContext(),
CheckForNewAppVersion.class);
App.getApp().startService(intent);
}
@Override
protected void onHandleIntent(@Nullable final Intent intent) {
try {
checkNewVersion();
} catch (final IOException e) {
Log.w(TAG, "Could not fetch NewPipe API: probably network problem", e);
} catch (final ReCaptchaException e) {
Log.e(TAG, "ReCaptchaException should never happen here.", e);
}
}
}

View File

@ -20,7 +20,6 @@
package org.schabi.newpipe;
import static org.schabi.newpipe.CheckForNewAppVersion.startNewVersionCheckService;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.content.BroadcastReceiver;
@ -174,10 +173,9 @@ public class MainActivity extends AppCompatActivity {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
if (prefs.getBoolean(app.getString(R.string.update_app_key), true)) {
// Start the service which is checking all conditions
// Start the worker which is checking all conditions
// and eventually searching for a new version.
// The service searching for a new NewPipe version must not be started in background.
startNewVersionCheckService();
NewVersionWorker.enqueueNewVersionCheckingWork(app);
}
}

View File

@ -1,28 +0,0 @@
package org.schabi.newpipe
import java.time.Instant
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
class NewVersionManager {
fun isExpired(expiry: Long): Boolean {
return Instant.ofEpochSecond(expiry).isBefore(Instant.now())
}
/**
* Coerce expiry date time in between 6 hours and 72 hours from now
*
* @return Epoch second of expiry date time
*/
fun coerceExpiry(expiryString: String?): Long {
val now = ZonedDateTime.now()
return expiryString?.let {
var expiry = ZonedDateTime.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(expiryString))
expiry = maxOf(expiry, now.plusHours(6))
expiry = minOf(expiry, now.plusHours(72))
expiry.toEpochSecond()
} ?: now.plusHours(6).toEpochSecond()
}
}

View File

@ -0,0 +1,163 @@
package org.schabi.newpipe
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.preference.PreferenceManager
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import androidx.work.WorkRequest
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.grack.nanojson.JsonParser
import com.grack.nanojson.JsonParserException
import org.schabi.newpipe.extractor.downloader.Response
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
import org.schabi.newpipe.util.ReleaseVersionUtil.coerceUpdateCheckExpiry
import org.schabi.newpipe.util.ReleaseVersionUtil.isLastUpdateCheckExpired
import org.schabi.newpipe.util.ReleaseVersionUtil.isReleaseApk
import java.io.IOException
class NewVersionWorker(
context: Context,
workerParams: WorkerParameters
) : Worker(context, workerParams) {
/**
* Method to compare the current and latest available app version.
* If a newer version is available, we show the update notification.
*
* @param versionName Name of new version
* @param apkLocationUrl Url with the new apk
* @param versionCode Code of new version
*/
private fun compareAppVersionAndShowNotification(
versionName: String,
apkLocationUrl: String?,
versionCode: Int
) {
if (BuildConfig.VERSION_CODE >= versionCode) {
return
}
val app = App.getApp()
// A pending intent to open the apk location url in the browser.
val intent = Intent(Intent.ACTION_VIEW, apkLocationUrl?.toUri())
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val pendingIntent = PendingIntent.getActivity(app, 0, intent, 0)
val channelId = app.getString(R.string.app_update_notification_channel_id)
val notificationBuilder = NotificationCompat.Builder(app, channelId)
.setSmallIcon(R.drawable.ic_newpipe_update)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setContentTitle(app.getString(R.string.app_update_notification_content_title))
.setContentText(
app.getString(R.string.app_update_notification_content_text) +
" " + versionName
)
val notificationManager = NotificationManagerCompat.from(app)
notificationManager.notify(2000, notificationBuilder.build())
}
@Throws(IOException::class, ReCaptchaException::class)
private fun checkNewVersion() {
// Check if the current apk is a github one or not.
if (!isReleaseApk()) {
return
}
val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
// Check if the last request has happened a certain time ago
// to reduce the number of API requests.
val expiry = prefs.getLong(applicationContext.getString(R.string.update_expiry_key), 0)
if (!isLastUpdateCheckExpired(expiry)) {
return
}
// Make a network request to get latest NewPipe data.
val response = DownloaderImpl.getInstance().get(NEWPIPE_API_URL)
handleResponse(response)
}
private fun handleResponse(response: Response) {
val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
try {
// Store a timestamp which needs to be exceeded,
// before a new request to the API is made.
val newExpiry = coerceUpdateCheckExpiry(response.getHeader("expires"))
prefs.edit {
putLong(applicationContext.getString(R.string.update_expiry_key), newExpiry)
}
} catch (e: Exception) {
if (DEBUG) {
Log.w(TAG, "Could not extract and save new expiry date", e)
}
}
// Parse the json from the response.
try {
val githubStableObject = JsonParser.`object`()
.from(response.responseBody()).getObject("flavors")
.getObject("github").getObject("stable")
val versionName = githubStableObject.getString("version")
val versionCode = githubStableObject.getInt("version_code")
val apkLocationUrl = githubStableObject.getString("apk")
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode)
} catch (e: JsonParserException) {
// Most likely something is wrong in data received from NEWPIPE_API_URL.
// Do not alarm user and fail silently.
if (DEBUG) {
Log.w(TAG, "Could not get NewPipe API: invalid json", e)
}
}
}
override fun doWork(): Result {
try {
checkNewVersion()
} catch (e: IOException) {
Log.w(TAG, "Could not fetch NewPipe API: probably network problem", e)
return Result.failure()
} catch (e: ReCaptchaException) {
Log.e(TAG, "ReCaptchaException should never happen here.", e)
return Result.failure()
}
return Result.success()
}
companion object {
private val DEBUG = MainActivity.DEBUG
private val TAG = NewVersionWorker::class.java.simpleName
private const val NEWPIPE_API_URL = "https://newpipe.net/api/data.json"
/**
* Start a new worker which
* checks if all conditions for performing a version check are met,
* fetches the API endpoint [.NEWPIPE_API_URL] containing info
* about the latest NewPipe version
* and displays a notification about ana available update.
* <br></br>
* Following conditions need to be met, before data is request from the server:
*
* * The app is signed with the correct signing key (by TeamNewPipe / schabi).
* If the signing key differs from the one used upstream, the update cannot be installed.
* * The user enabled searching for and notifying about updates in the settings.
* * The app did not recently check for updates.
* We do not want to make unnecessary connections and DOS our servers.
*
*/
@JvmStatic
fun enqueueNewVersionCheckingWork(context: Context) {
val workRequest: WorkRequest =
OneTimeWorkRequest.Builder(NewVersionWorker::class.java).build()
WorkManager.getInstance(context).enqueue(workRequest)
}
}
}

View File

@ -350,7 +350,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
new AlertDialog.Builder(requireContext())
.setMessage(R.string.remove_watched_popup_warning)
.setTitle(R.string.remove_watched_popup_title)
.setPositiveButton(R.string.yes,
.setPositiveButton(R.string.ok,
(DialogInterface d, int id) -> removeWatchedStreams(false))
.setNeutralButton(
R.string.remove_watched_popup_yes_and_partially_watched_videos,

View File

@ -1,5 +1,9 @@
package org.schabi.newpipe.player;
import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@ -23,11 +27,9 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.PlaybackParameters;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.databinding.ActivityPlayerQueueControlBinding;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.local.dialog.PlaylistDialog;
import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
import org.schabi.newpipe.player.playqueue.PlayQueue;
@ -42,13 +44,6 @@ import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.List;
import java.util.stream.Collectors;
import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public final class PlayQueueActivity extends AppCompatActivity
implements PlayerEventListener, SeekBar.OnSeekBarChangeListener,
View.OnClickListener, PlaybackParameterDialog.Callback {
@ -129,7 +124,7 @@ public final class PlayQueueActivity extends AppCompatActivity
NavigationHelper.openSettings(this);
return true;
case R.id.action_append_playlist:
appendAllToPlaylist();
player.onAddToPlaylistClicked(getSupportFragmentManager());
return true;
case R.id.action_playback_speed:
openPlaybackParameterDialog();
@ -443,24 +438,6 @@ public final class PlayQueueActivity extends AppCompatActivity
seeking = false;
}
////////////////////////////////////////////////////////////////////////////
// Playlist append
////////////////////////////////////////////////////////////////////////////
private void appendAllToPlaylist() {
if (player != null && player.getPlayQueue() != null) {
openPlaylistAppendDialog(player.getPlayQueue().getStreams());
}
}
private void openPlaylistAppendDialog(final List<PlayQueueItem> playQueueItems) {
PlaylistDialog.createCorrespondingDialog(
getApplicationContext(),
playQueueItems.stream().map(StreamEntity::new).collect(Collectors.toList()),
dialog -> dialog.show(getSupportFragmentManager(), TAG)
);
}
////////////////////////////////////////////////////////////////////////////
// Binding Service Listener
////////////////////////////////////////////////////////////////////////////

View File

@ -105,6 +105,7 @@ import androidx.core.graphics.Insets;
import androidx.core.view.GestureDetectorCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.FragmentManager;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
@ -138,6 +139,7 @@ import com.squareup.picasso.Target;
import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.databinding.PlayerBinding;
import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding;
import org.schabi.newpipe.error.ErrorInfo;
@ -152,6 +154,7 @@ import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.info_list.StreamSegmentAdapter;
import org.schabi.newpipe.ktx.AnimationType;
import org.schabi.newpipe.local.dialog.PlaylistDialog;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.MainPlayer.PlayerType;
import org.schabi.newpipe.player.event.DisplayPortion;
@ -197,6 +200,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
@ -541,6 +545,7 @@ public final class Player implements
binding.segmentsButton.setOnClickListener(this);
binding.repeatButton.setOnClickListener(this);
binding.shuffleButton.setOnClickListener(this);
binding.addToPlaylistButton.setOnClickListener(this);
binding.playPauseButton.setOnClickListener(this);
binding.playPreviousButton.setOnClickListener(this);
@ -2389,6 +2394,32 @@ public final class Player implements
/*//////////////////////////////////////////////////////////////////////////
// Playlist append
//////////////////////////////////////////////////////////////////////////*/
//region Playlist append
public void onAddToPlaylistClicked(@NonNull final FragmentManager fragmentManager) {
if (DEBUG) {
Log.d(TAG, "onAddToPlaylistClicked() called");
}
if (getPlayQueue() != null) {
PlaylistDialog.createCorrespondingDialog(
getContext(),
getPlayQueue()
.getStreams()
.stream()
.map(StreamEntity::new)
.collect(Collectors.toList()),
dialog -> dialog.show(fragmentManager, TAG)
);
}
}
//endregion
/*//////////////////////////////////////////////////////////////////////////
// Mute / Unmute
//////////////////////////////////////////////////////////////////////////*/
@ -3131,6 +3162,7 @@ public final class Player implements
binding.itemsListHeaderDuration.setVisibility(View.VISIBLE);
binding.shuffleButton.setVisibility(View.VISIBLE);
binding.repeatButton.setVisibility(View.VISIBLE);
binding.addToPlaylistButton.setVisibility(View.VISIBLE);
hideControls(0, 0);
binding.itemsListPanel.requestFocus();
@ -3168,6 +3200,7 @@ public final class Player implements
binding.itemsListHeaderDuration.setVisibility(View.GONE);
binding.shuffleButton.setVisibility(View.GONE);
binding.repeatButton.setVisibility(View.GONE);
binding.addToPlaylistButton.setVisibility(View.GONE);
hideControls(0, 0);
binding.itemsListPanel.requestFocus();
@ -3196,6 +3229,7 @@ public final class Player implements
binding.shuffleButton.setVisibility(View.GONE);
binding.repeatButton.setVisibility(View.GONE);
binding.addToPlaylistButton.setVisibility(View.GONE);
binding.itemsListClose.setOnClickListener(view -> closeItemsList());
}
@ -3733,6 +3767,11 @@ public final class Player implements
} else if (v.getId() == binding.shuffleButton.getId()) {
onShuffleClicked();
return;
} else if (v.getId() == binding.addToPlaylistButton.getId()) {
if (getParentActivity() != null) {
onAddToPlaylistClicked(getParentActivity().getSupportFragmentManager());
}
return;
} else if (v.getId() == binding.moreOptionsButton.getId()) {
onMoreOptionsClicked();
} else if (v.getId() == binding.share.getId()) {
@ -3799,6 +3838,10 @@ public final class Player implements
case KeyEvent.KEYCODE_SPACE:
if (isFullscreen) {
playPause();
if (isPlaying()) {
hideControls(0, 0);
}
return true;
}
break;
case KeyEvent.KEYCODE_BACK:

View File

@ -88,6 +88,8 @@ public class PlayerMediaSession implements MediaSessionCallback {
@Override
public void play() {
player.play();
// hide the player controls even if the play command came from the media session
player.hideControls(0, 0);
}
@Override

View File

@ -7,10 +7,9 @@ import android.view.MenuItem;
import androidx.annotation.NonNull;
import org.schabi.newpipe.App;
import org.schabi.newpipe.CheckForNewAppVersion;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ReleaseVersionUtil;
public class MainSettingsFragment extends BasePreferenceFragment {
public static final boolean DEBUG = MainActivity.DEBUG;
@ -24,7 +23,7 @@ public class MainSettingsFragment extends BasePreferenceFragment {
setHasOptionsMenu(true); // Otherwise onCreateOptionsMenu is not called
// Check if the app is updatable
if (!CheckForNewAppVersion.isReleaseApk(App.getApp())) {
if (!ReleaseVersionUtil.isReleaseApk()) {
getPreferenceScreen().removePreference(
findPreference(getString(R.string.update_pref_screen_key)));

View File

@ -191,7 +191,7 @@ public class PeertubeInstanceListFragment extends Fragment {
.setTitle(R.string.restore_defaults)
.setMessage(R.string.restore_defaults_confirmation)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.yes, (dialog, which) -> {
.setPositiveButton(R.string.ok, (dialog, which) -> {
sharedPreferences.edit().remove(savedInstanceListKey).apply();
selectInstance(PeertubeInstance.defaultInstance);
updateInstanceList();

View File

@ -23,8 +23,6 @@ import androidx.preference.PreferenceFragmentCompat;
import com.jakewharton.rxbinding4.widget.RxTextView;
import org.schabi.newpipe.App;
import org.schabi.newpipe.CheckForNewAppVersion;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.SettingsLayoutBinding;
@ -37,6 +35,7 @@ import org.schabi.newpipe.settings.preferencesearch.PreferenceSearchResultListen
import org.schabi.newpipe.settings.preferencesearch.PreferenceSearcher;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.KeyboardUtil;
import org.schabi.newpipe.util.ReleaseVersionUtil;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView;
@ -267,7 +266,7 @@ public class SettingsActivity extends AppCompatActivity implements
*/
private void ensureSearchRepresentsApplicationState() {
// Check if the update settings are available
if (!CheckForNewAppVersion.isReleaseApk(App.getApp())) {
if (!ReleaseVersionUtil.isReleaseApk()) {
SettingsResourceRegistry.getInstance()
.getEntryByPreferencesResId(R.xml.update_settings)
.setSearchable(false);

View File

@ -1,12 +1,11 @@
package org.schabi.newpipe.settings;
import static org.schabi.newpipe.CheckForNewAppVersion.startNewVersionCheckService;
import android.os.Bundle;
import android.widget.Toast;
import androidx.preference.Preference;
import org.schabi.newpipe.NewVersionWorker;
import org.schabi.newpipe.R;
public class UpdateSettingsFragment extends BasePreferenceFragment {
@ -33,7 +32,7 @@ public class UpdateSettingsFragment extends BasePreferenceFragment {
// Reset the expire time. This is necessary to check for an update immediately.
defaultPreferences.edit()
.putLong(getString(R.string.update_expiry_key), 0).apply();
startNewVersionCheckService();
NewVersionWorker.enqueueNewVersionCheckingWork(getContext());
}
@Override

View File

@ -136,7 +136,7 @@ public class ChooseTabsFragment extends Fragment {
.setTitle(R.string.restore_defaults)
.setMessage(R.string.restore_defaults_confirmation)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.yes, (dialog, which) -> {
.setPositiveButton(R.string.ok, (dialog, which) -> {
tabsManager.resetTabs();
updateTabList();
selectedTabsAdapter.notifyDataSetChanged();

View File

@ -0,0 +1,116 @@
package org.schabi.newpipe.util
import android.content.pm.PackageManager
import android.content.pm.Signature
import androidx.core.content.pm.PackageInfoCompat
import org.schabi.newpipe.App
import org.schabi.newpipe.error.ErrorInfo
import org.schabi.newpipe.error.ErrorUtil.Companion.createNotification
import org.schabi.newpipe.error.UserAction
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.security.cert.CertificateEncodingException
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.time.Instant
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
object ReleaseVersionUtil {
// Public key of the certificate that is used in NewPipe release versions
private const val RELEASE_CERT_PUBLIC_KEY_SHA1 =
"B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15"
@JvmStatic
fun isReleaseApk(): Boolean {
return certificateSHA1Fingerprint == RELEASE_CERT_PUBLIC_KEY_SHA1
}
/**
* Method to get the APK's SHA1 key. See https://stackoverflow.com/questions/9293019/#22506133.
*
* @return String with the APK's SHA1 fingerprint in hexadecimal
*/
private val certificateSHA1Fingerprint: String
get() {
val app = App.getApp()
val signatures: List<Signature> = try {
PackageInfoCompat.getSignatures(app.packageManager, app.packageName)
} catch (e: PackageManager.NameNotFoundException) {
showRequestError(app, e, "Could not find package info")
return ""
}
if (signatures.isEmpty()) {
return ""
}
val x509cert = try {
val cert = signatures[0].toByteArray()
val input: InputStream = ByteArrayInputStream(cert)
val cf = CertificateFactory.getInstance("X509")
cf.generateCertificate(input) as X509Certificate
} catch (e: CertificateException) {
showRequestError(app, e, "Certificate error")
return ""
}
return try {
val md = MessageDigest.getInstance("SHA1")
val publicKey = md.digest(x509cert.encoded)
byte2HexFormatted(publicKey)
} catch (e: NoSuchAlgorithmException) {
showRequestError(app, e, "Could not retrieve SHA1 key")
""
} catch (e: CertificateEncodingException) {
showRequestError(app, e, "Could not retrieve SHA1 key")
""
}
}
private fun byte2HexFormatted(arr: ByteArray): String {
val str = StringBuilder(arr.size * 2)
for (i in arr.indices) {
var h = Integer.toHexString(arr[i].toInt())
val l = h.length
if (l == 1) {
h = "0$h"
}
if (l > 2) {
h = h.substring(l - 2, l)
}
str.append(h.uppercase())
if (i < arr.size - 1) {
str.append(':')
}
}
return str.toString()
}
private fun showRequestError(app: App, e: Exception, request: String) {
createNotification(
app, ErrorInfo(e, UserAction.CHECK_FOR_NEW_APP_VERSION, request)
)
}
fun isLastUpdateCheckExpired(expiry: Long): Boolean {
return Instant.ofEpochSecond(expiry).isBefore(Instant.now())
}
/**
* Coerce expiry date time in between 6 hours and 72 hours from now
*
* @return Epoch second of expiry date time
*/
fun coerceUpdateCheckExpiry(expiryString: String?): Long {
val now = ZonedDateTime.now()
return expiryString?.let {
var expiry =
ZonedDateTime.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(expiryString))
expiry = maxOf(expiry, now.plusHours(6))
expiry = minOf(expiry, now.plusHours(72))
expiry.toEpochSecond()
} ?: now.plusHours(6).toEpochSecond()
}
}

View File

@ -581,6 +581,21 @@
app:srcCompat="@drawable/ic_close"
app:tint="@color/white" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/addToPlaylistButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/itemsListClose"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="10dp"
android:scaleType="fitXY"
android:tint="?attr/colorAccent"
app:srcCompat="@drawable/ic_playlist_add"
tools:ignore="ContentDescription,RtlHardcoded" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/repeatButton"
android:layout_width="50dp"
@ -620,7 +635,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/itemsListClose"
android:layout_toStartOf="@id/addToPlaylistButton"
android:layout_toEndOf="@id/shuffleButton"
android:gravity="center"
android:textColor="@android:color/white" />

View File

@ -91,7 +91,6 @@
<string name="show_age_restricted_content_title">محتوى مقيد للبالغين</string>
<string name="duration_live">بث مباشر</string>
<string name="error_report_title">تقرير عن المشكلة</string>
<string name="yes">نعم</string>
<string name="disabled">متوقف</string>
<string name="clear">تنظيف</string>
<string name="best_resolution">أفضل دقة</string>

View File

@ -169,7 +169,6 @@
<string name="best_resolution">Ən yaxşı görüntü keyfiyyəti</string>
<string name="clear">Təmizlə</string>
<string name="disabled">Deaktiv edilib</string>
<string name="yes">Bəli</string>
<string name="artists">İfaçılar</string>
<string name="albums">Albomlar</string>
<string name="songs">Mahnılar</string>

View File

@ -44,7 +44,6 @@
<string name="detail_dislikes_img_view_description">Tarrezmes</string>
<string name="default_video_format_title">Formatu de videu predetermináu</string>
<string name="black_theme_title">Prietu</string>
<string name="yes"></string>
<string name="short_thousand">mil</string>
<string name="short_million">mill.</string>
<string name="short_billion">mil mill.</string>

View File

@ -112,7 +112,6 @@
<string name="best_resolution">Eng yaxshi qaror</string>
<string name="clear">Tozalash</string>
<string name="disabled">Ijrochilar o\'chirib qo\'yilgan</string>
<string name="yes">Ha</string>
<string name="artists">Artistlar</string>
<string name="albums">Albomlar</string>
<string name="songs">Qo\'shiqlar</string>

View File

@ -28,7 +28,6 @@
<string name="unsupported_url">不支持的 URL</string>
<string name="settings_category_appearance_title">外观</string>
<string name="all">全部</string>
<string name="yes"></string>
<string name="network_error">网络错误</string>
<plurals name="videos">
<item quantity="other">%s 个视频</item>

View File

@ -97,7 +97,6 @@
<string name="playlists">Плэйлісты</string>
<string name="tracks">Дарожкі</string>
<string name="users">Карыстальнікі</string>
<string name="yes">Так</string>
<string name="disabled">Адключана</string>
<string name="clear">Ачысціць</string>
<string name="best_resolution">Лепшае разрозненне</string>

View File

@ -56,7 +56,6 @@
<string name="file">ⴰⴼⴰⵢⵍⵓ</string>
<string name="play_all">ⵖⵔ ⵎⴰⵕⵕⴰ</string>
<string name="file_deleted">ⵉⵜⵜⵡⴰⴽⴽⵙ ⵓⴼⴰⵢⵍⵓ</string>
<string name="yes">ⵢⴰⵀ</string>
<string name="videos_string">ⵉⴼⵉⴷⵢⵓⵜⵏ</string>
<string name="all">ⵎⴰⵕⵕⴰ</string>
<string name="downloads_title">ⵓⴳⴳⴰⵎⵏ</string>

View File

@ -75,7 +75,6 @@
<string name="downloads_title">Изтегляния</string>
<string name="error_report_title">Съобщение за грешка</string>
<string name="all">Всички</string>
<string name="yes">Да</string>
<string name="disabled">Забранено</string>
<string name="clear">Изчисти</string>
<string name="best_resolution">Най-добра резолюция</string>

View File

@ -55,7 +55,6 @@
<string name="downloads_title">ডাউনলোডগুলি</string>
<string name="error_report_title">ত্রুটি প্রতিবেদন</string>
<string name="all">সবগুলি</string>
<string name="yes">হ্যাঁ</string>
<string name="disabled">নিস্ক্রীয়</string>
<string name="clear">পরিষ্কার</string>
<!-- error strings -->

View File

@ -53,7 +53,6 @@
<string name="always">সবসময়</string>
<string name="clear">পরিষ্কার</string>
<string name="disabled">নিস্ক্রীয়</string>
<string name="yes">হ্যাঁ</string>
<string name="all">সবগুলি</string>
<string name="error_report_title">ত্রুটি প্রতিবেদন</string>
<string name="downloads_title">ডাউনলোডগুলি</string>

View File

@ -176,7 +176,6 @@
<string name="best_resolution">সেরা রেজুলিউসন</string>
<string name="clear">পরিষ্কার</string>
<string name="disabled">নিস্ক্রীয়</string>
<string name="yes">হ্যাঁ</string>
<string name="artists">শিল্পীরা</string>
<string name="albums">অ্যালবাম গুলি</string>
<string name="songs">গান গুলি</string>

View File

@ -37,7 +37,6 @@
<string name="downloads">Baixades</string>
<string name="downloads_title">Baixades</string>
<string name="all">Tot</string>
<string name="yes"></string>
<string name="disabled">Desactivat</string>
<string name="clear">Neteja</string>
<string name="best_resolution">Millor resolució</string>

View File

@ -101,7 +101,6 @@
<string name="title_licenses">مۆڵەتنامەی لایەنی-سێیەم</string>
<string name="app_license_title">مۆڵەتنامەی نیوپایپ</string>
<string name="show_hold_to_append_title">پیشاندانی ڕێنمایی ”داگرتن تا پاشکۆ”</string>
<string name="yes">بەڵێ</string>
<string name="msg_threads">دابەشکراوەکان</string>
<string name="title_most_played">زۆرترین لێدراو</string>
<string name="unbookmark_playlist">لادانی نیشانه‌كراو</string>

View File

@ -84,7 +84,6 @@
<string name="no_available_dir">Určete prosím složku pro stahování později v nastavení</string>
<string name="info_labels">Co:\\nŽádost:\\nJazyk obsahu\\nZemě obsahu:\\nJazyk aplikace:\\nSlužba:\\nČas GMT:\\nBalíček:\\nVerze:\\nVerze OS:</string>
<string name="all">Vše</string>
<string name="yes">Ano</string>
<string name="short_thousand">tis.</string>
<string name="open_in_popup_mode">Otevřít ve vyskakovacím okně</string>
<string name="short_million">mil.</string>

View File

@ -108,7 +108,6 @@
</plurals>
<string name="tracks">Numre</string>
<string name="users">Brugere</string>
<string name="yes">Ja</string>
<string name="disabled">Slået fra</string>
<string name="clear">Slet</string>
<string name="best_resolution">Bedste opløsning</string>

View File

@ -88,7 +88,6 @@
<string name="black_theme_title">Schwarz</string>
<string name="title_activity_recaptcha">reCAPTCHA-Aufgabe</string>
<string name="recaptcha_request_toast">reCAPTCHA-Aufgabe angefordert</string>
<string name="yes">Ja</string>
<string name="all">Alle</string>
<string name="disabled">Deaktiviert</string>
<string name="open_in_popup_mode">Im Pop-up-Modus öffnen</string>

View File

@ -52,7 +52,6 @@
<string name="downloads">Λήψεις</string>
<string name="downloads_title">Λήψεις</string>
<string name="all">Όλα</string>
<string name="yes">Ναι</string>
<string name="general_error">Σφάλμα</string>
<string name="error_snackbar_action">Αναφορά</string>
<string name="what_device_headline">Πληροφορίες:</string>

View File

@ -95,7 +95,6 @@
<item quantity="one">%s filmeto</item>
<item quantity="other">%s filmetoj</item>
</plurals>
<string name="yes">Jes</string>
<string name="msg_popup_permission">Tiu permeso estas necesa por
\nmalfermi en ŝprucfenestra modo</string>
<string name="popup_playing_toast">Ludante en ŝprucfenestra modo</string>

View File

@ -82,7 +82,6 @@
<string name="info_labels">Lo sucedido:\\nPetición:\\nIdioma del contenido:\\nPaís del contenido:\\nIdioma de la aplicación:\\nServicio:\\nHora GMT:\\nPaquete:\\nVersión:\\nVersión del SO:</string>
<string name="black_theme_title">Negro</string>
<string name="all">Todo</string>
<string name="yes"></string>
<string name="short_thousand">k</string>
<string name="short_million">M</string>
<string name="short_billion">MM</string>

View File

@ -93,7 +93,6 @@
<string name="downloads_title">Allalaadimised</string>
<string name="error_report_title">Vea teatamine</string>
<string name="all">Kõik</string>
<string name="yes">Jah</string>
<string name="disabled">Keelatud</string>
<string name="clear">Kustuta</string>
<string name="best_resolution">Parim lahutus</string>

View File

@ -62,7 +62,6 @@
<string name="downloads_title">Deskargak</string>
<string name="error_report_title">Errore-txostena</string>
<string name="all">Dena</string>
<string name="yes">Bai</string>
<string name="disabled">Desgaituta</string>
<string name="clear">Garbitu</string>
<string name="best_resolution">Bereizmen onena</string>

View File

@ -109,7 +109,6 @@
<string name="channels">کانال‌ها</string>
<string name="playlists">سیاهه‌های پخش</string>
<string name="users">کاربران</string>
<string name="yes">بله</string>
<string name="disabled">غیرفعال</string>
<string name="clear">پاک‌کردن</string>
<string name="play_all">پخش همه</string>

View File

@ -73,7 +73,6 @@
<string name="downloads_title">Lataukset</string>
<string name="error_report_title">Virheraportti</string>
<string name="all">Kaikki</string>
<string name="yes">Kyllä</string>
<string name="disabled">Poistettu käytöstä</string>
<string name="clear">Pyyhi</string>
<string name="best_resolution">Paras resoluutio</string>

View File

@ -86,7 +86,6 @@
<string name="recaptcha_request_toast">Défi reCAPTCHA demandé</string>
<string name="open_in_popup_mode">Ouvrir en mode pop-up</string>
<string name="popup_playing_toast">Lecture en mode flottant</string>
<string name="yes">Oui</string>
<string name="disabled">Désactivés</string>
<string name="info_labels">Quoi:\\nRequest:\\nContent Language:\\nContent Country:\\nApp Language:\\nService:\\nGMT Time:\\nPackage:\\nVersion:\\nOS version:</string>
<string name="short_thousand">k</string>

View File

@ -96,7 +96,6 @@
<string name="playlists">Listas de reprodución</string>
<string name="tracks">Pistas</string>
<string name="users">Usuarios</string>
<string name="yes">Si</string>
<string name="disabled">Desactivado</string>
<string name="clear">Limpar</string>
<string name="best_resolution">Mellor resolución</string>

View File

@ -58,7 +58,6 @@
<string name="downloads_title">הורדות</string>
<string name="error_report_title">דוח שגיאה</string>
<string name="all">הכול</string>
<string name="yes">כן</string>
<string name="disabled">מושבת</string>
<string name="clear">ניקוי</string>
<string name="general_error">שגיאה</string>

View File

@ -90,7 +90,6 @@
<string name="downloads_title">डाउनलोड</string>
<string name="error_report_title">त्रुटी की रिपोर्ट</string>
<string name="all">सारे</string>
<string name="yes">सहमत हूँ</string>
<string name="disabled">बंद करे</string>
<string name="clear">साफ़</string>
<string name="best_resolution">बेहतर विडियो की क्वालिटी</string>

View File

@ -71,7 +71,6 @@
<string name="downloads_title">Preuzimanja</string>
<string name="error_report_title">Prijavi grešku</string>
<string name="all">Sve</string>
<string name="yes">Da</string>
<string name="disabled">Isključeno</string>
<string name="clear">Očisti</string>
<string name="best_resolution">Najbolja rezolucija</string>

View File

@ -111,7 +111,6 @@
<string name="settings_category_debug_title">Hibaelhárítás</string>
<string name="popup_playing_toast">Lejátszás felugró ablakban</string>
<string name="all">Összes</string>
<string name="yes">Igen</string>
<string name="disabled">Letiltva</string>
<string name="clear">Törlés</string>
<string name="best_resolution">Legjobb felbontás</string>

View File

@ -65,7 +65,6 @@
<string name="file_deleted">Ֆայլը ջնջվեց</string>
<string name="file">Ֆայլ</string>
<string name="songs">Երգեր</string>
<string name="yes">Այո</string>
<string name="enable_search_history_title">Որոնման պատմություն</string>
<string name="close">Փակել</string>
<plurals name="days">

View File

@ -85,7 +85,6 @@
<string name="tracks">Pistas</string>
<string name="users">Usatores</string>
<string name="events">Eventos</string>
<string name="yes">Si</string>
<string name="disabled">Disactivate</string>
<string name="clear">Vacuar</string>
<string name="best_resolution">Melior resolution</string>

View File

@ -86,7 +86,6 @@
<string name="short_thousand">r</string>
<string name="short_million">J</string>
<string name="short_billion">T</string>
<string name="yes">Ya</string>
<string name="open_in_popup_mode">Buka dalam mode popup</string>
<string name="msg_popup_permission">Izin ini dibutuhkan untuk
\nmembuka di mode sembul</string>

View File

@ -87,7 +87,6 @@
<string name="short_million">M</string>
<string name="short_billion">Mrd</string>
<string name="recaptcha_request_toast">È richiesta la risoluzione del reCAPTCHA</string>
<string name="yes"></string>
<string name="open_in_popup_mode">Apri in modalità popup</string>
<string name="popup_playing_toast">Riproduzione in modalità popup</string>
<string name="disabled">Disattivato</string>

View File

@ -87,7 +87,6 @@
<string name="short_thousand">k</string>
<string name="short_million">M</string>
<string name="short_billion">B</string>
<string name="yes">はい</string>
<string name="open_in_popup_mode">ポップアップモードで開く</string>
<string name="msg_popup_permission">ポップアップモードで開くには
\n権限の許可が必要です</string>

View File

@ -30,7 +30,6 @@
<string name="export_to">Sifeḍ ɣer</string>
<string name="controls_add_to_playlist_title">Rnu ɣer</string>
<string name="playback_step">Amecwaṛ</string>
<string name="yes">Ih</string>
<string name="msg_running">Azdam n NewPipe</string>
<string name="restore_defaults">Err-d imezwar</string>
<string name="channel_created_by">Yerna-t %s</string>

View File

@ -169,7 +169,6 @@
<string name="app_license">NewPipe nermalava kopîleft libre ye: Hûn dikarin li gorî kêfa xwe bikar bînin, parve bikin û baştir bikin. Bi taybetî hûn dikarin wê di bin mercên Lîsansa Giştî ya GNU ya Giştî ya ku ji hêla Weqfa Nermalava Azad ve hatî weşandin de, an guhertoya 3 ya Lîsansê, an jî (li gorî vebijarka we) guhertoyek paşîn ji nû ve belav bikin û / an biguherînin.</string>
<string name="clear">Zelal</string>
<string name="disabled">Bêmecel</string>
<string name="yes">Erê</string>
<string name="artists">Hunermend</string>
<string name="albums">Album</string>
<string name="songs">Stran</string>

View File

@ -102,7 +102,6 @@
<string name="popup_playing_toast">팝업 모드에서 재생 중</string>
<string name="error_report_title">오류 보고</string>
<string name="all">전부</string>
<string name="yes"></string>
<string name="disabled">해제됨</string>
<string name="clear">지우기</string>
<string name="best_resolution">최대 해상도</string>

View File

@ -86,7 +86,6 @@
<string name="downloads_title">دابەزاندنەکان</string>
<string name="error_report_title">ناتوانرێ سکاڵابکرێ</string>
<string name="all">گشتی</string>
<string name="yes">بەڵێ</string>
<string name="disabled">ناکارایە</string>
<string name="clear">پاککردنەوە</string>
<string name="best_resolution">باشترین قەبارە</string>

View File

@ -58,7 +58,6 @@
<string name="downloads_title">Atsisiuntimai</string>
<string name="error_report_title">Klaidų ataskaita</string>
<string name="all">Visi</string>
<string name="yes">Taip</string>
<string name="disabled">Išjungta</string>
<string name="clear">Išvalyti</string>
<string name="best_resolution">Geriausia raiška</string>

View File

@ -236,7 +236,6 @@
<string name="best_resolution">Labākā izšķirtspēja</string>
<string name="clear">Notīrīt</string>
<string name="disabled">Atspējots</string>
<string name="yes"></string>
<string name="artists">Mākslinieki</string>
<string name="albums">Albūmi</string>
<string name="songs">Dziesmas</string>

View File

@ -93,7 +93,6 @@
<string name="downloads_title">Превземања</string>
<string name="error_report_title">Извештај за грешки</string>
<string name="all">Сите</string>
<string name="yes">Да</string>
<string name="disabled">Оневозможено</string>
<string name="clear">Избриши</string>
<string name="best_resolution">Најдобра резолуција</string>

View File

@ -270,7 +270,6 @@
<string name="best_resolution">മികച്ച റിസല്യൂഷൻ</string>
<string name="clear">തെളിക്കുക</string>
<string name="disabled">അസാധുവാക്കപ്പെട്ടു</string>
<string name="yes">അതെ</string>
<string name="artists">കലാകാരന്മാർ</string>
<string name="albums">ആൽബങ്ങൾ</string>
<string name="songs">പാട്ടുകൾ</string>

View File

@ -105,7 +105,6 @@
<string name="tracks">Trek</string>
<string name="users">Pengguna</string>
<string name="events">Peristiwa</string>
<string name="yes">Ya</string>
<string name="disabled">Dinyahdayakan</string>
<string name="clear">Bersihkan</string>
<string name="best_resolution">Resolusi terbaik</string>

View File

@ -89,7 +89,6 @@
<string name="black_theme_title">Svart</string>
<string name="popup_playing_toast">Spiller av i oppsprettsmodus</string>
<string name="all">Alle</string>
<string name="yes">Ja</string>
<string name="disabled">Avskrudd</string>
<string name="short_thousand">k</string>
<string name="short_million">M</string>

View File

@ -111,7 +111,6 @@
<string name="tracks">ट्रयाकहरु</string>
<string name="users">प्रयोगकर्ताहरु</string>
<string name="events">घटनाहरू</string>
<string name="yes">हो</string>
<string name="disabled">अक्षम</string>
<string name="clear">स्पष्ट</string>
<string name="best_resolution">सर्वश्रेष्ठ रेसोलुशन</string>

View File

@ -93,7 +93,6 @@
<string name="downloads_title">Downloads</string>
<string name="error_report_title">Foutrapport</string>
<string name="all">Alles</string>
<string name="yes">Ja</string>
<string name="disabled">Uitgeschakeld</string>
<string name="clear">Wissen</string>
<string name="best_resolution">Beste resolutie</string>

View File

@ -85,7 +85,6 @@
<string name="recaptcha_request_toast">reCAPTCHA-uitdaging gevraagd</string>
<string name="open_in_popup_mode">Openen in pop-upmodus</string>
<string name="all">Alles</string>
<string name="yes">Ja</string>
<string name="short_thousand">k</string>
<string name="short_million">M</string>
<string name="short_billion">B</string>

View File

@ -92,7 +92,6 @@
<string name="downloads_title">ਡਾਊਨਲੋਡਸ</string>
<string name="error_report_title">Error ਰਿਪੋਰਟ</string>
<string name="all">ਸਾਰੇ</string>
<string name="yes">ਹਾਂ</string>
<string name="disabled">ਬੰਦ ਕੀਤਾ</string>
<string name="clear">ਮਿਟਾਓ</string>
<string name="best_resolution">ਵਧੀਆ Resolution</string>

View File

@ -95,7 +95,6 @@
<string name="show_search_suggestions_summary">Wybierz podpowiedzi, które będą wyświetlane podczas wyszukiwania</string>
<string name="popup_playing_toast">Odtwarzanie w trybie okienkowym</string>
<string name="all">Wszystkie</string>
<string name="yes">Tak</string>
<string name="disabled">Wyłączone</string>
<string name="clear">Wyczyść</string>
<string name="short_thousand">tys.</string>

View File

@ -90,7 +90,6 @@
<string name="default_video_format_title">Formato de vídeo padrão</string>
<string name="popup_playing_toast">Reproduzindo em modo popup</string>
<string name="all">Tudo</string>
<string name="yes">Sim</string>
<string name="disabled">Desativado</string>
<string name="short_thousand">k</string>
<string name="short_million">M</string>

View File

@ -395,7 +395,6 @@
<string name="max_retry_msg">Tentativas máximas</string>
<string name="title_activity_history">Histórico</string>
<string name="playback_pitch">Velocidade</string>
<string name="yes">Sim</string>
<string name="error_download_resource_gone">Não é possível recuperar esta descarga</string>
<string name="rename_playlist">Mudar nome</string>
<string name="feed_group_dialog_empty_selection">Nenhuma subscrição selecionada</string>

View File

@ -83,7 +83,6 @@
<string name="open_in_popup_mode">Abrir no modo popup</string>
<string name="black_theme_title">Preto</string>
<string name="all">Tudo</string>
<string name="yes">Sim</string>
<string name="short_thousand">k</string>
<string name="short_million">M</string>
<string name="short_billion">MM</string>

View File

@ -90,7 +90,6 @@
<string name="black_theme_title">Negru</string>
<string name="popup_playing_toast">Redare în mod pop-up</string>
<string name="all">Toate</string>
<string name="yes">Da</string>
<string name="disabled">Dezactivat</string>
<string name="app_ui_crash">Aplicația/UI s-a oprit</string>
<string name="info_labels">Ce:\\nSolicitare:\\nLimba conținutului:\\nȚara conținutului:\\nLimba aplicației:\\nServiciu:\\nOra GMT:\\nPachet:\\nVersiune: \\nVersiune SO:</string>

View File

@ -86,7 +86,6 @@
<string name="black_theme_title">Чёрная</string>
<string name="popup_remember_size_pos_title">Помнить параметры окна</string>
<string name="popup_playing_toast">Воспроизведение во всплывающем окне</string>
<string name="yes">Да</string>
<string name="clear">Очистить</string>
<string name="all">Всё</string>
<string name="info_labels">Что:\\nЗапрос:\\nЯзык контента:\\nСтрана контента:\\nЯзык приложения:\\nСервис:\\nВремя по Гринвичу:\\nПакет:\\nВерсия пакета:\\nВерсия ОС:</string>

View File

@ -415,7 +415,6 @@
<string name="best_resolution">Risolutzione mègius</string>
<string name="clear">Isbòida</string>
<string name="disabled">Disabilitadu</string>
<string name="yes">Eja</string>
<string name="artists">Artista</string>
<string name="albums">Albums</string>
<string name="songs">Cantzones</string>

View File

@ -87,7 +87,6 @@
<string name="short_million">M</string>
<string name="short_billion">B</string>
<string name="recaptcha_request_toast">Požiadavka reCAPTCHA</string>
<string name="yes">Áno</string>
<string name="open_in_popup_mode">Spustiť v okne</string>
<string name="msg_popup_permission">Tieto práva sú potrebné pre
\nprehrávanie v mini okne</string>

View File

@ -87,7 +87,6 @@
<string name="short_thousand">k</string>
<string name="short_million">mio</string>
<string name="short_billion">mrd</string>
<string name="yes">Da</string>
<string name="open_in_popup_mode">Odpri v pojavnem načinu</string>
<string name="msg_popup_permission">To dovoljenje je potrebno za odpiranje
\nv pojavnem načinu</string>

View File

@ -319,7 +319,6 @@
<string name="duration_live">Toos</string>
<string name="undo">Soo celi</string>
<string name="disabled">Xidhan</string>
<string name="yes">Haa</string>
<string name="users">Isticmaale</string>
<string name="videos_string">Muuqaalo</string>
<string name="all">Dhammaan</string>

View File

@ -34,7 +34,6 @@
<string name="downloads_title">Shkarkimet</string>
<string name="error_report_title">Raporti i gabimit</string>
<string name="all">Të gjitha</string>
<string name="yes">Po</string>
<string name="missions_header_pending">Në pritje</string>
<plurals name="feed_group_dialog_selection_count">
<item quantity="one">%d i zgjedhur</item>

View File

@ -87,7 +87,6 @@
<string name="short_thousand">хиљ</string>
<string name="short_million">мил</string>
<string name="short_billion">млрд</string>
<string name="yes">Да</string>
<string name="open_in_popup_mode">Отвори у искачућем режиму</string>
<string name="msg_popup_permission">Ова дозвола је потребна за
\nотварање у искачућем режиму</string>

View File

@ -58,7 +58,6 @@
<string name="downloads_title">Hämtningar</string>
<string name="error_report_title">Felrapport</string>
<string name="all">Alla</string>
<string name="yes">Ja</string>
<string name="disabled">Inaktiverad</string>
<string name="clear">Rensa</string>
<string name="best_resolution">Bästa upplösningen</string>

View File

@ -73,7 +73,6 @@
<string name="all">அனைத்தும்</string>
<string name="playlists">ஒளிச்சரங்கள்</string>
<string name="users">பயனர்கள்</string>
<string name="yes">ஆம்</string>
<string name="clear">அழி</string>
<string name="always">எப்பொழுதும்</string>
<string name="just_once">ஒரு முறை</string>

View File

@ -52,7 +52,6 @@
<string name="downloads_title">డౌన్ లోడ్</string>
<string name="error_report_title">లోపం నివేదిక</string>
<string name="all">అన్ని</string>
<string name="yes">అవును</string>
<string name="play_all">అన్నింటినీ ప్లే చేయండి</string>
<string name="notification_channel_name">న్యూప్యాప్ నోటిఫికేషన్</string>
<string name="general_error">లోపం</string>

View File

@ -104,7 +104,6 @@
<string name="tracks">แทร็ค</string>
<string name="users">ผู้ใช้</string>
<string name="events">เหตุการณ์</string>
<string name="yes">ใช่</string>
<string name="disabled">ปิดการใช้งาน</string>
<string name="clear">ล้าง</string>
<string name="best_resolution">ความละเอียดที่ดีที่สุด</string>

View File

@ -86,7 +86,6 @@
<string name="black_theme_title">Siyah</string>
<string name="popup_playing_toast">ılır pencere kipinde oynatılıyor</string>
<string name="all">Tümü</string>
<string name="yes">Evet</string>
<string name="disabled">Devre dışı</string>
<string name="your_comment">Yorumunuz (İngilizce):</string>
<string name="error_details_headline">Ayrıntılar:</string>

View File

@ -123,7 +123,6 @@
<string name="file">Afaylu</string>
<string name="always">Ku dwal</string>
<string name="clear">Sfeḍ</string>
<string name="yes">Yah</string>
<string name="artists">Inaẓuṛen</string>
<string name="songs">Tiɣennijin</string>
<string name="events">Imezza</string>

View File

@ -58,7 +58,6 @@
<string name="downloads_title">Завантаження</string>
<string name="error_report_title">Звіт про помилку</string>
<string name="all">Усе</string>
<string name="yes">Так</string>
<string name="disabled">Вимкнено</string>
<string name="app_ui_crash">Збій застосунку/інтерфейсу</string>
<string name="your_comment">Ваш коментар (англійською):</string>

View File

@ -92,7 +92,6 @@
<string name="downloads_title">ڈاؤن لوڈز</string>
<string name="error_report_title">خرابی کی اطلاع</string>
<string name="all">تمام</string>
<string name="yes">ہاں</string>
<string name="disabled">غیر فعال</string>
<string name="clear">صاف</string>
<string name="best_resolution">بہترین ریزولوشن</string>

View File

@ -56,7 +56,6 @@
<string name="downloads_title">Tải xuống</string>
<string name="error_report_title">Báo lỗi</string>
<string name="all">Tất cả</string>
<string name="yes"></string>
<string name="disabled">Vô hiệu</string>
<string name="clear">Xóa</string>
<string name="best_resolution">Độ phân giải tốt nhất</string>

View File

@ -28,7 +28,6 @@
<string name="unsupported_url">不支持的 URL</string>
<string name="settings_category_appearance_title">外观</string>
<string name="all">全部</string>
<string name="yes"></string>
<string name="network_error">网络错误</string>
<plurals name="videos">
<item quantity="other">%s 个视频</item>

View File

@ -86,7 +86,6 @@
<string name="black_theme_title">純黑</string>
<string name="popup_playing_toast">以畫中畫模式播放</string>
<string name="all">所有</string>
<string name="yes"></string>
<string name="app_ui_crash">App/界面閃退</string>
<string name="info_labels">經過:\\n請求\\n內容語言\\n內容國家\\nApp 語言:\\n服務\\nGMT 時間:\\n封裝\\n版本\\nOS 版本:</string>
<string name="short_thousand"></string>

View File

@ -61,7 +61,6 @@
<string name="downloads_title">下載</string>
<string name="error_report_title">錯誤回報</string>
<string name="all">全部</string>
<string name="yes">是的</string>
<string name="disabled">已停用</string>
<string name="clear">清除</string>
<string name="best_resolution">最佳解析度</string>

View File

@ -168,7 +168,6 @@
<string name="songs">Songs</string>
<string name="albums">Albums</string>
<string name="artists">Artists</string>
<string name="yes">Yes</string>
<string name="disabled">Disabled</string>
<string name="clear">Clear</string>
<string name="best_resolution">Best resolution</string>

View File

@ -2,8 +2,9 @@ package org.schabi.newpipe
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.schabi.newpipe.util.ReleaseVersionUtil.coerceUpdateCheckExpiry
import org.schabi.newpipe.util.ReleaseVersionUtil.isLastUpdateCheckExpired
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
@ -11,18 +12,11 @@ import kotlin.math.abs
class NewVersionManagerTest {
private lateinit var manager: NewVersionManager
@Before
fun setup() {
manager = NewVersionManager()
}
@Test
fun `Expiry is reached`() {
val oneHourEarlier = Instant.now().atZone(ZoneId.of("GMT")).minusHours(1)
val expired = manager.isExpired(oneHourEarlier.toEpochSecond())
val expired = isLastUpdateCheckExpired(oneHourEarlier.toEpochSecond())
assertTrue(expired)
}
@ -31,7 +25,7 @@ class NewVersionManagerTest {
fun `Expiry is not reached`() {
val oneHourLater = Instant.now().atZone(ZoneId.of("GMT")).plusHours(1)
val expired = manager.isExpired(oneHourLater.toEpochSecond())
val expired = isLastUpdateCheckExpired(oneHourLater.toEpochSecond())
assertFalse(expired)
}
@ -47,7 +41,7 @@ class NewVersionManagerTest {
fun `Expiry must be returned as is because it is inside the acceptable range of 6-72 hours`() {
val sixHoursLater = Instant.now().atZone(ZoneId.of("GMT")).plusHours(6)
val coerced = manager.coerceExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(sixHoursLater))
val coerced = coerceUpdateCheckExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(sixHoursLater))
assertNearlyEqual(sixHoursLater.toEpochSecond(), coerced)
}
@ -56,7 +50,7 @@ class NewVersionManagerTest {
fun `Expiry must be increased to 6 hours if below`() {
val tooLow = Instant.now().atZone(ZoneId.of("GMT")).plusHours(5)
val coerced = manager.coerceExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(tooLow))
val coerced = coerceUpdateCheckExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(tooLow))
assertNearlyEqual(tooLow.plusHours(1).toEpochSecond(), coerced)
}
@ -65,7 +59,7 @@ class NewVersionManagerTest {
fun `Expiry must be decreased to 72 hours if above`() {
val tooHigh = Instant.now().atZone(ZoneId.of("GMT")).plusHours(73)
val coerced = manager.coerceExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(tooHigh))
val coerced = coerceUpdateCheckExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(tooHigh))
assertNearlyEqual(tooHigh.minusHours(1).toEpochSecond(), coerced)
}