mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-11-04 09:13:00 +00:00 
			
		
		
		
	Move utility methods out of CheckForNewAppVersion
This commit is contained in:
		@@ -1,12 +1,9 @@
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
@@ -14,29 +11,17 @@ 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 org.schabi.newpipe.util.ReleaseVersionUtil;
 | 
			
		||||
 | 
			
		||||
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() {
 | 
			
		||||
@@ -45,122 +30,45 @@ public final class CheckForNewAppVersion extends IntentService {
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
    private static void compareAppVersionAndShowNotification(final String versionName,
 | 
			
		||||
                                                            final String apkLocationUrl,
 | 
			
		||||
                                                            final int versionCode) {
 | 
			
		||||
        if (BuildConfig.VERSION_CODE >= versionCode) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        final App app = App.getApp();
 | 
			
		||||
        // 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 PendingIntent pendingIntent = PendingIntent.getActivity(app, 0, intent, 0);
 | 
			
		||||
 | 
			
		||||
        final String channelId = application
 | 
			
		||||
                .getString(R.string.app_update_notification_channel_id);
 | 
			
		||||
        final String channelId = app.getString(R.string.app_update_notification_channel_id);
 | 
			
		||||
        final NotificationCompat.Builder notificationBuilder
 | 
			
		||||
                = new NotificationCompat.Builder(application, channelId)
 | 
			
		||||
                = new NotificationCompat.Builder(app, 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)
 | 
			
		||||
                .setContentTitle(app.getString(R.string.app_update_notification_content_title))
 | 
			
		||||
                .setContentText(app.getString(R.string.app_update_notification_content_text)
 | 
			
		||||
                        + " " + versionName);
 | 
			
		||||
 | 
			
		||||
        final NotificationManagerCompat notificationManager
 | 
			
		||||
                = NotificationManagerCompat.from(application);
 | 
			
		||||
                = NotificationManagerCompat.from(app);
 | 
			
		||||
        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();
 | 
			
		||||
 | 
			
		||||
@@ -168,7 +76,7 @@ public final class CheckForNewAppVersion extends IntentService {
 | 
			
		||||
        final NewVersionManager manager = new NewVersionManager();
 | 
			
		||||
 | 
			
		||||
        // Check if the current apk is a github one or not.
 | 
			
		||||
        if (!isReleaseApk(app)) {
 | 
			
		||||
        if (!ReleaseVersionUtil.isReleaseApk()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -181,13 +89,13 @@ public final class CheckForNewAppVersion extends IntentService {
 | 
			
		||||
 | 
			
		||||
        // Make a network request to get latest NewPipe data.
 | 
			
		||||
        final Response response = DownloaderImpl.getInstance().get(NEWPIPE_API_URL);
 | 
			
		||||
        handleResponse(response, manager, prefs, app);
 | 
			
		||||
        handleResponse(response, manager);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void handleResponse(@NonNull final Response response,
 | 
			
		||||
                                @NonNull final NewVersionManager manager,
 | 
			
		||||
                                @NonNull final SharedPreferences prefs,
 | 
			
		||||
                                @NonNull final App app) {
 | 
			
		||||
                                @NonNull final NewVersionManager manager) {
 | 
			
		||||
        final App app = App.getApp();
 | 
			
		||||
        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
 | 
			
		||||
        try {
 | 
			
		||||
            // Store a timestamp which needs to be exceeded,
 | 
			
		||||
            // before a new request to the API is made.
 | 
			
		||||
@@ -209,14 +117,11 @@ public final class CheckForNewAppVersion extends IntentService {
 | 
			
		||||
                    .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");
 | 
			
		||||
            final String versionName = githubStableObject.getString("version");
 | 
			
		||||
            final int versionCode = githubStableObject.getInt("version_code");
 | 
			
		||||
            final String apkLocationUrl = githubStableObject.getString("apk");
 | 
			
		||||
 | 
			
		||||
            compareAppVersionAndShowNotification(app, versionName,
 | 
			
		||||
            compareAppVersionAndShowNotification(versionName,
 | 
			
		||||
                    apkLocationUrl, versionCode);
 | 
			
		||||
        } catch (final JsonParserException e) {
 | 
			
		||||
            // Most likely something is wrong in data received from NEWPIPE_API_URL.
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
@@ -177,7 +176,7 @@ public class MainActivity extends AppCompatActivity {
 | 
			
		||||
            // Start the service 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();
 | 
			
		||||
            CheckForNewAppVersion.startNewVersionCheckService();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)));
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,96 @@
 | 
			
		||||
package org.schabi.newpipe.util;
 | 
			
		||||
 | 
			
		||||
import android.content.pm.PackageManager;
 | 
			
		||||
import android.content.pm.Signature;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.core.content.pm.PackageInfoCompat;
 | 
			
		||||
 | 
			
		||||
import org.schabi.newpipe.App;
 | 
			
		||||
import org.schabi.newpipe.error.ErrorInfo;
 | 
			
		||||
import org.schabi.newpipe.error.ErrorUtil;
 | 
			
		||||
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.util.List;
 | 
			
		||||
 | 
			
		||||
public class ReleaseVersionUtil {
 | 
			
		||||
    // 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";
 | 
			
		||||
 | 
			
		||||
    public static boolean isReleaseApk() {
 | 
			
		||||
        return getCertificateSHA1Fingerprint().equals(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
 | 
			
		||||
     */
 | 
			
		||||
    @NonNull
 | 
			
		||||
    private static String getCertificateSHA1Fingerprint() {
 | 
			
		||||
        final App app = App.getApp();
 | 
			
		||||
        final List<Signature> signatures;
 | 
			
		||||
        try {
 | 
			
		||||
            signatures = PackageInfoCompat.getSignatures(app.getPackageManager(),
 | 
			
		||||
                    app.getPackageName());
 | 
			
		||||
        } catch (final PackageManager.NameNotFoundException e) {
 | 
			
		||||
            ErrorUtil.createNotification(app, 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(app, 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(app, 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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user