Skip to content

Commit

Permalink
Merge pull request #2067 from TeamNewPipe/release_v0.15.1
Browse files Browse the repository at this point in the history
release v0.15.1
  • Loading branch information
theScrabi authored Jan 29, 2019
2 parents c5d1271 + b4ef20e commit 568cf9c
Show file tree
Hide file tree
Showing 119 changed files with 2,966 additions and 1,721 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
</p>
<hr>
<p align="center"><a href="#screenshots">Screenshots</a> &bull; <a href="#description">Description</a> &bull; <a href="#features">Features</a> &bull; <a href="#contribution">Contribution</a> &bull; <a href="#donate">Donate</a> &bull; <a href="#license">License</a></p>
<p align="center"><a href="#screenshots">Screenshots</a> &bull; <a href="#description">Description</a> &bull; <a href="#features">Features</a> &bull; <a href="#updates">Updates</a> &bull; <a href="#contribution">Contribution</a> &bull; <a href="#donate">Donate</a> &bull; <a href="#license">License</a></p>
<p align="center"><a href="https://newpipe.schabi.org">Website</a> &bull; <a href="https://newpipe.schabi.org/blog/">Blog</a> &bull; <a href="https://newpipe.schabi.org/press/">Press</a></p>
<hr>

Expand Down Expand Up @@ -73,6 +73,20 @@ NewPipe does not use any Google framework libraries, nor the YouTube API. Websit
* Show comments
* … and many more

## Updates
When a change to the NewPipe code occurs (due to either adding features or bug fixing), eventually a release will occur. These are in the format x.xx.x . In order to get this new version, you can:
* Build a debug APK yourself. This is the fastest way to get new features on your device, but is much more complicated, so we recommend using one of the other methods.
* Download the APK from [releases](https://github.com/TeamNewPipe/NewPipe/releases) and install it.
* Update via F-droid. This is the slowest method of getting updates, as F-Droid must recognize changes, build the APK itself, sign it, then push the update to users.

When you install an APK from one of these options, it will be incompatible with an APK from one of the other options. This is due to different signing keys being used. Signing keys help ensure that a user isn't tricked into installing a malicious update to an app, and are independent. F-Droid and GitHub use different signing keys, and building an APK debug excludes a key. The signing key issue is being discussed in issue [#1981](https://github.com/TeamNewPipe/NewPipe/issues/1981), and may be fixed by setting up our own repository on F-Droid.

In the meanwhile, if you want to switch sources for some reason (e.g. NewPipe's core functionality was broken and F-Droid doesn't have the update yet), we recommend following this procedure:
1. Back up your data via "Settings>Content>Export Database" so you keep your history, subscriptions, and playlists
2. Uninstall NewPipe
3. Download the APK from the new source and install it
4. Import the data from step 1 via "Settings>Content>Import Database"

## Contribution
Whether you have ideas, translations, design changes, code cleaning, or real heavy code changes, help is always welcome.
The more is done the better it gets!
Expand Down
9 changes: 6 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,20 @@ android {
applicationId "org.schabi.newpipe"
minSdkVersion 19
targetSdkVersion 28
versionCode 70
versionName "0.15.0"
versionCode 71
versionName "0.15.1"

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}

buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}

debug {
multiDexEnabled true
debuggable true
Expand All @@ -33,6 +35,7 @@ android {
// but continue the build even when errors are found:
abortOnError false
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
Expand All @@ -54,7 +57,7 @@ dependencies {
exclude module: 'support-annotations'
})

implementation 'com.github.TeamNewPipe:NewPipeExtractor:99915e4527c0'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:f7c7b9df1a'

testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.23.0'
Expand Down
13 changes: 0 additions & 13 deletions app/src/androidTest/java/org/schabi/newpipe/ApplicationTest.java

This file was deleted.

1 change: 0 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@
<activity
android:name=".ReCaptchaActivity"
android:label="@string/reCaptchaActivity"/>
<activity android:name=".download.ExtSDDownloadFailedActivity" />

<provider
android:name="android.support.v4.content.FileProvider"
Expand Down
36 changes: 36 additions & 0 deletions app/src/main/java/org/schabi/newpipe/App.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.schabi.newpipe;

import android.annotation.TargetApi;
import android.app.Application;
import android.app.NotificationChannel;
import android.app.NotificationManager;
Expand Down Expand Up @@ -65,6 +66,7 @@
public class App extends Application {
protected static final String TAG = App.class.toString();
private RefWatcher refWatcher;
private static App app;

@SuppressWarnings("unchecked")
private static final Class<? extends ReportSenderFactory>[]
Expand All @@ -88,6 +90,8 @@ public void onCreate() {
}
refWatcher = installLeakCanary();

app = this;

// Initialize settings first because others inits can use its values
SettingsActivity.initSettings(this);

Expand All @@ -100,6 +104,9 @@ public void onCreate() {
ImageLoader.getInstance().init(getImageLoaderConfigurations(10, 50));

configureRxJavaErrorHandler();

// Check for new version
new CheckForNewAppVersionTask().execute();
}

protected Downloader getDownloader() {
Expand Down Expand Up @@ -211,6 +218,31 @@ public void initNotificationChannel() {
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.createNotificationChannel(mChannel);

setUpUpdateNotificationChannel(importance);
}

/**
* Set up notification channel for app update.
* @param importance
*/
@TargetApi(Build.VERSION_CODES.O)
private void setUpUpdateNotificationChannel(int importance) {

final String appUpdateId
= getString(R.string.app_update_notification_channel_id);
final CharSequence appUpdateName
= getString(R.string.app_update_notification_channel_name);
final String appUpdateDescription
= getString(R.string.app_update_notification_channel_description);

NotificationChannel appUpdateChannel
= new NotificationChannel(appUpdateId, appUpdateName, importance);
appUpdateChannel.setDescription(appUpdateDescription);

NotificationManager appUpdateNotificationManager
= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
}

@Nullable
Expand All @@ -226,4 +258,8 @@ protected RefWatcher installLeakCanary() {
protected boolean isDisposedRxExceptionsReported() {
return false;
}

public static App getApp() {
return app;
}
}
230 changes: 230 additions & 0 deletions app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
package org.schabi.newpipe;

import android.app.Application;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.net.Uri;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;

import org.json.JSONException;
import org.json.JSONObject;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
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.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

/**
* AsyncTask to check if there is a newer version of the NewPipe github apk available or not.
* If there is a newer version we show a notification, informing the user. On tapping
* the notification, the user will be directed to the download link.
*/
public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {

private static final Application app = App.getApp();
private static final String GITHUB_APK_SHA1 = "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
private static final String newPipeApiUrl = "https://newpipe.schabi.org/api/data.json";
private static final int timeoutPeriod = 30;

private SharedPreferences mPrefs;
private OkHttpClient client;

@Override
protected void onPreExecute() {

mPrefs = PreferenceManager.getDefaultSharedPreferences(app);

// Check if user has enabled/ disabled update checking
// and if the current apk is a github one or not.
if (!mPrefs.getBoolean(app.getString(R.string.update_app_key), true)
|| !isGithubApk()) {
this.cancel(true);
}
}

@Override
protected String doInBackground(Void... voids) {

// Make a network request to get latest NewPipe data.
if (client == null) {

client = new OkHttpClient
.Builder()
.readTimeout(timeoutPeriod, TimeUnit.SECONDS)
.build();
}

Request request = new Request.Builder()
.url(newPipeApiUrl)
.build();

try {
Response response = client.newCall(request).execute();
return response.body().string();
} catch (IOException ex) {
ErrorActivity.reportError(app, ex, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"app update API fail", R.string.app_ui_crash));
}

return null;
}

@Override
protected void onPostExecute(String response) {

// Parse the json from the response.
if (response != null) {

try {
JSONObject mainObject = new JSONObject(response);
JSONObject flavoursObject = mainObject.getJSONObject("flavors");
JSONObject githubObject = flavoursObject.getJSONObject("github");
JSONObject githubStableObject = githubObject.getJSONObject("stable");

String versionName = githubStableObject.getString("version");
String versionCode = githubStableObject.getString("version_code");
String apkLocationUrl = githubStableObject.getString("apk");

compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode);

} catch (JSONException ex) {
ErrorActivity.reportError(app, ex, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"could not parse app update JSON data", R.string.app_ui_crash));
}
}
}

/**
* Method to compare the current and latest available app version.
* If a newer version is available, we show the update notification.
* @param versionName
* @param apkLocationUrl
*/
private void compareAppVersionAndShowNotification(String versionName,
String apkLocationUrl,
String versionCode) {

int NOTIFICATION_ID = 2000;

if (BuildConfig.VERSION_CODE < Integer.valueOf(versionCode)) {

// A pending intent to open the apk location url in the browser.
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
PendingIntent pendingIntent
= PendingIntent.getActivity(app, 0, intent, 0);

NotificationCompat.Builder notificationBuilder = new NotificationCompat
.Builder(app, app.getString(R.string.app_update_notification_channel_id))
.setSmallIcon(R.drawable.ic_newpipe_update)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setContentTitle(app.getString(R.string.app_update_notification_content_title))
.setContentText(app.getString(R.string.app_update_notification_content_text)
+ " " + versionName);

NotificationManagerCompat notificationManager = NotificationManagerCompat.from(app);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
}

/**
* Method to get the apk's SHA1 key.
* https://stackoverflow.com/questions/9293019/get-certificate-fingerprint-from-android-app#22506133
*/
private static String getCertificateSHA1Fingerprint() {

PackageManager pm = app.getPackageManager();
String packageName = app.getPackageName();
int flags = PackageManager.GET_SIGNATURES;
PackageInfo packageInfo = null;

try {
packageInfo = pm.getPackageInfo(packageName, flags);
} catch (PackageManager.NameNotFoundException ex) {
ErrorActivity.reportError(app, ex, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not find package info", R.string.app_ui_crash));
}

Signature[] signatures = packageInfo.signatures;
byte[] cert = signatures[0].toByteArray();
InputStream input = new ByteArrayInputStream(cert);

CertificateFactory cf = null;
X509Certificate c = null;

try {
cf = CertificateFactory.getInstance("X509");
c = (X509Certificate) cf.generateCertificate(input);
} catch (CertificateException ex) {
ErrorActivity.reportError(app, ex, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Certificate error", R.string.app_ui_crash));
}

String hexString = null;

try {
MessageDigest md = MessageDigest.getInstance("SHA1");
byte[] publicKey = md.digest(c.getEncoded());
hexString = byte2HexFormatted(publicKey);
} catch (NoSuchAlgorithmException ex1) {
ErrorActivity.reportError(app, ex1, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not retrieve SHA1 key", R.string.app_ui_crash));
} catch (CertificateEncodingException ex2) {
ErrorActivity.reportError(app, ex2, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not retrieve SHA1 key", R.string.app_ui_crash));
}

return hexString;
}

private static String byte2HexFormatted(byte[] arr) {

StringBuilder str = new StringBuilder(arr.length * 2);

for (int i = 0; i < arr.length; i++) {
String h = Integer.toHexString(arr[i]);
int l = h.length();
if (l == 1) h = "0" + h;
if (l > 2) h = h.substring(l - 2, l);
str.append(h.toUpperCase());
if (i < (arr.length - 1)) str.append(':');
}
return str.toString();
}

public static boolean isGithubApk() {

return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1);
}
}
Loading

0 comments on commit 568cf9c

Please sign in to comment.