diff --git a/.gitignore b/.gitignore index 42caadb93..2b3c40d66 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ /app/app.iml /.idea /*.iml +gradle.properties diff --git a/app/build.gradle b/app/build.gradle index 871a98a9d..9eb3ccef7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "org.schabi.newpipe" minSdkVersion 15 targetSdkVersion 23 - versionCode 9 - versionName "0.7.0" + versionCode 10 + versionName "0.7.1" } buildTypes { release { @@ -17,7 +17,7 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - + lintOptions { checkReleaseBuilds false // Or, if you prefer, you can continue to check for errors in release builds, @@ -35,4 +35,5 @@ dependencies { compile 'com.android.support:recyclerview-v7:23.1.1' compile 'org.jsoup:jsoup:1.8.3' compile 'org.mozilla:rhino:1.7.7' + compile 'info.guardianproject.netcipher:netcipher:1.2' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 05eda83c5..ef84c57b3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ + - + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + android:label="@string/background_player_name" + android:exported="false" /> + + + + + + + diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java new file mode 100644 index 000000000..877000e61 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -0,0 +1,46 @@ +package org.schabi.newpipe; + +import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import info.guardianproject.netcipher.NetCipher; +import info.guardianproject.netcipher.proxy.OrbotHelper; + +public class App extends Application { + + private static boolean useTor; + + @Override + public void onCreate() { + super.onCreate(); + // if Orbot is installed, then default to using Tor, the user can still override + if (OrbotHelper.requestStartTor(this)) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + configureTor(prefs.getBoolean(getString(R.string.useTor), true)); + } + } + + /** + * Set the proxy settings based on whether Tor should be enabled or not. + */ + static void configureTor(boolean enabled) { + useTor = enabled; + if (useTor) { + NetCipher.useTor(); + } else { + NetCipher.setProxy(null); + } + } + + static void checkStartTor(Context context) { + if (useTor) { + OrbotHelper.requestStartTor(context); + } + } + + static boolean isUsingTor() { + return useTor; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/BackgroundPlayer.java index 88e5fad9f..58823dcb2 100644 --- a/app/src/main/java/org/schabi/newpipe/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/BackgroundPlayer.java @@ -17,6 +17,7 @@ import android.os.IBinder; import android.os.PowerManager; import android.support.v7.app.NotificationCompat; import android.util.Log; +import android.widget.RemoteViews; import android.widget.Toast; import java.io.IOException; @@ -113,9 +114,9 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare private int noteID = TAG.hashCode(); private BackgroundPlayer owner; private NotificationManager noteMgr; - private NotificationCompat.Builder noteBuilder; private WifiManager.WifiLock wifiLock; private Bitmap videoThumbnail = null; + private NotificationCompat.Builder noteBuilder; public PlayerThread(String src, String title, BackgroundPlayer owner) { this.source = src; @@ -124,10 +125,9 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare mediaPlayer = new MediaPlayer(); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); } + @Override public void run() { - Resources res = getApplicationContext().getResources(); - mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);//cpu lock try { mediaPlayer.setDataSource(source); @@ -174,54 +174,7 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare filter.addAction(ACTION_STOP); registerReceiver(broadcastReceiver, filter); - PendingIntent playPI = PendingIntent.getBroadcast(owner, noteID, - new Intent(ACTION_PLAYPAUSE), PendingIntent.FLAG_UPDATE_CURRENT); - - NotificationCompat.Action playButton = new NotificationCompat.Action.Builder - (R.drawable.ic_play_arrow_white_48dp, "Play", playPI).build(); - - /* - NotificationCompat.Action pauseButton = new NotificationCompat.Action.Builder - (R.drawable.ic_pause_white_24dp, "Pause", playPI).build(); - */ - - PendingIntent stopPI = PendingIntent.getBroadcast(owner, noteID, - new Intent(ACTION_STOP), PendingIntent.FLAG_UPDATE_CURRENT); - - noteBuilder = new NotificationCompat.Builder(owner); - noteBuilder - .setContentTitle(title) - //really? Id like to put something more helpful here. - //.setContentText("NewPipe is playing in the background") - .setContentText(channelName) - //.setAutoCancel(!mediaPlayer.isPlaying()) - .setOngoing(true) - .setDeleteIntent(stopPI) - //doesn't fit with Notification.MediaStyle - //.setProgress(vidLength, 0, false) - .setSmallIcon(R.drawable.ic_play_circle_filled_white_24dp) - .setLargeIcon(videoThumbnail) - .setTicker( - String.format(res.getString( - R.string.backgroundPlayerTickerText), title)) - .addAction(playButton); - //.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - //.setLargeIcon(cover) - if(android.os.Build.VERSION.SDK_INT >= 16) - noteBuilder.setPriority(Notification.PRIORITY_LOW); - if(android.os.Build.VERSION.SDK_INT >= 21) - noteBuilder.setCategory(Notification.CATEGORY_TRANSPORT); - - noteBuilder.setStyle(new NotificationCompat.MediaStyle() - //.setMediaSession(mMediaSession.getSessionToken()) - .setShowActionsInCompactView(new int[]{0}) - .setShowCancelButton(true) - .setCancelButtonIntent(stopPI)); - if(videoThumbnail != null) { - noteBuilder.setLargeIcon(videoThumbnail); - } - - Notification note = noteBuilder.build(); + Notification note = buildNotification(); Intent openDetailView = new Intent(getApplicationContext(), VideoItemDetailActivity.class); @@ -249,7 +202,6 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare Log.d(TAG, "sleep failure"); } }*/ - } private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { @@ -303,5 +255,93 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare afterPlayCleanup(); } } + + private Notification buildNotification() { + Notification note; + Resources res = getApplicationContext().getResources(); + noteBuilder = new NotificationCompat.Builder(owner); + + PendingIntent playPI = PendingIntent.getBroadcast(owner, noteID, + new Intent(ACTION_PLAYPAUSE), PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent stopPI = PendingIntent.getBroadcast(owner, noteID, + new Intent(ACTION_STOP), PendingIntent.FLAG_UPDATE_CURRENT); + /* + NotificationCompat.Action pauseButton = new NotificationCompat.Action.Builder + (R.drawable.ic_pause_white_24dp, "Pause", playPI).build(); + */ + + noteBuilder + .setOngoing(true) + .setDeleteIntent(stopPI) + //doesn't fit with Notification.MediaStyle + //.setProgress(vidLength, 0, false) + .setSmallIcon(R.drawable.ic_play_circle_filled_white_24dp) + .setTicker( + String.format(res.getString( + R.string.backgroundPlayerTickerText), title)); + + if (android.os.Build.VERSION.SDK_INT < 21) { + + NotificationCompat.Action playButton = new NotificationCompat.Action.Builder + (R.drawable.ic_play_arrow_white_48dp, + res.getString(R.string.play), playPI).build(); + + noteBuilder + .setContentTitle(title) + //really? Id like to put something more helpful here. + //.setContentText("NewPipe is playing in the background") + .setContentText(channelName) + //.setAutoCancel(!mediaPlayer.isPlaying()) + .setDeleteIntent(stopPI) + //doesn't fit with Notification.MediaStyle + //.setProgress(vidLength, 0, false) + .setLargeIcon(videoThumbnail) + .addAction(playButton); + //.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + //.setLargeIcon(cover) + + if (android.os.Build.VERSION.SDK_INT >= 16) + noteBuilder.setPriority(Notification.PRIORITY_LOW); + + noteBuilder.setStyle(new NotificationCompat.MediaStyle() + //.setMediaSession(mMediaSession.getSessionToken()) + .setShowActionsInCompactView(new int[]{0}) + .setShowCancelButton(true) + .setCancelButtonIntent(stopPI)); + if (videoThumbnail != null) { + noteBuilder.setLargeIcon(videoThumbnail); + } + note = noteBuilder.build(); + } else { + RemoteViews view = + new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification); + view.setImageViewBitmap(R.id.backgroundCover, videoThumbnail); + view.setTextViewText(R.id.backgroundSongName, title); + view.setTextViewText(R.id.backgroundArtist, channelName); + view.setOnClickPendingIntent(R.id.backgroundStop, stopPI); + view.setOnClickPendingIntent(R.id.backgroundPlayPause, playPI); + + RemoteViews expandedView = + new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification); + expandedView.setImageViewBitmap(R.id.backgroundCover, videoThumbnail); + expandedView.setTextViewText(R.id.backgroundSongName, title); + expandedView.setTextViewText(R.id.backgroundArtist, channelName); + expandedView.setOnClickPendingIntent(R.id.backgroundStop, stopPI); + expandedView.setOnClickPendingIntent(R.id.backgroundPlayPause, playPI); + + noteBuilder.setCategory(Notification.CATEGORY_TRANSPORT); + + //Make notification appear on lockscreen + noteBuilder.setVisibility(Notification.VISIBILITY_PUBLIC); + + note = noteBuilder.build(); + note.contentView = view; + + //todo: This never shows up. I was not able to figure out why: + note.bigContentView = expandedView; + } + + return note; + } } } diff --git a/app/src/main/java/org/schabi/newpipe/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/DownloadDialog.java index 903251e58..1f018618a 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/DownloadDialog.java @@ -57,26 +57,29 @@ public class DownloadDialog extends DialogFragment { @Override public void onClick(DialogInterface dialog, int which) { Context context = getActivity(); - SharedPreferences defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String suffix = ""; String title = arguments.getString(TITLE); String url = ""; + String downloadFolder = Environment.DIRECTORY_DOWNLOADS; switch(which) { case 0: // Video suffix = arguments.getString(FILE_SUFFIX_VIDEO); url = arguments.getString(VIDEO_URL); + downloadFolder = Environment.DIRECTORY_MOVIES; break; case 1: suffix = arguments.getString(FILE_SUFFIX_AUDIO); url = arguments.getString(AUDIO_URL); + downloadFolder = Environment.DIRECTORY_MUSIC; break; default: Log.d(TAG, "lolz"); } - //to avoid hard-coded string like "/storage/emulated/0/NewPipe" - final File dir = new File(defaultPreferences.getString( - "download_path_preference", - Environment.getExternalStorageDirectory().getAbsolutePath() + "/NewPipe")); + //to avoid hard-coded string like "/storage/emulated/0/Movies" + String downloadPath = prefs.getString(getString(R.string.downloadPathPreference), + Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + downloadFolder); + final File dir = new File(downloadPath); if(!dir.exists()) { boolean mkdir = dir.mkdir(); //attempt to create directory if(!mkdir && !dir.isDirectory()) { @@ -84,17 +87,21 @@ public class DownloadDialog extends DialogFragment { //TODO notify user "download directory should be changed" ? } } - DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - DownloadManager.Request request = new DownloadManager.Request( - Uri.parse(url)); - request.setDestinationUri(Uri.fromFile(new File( - defaultPreferences.getString("download_path_preference", "/storage/emulated/0/NewPipe") - + "/" + title + suffix))); - request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); - try { - dm.enqueue(request); - } catch (Exception e) { - e.printStackTrace(); + String saveFilePath = dir + "/" + title + suffix; + if (App.isUsingTor()) { + // if using Tor, do not use DownloadManager because the proxy cannot be set + Downloader.downloadFile(getContext(), url, saveFilePath); + } else { + DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); + DownloadManager.Request request = new DownloadManager.Request( + Uri.parse(url)); + request.setDestinationUri(Uri.fromFile(new File(saveFilePath))); + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + try { + dm.enqueue(request); + } catch (Exception e) { + e.printStackTrace(); + } } } }); diff --git a/app/src/main/java/org/schabi/newpipe/Downloader.java b/app/src/main/java/org/schabi/newpipe/Downloader.java index f0a19cfc5..8a296c61e 100644 --- a/app/src/main/java/org/schabi/newpipe/Downloader.java +++ b/app/src/main/java/org/schabi/newpipe/Downloader.java @@ -1,12 +1,30 @@ package org.schabi.newpipe; +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.support.v4.app.NotificationCompat; +import android.util.Log; + +import java.io.BufferedInputStream; import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; +import java.net.MalformedURLException; import java.net.URL; import java.net.UnknownHostException; +import javax.net.ssl.HttpsURLConnection; + +import info.guardianproject.netcipher.NetCipher; + /** * Created by Christian Schabesberger on 14.08.15. * @@ -28,6 +46,7 @@ import java.net.UnknownHostException; */ public class Downloader { + public static final String TAG = "Downloader"; private static final String USER_AGENT = "Mozilla/5.0"; @@ -40,7 +59,7 @@ public class Downloader { String ret = ""; try { URL url = new URL(siteUrl); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); + HttpsURLConnection con = NetCipher.getHttpsURLConnection(url); con.setRequestProperty("Accept-Language", language); ret = dl(con); } @@ -84,7 +103,7 @@ public class Downloader { try { URL url = new URL(siteUrl); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); + HttpsURLConnection con = NetCipher.getHttpsURLConnection(url); ret = dl(con); } catch(Exception e) { @@ -93,4 +112,92 @@ public class Downloader { return ret; } + + /** + * Downloads a file from a URL in the background using an {@link AsyncTask}. + * + * @param fileURL HTTP URL of the file to be downloaded + * @param saveFilePath path of the directory to save the file + * @throws IOException + */ + public static void downloadFile(final Context context, final String fileURL, final String saveFilePath) { + new AsyncTask() { + + private NotificationManager nm; + private NotificationCompat.Builder builder; + private int notifyId = 0x1234; + private int fileSize = 0xffffffff; + + @Override + protected void onPreExecute() { + super.onPreExecute(); + nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + Drawable icon = context.getResources().getDrawable(R.mipmap.ic_launcher); + builder = new NotificationCompat.Builder(context) + .setSmallIcon(android.R.drawable.stat_sys_download) + .setLargeIcon(((BitmapDrawable) icon).getBitmap()) + .setContentTitle(saveFilePath.substring(saveFilePath.lastIndexOf('/') + 1)) + .setContentText(saveFilePath) + .setProgress(fileSize, 0, false); + nm.notify(notifyId, builder.build()); + } + + @Override + protected Void doInBackground(Void... voids) { + HttpsURLConnection con = null; + try { + con = NetCipher.getHttpsURLConnection(fileURL); + int responseCode = con.getResponseCode(); + + // always check HTTP response code first + if (responseCode == HttpURLConnection.HTTP_OK) { + fileSize = con.getContentLength(); + InputStream inputStream = new BufferedInputStream(con.getInputStream()); + FileOutputStream outputStream = new FileOutputStream(saveFilePath); + + int bufferSize = 8192; + int downloaded = 0; + + int bytesRead = -1; + byte[] buffer = new byte[bufferSize]; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + downloaded += bytesRead; + if (downloaded % 50000 < bufferSize) { + publishProgress(downloaded); + } + } + + outputStream.close(); + inputStream.close(); + publishProgress(bufferSize); + + } else { + Log.i(TAG, "No file to download. Server replied HTTP code: " + responseCode); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (con != null) { + con.disconnect(); + con = null; + } + } + return null; + } + + @Override + protected void onProgressUpdate(Integer... progress) { + builder.setProgress(fileSize, progress[0], false); + nm.notify(notifyId, builder.build()); + } + + @Override + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + nm.cancel(notifyId); + } + }.execute(); + } + } diff --git a/app/src/main/java/org/schabi/newpipe/ExitActivity.java b/app/src/main/java/org/schabi/newpipe/ExitActivity.java new file mode 100644 index 000000000..c193ffbde --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/ExitActivity.java @@ -0,0 +1,36 @@ + +package org.schabi.newpipe; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; + +public class ExitActivity extends Activity { + + @SuppressLint("NewApi") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (Build.VERSION.SDK_INT >= 21) { + finishAndRemoveTask(); + } else { + finish(); + } + + System.exit(0); + } + + public static void exitAndRemoveFromRecentApps(Activity activity) { + Intent intent = new Intent(activity, ExitActivity.class); + + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + | Intent.FLAG_ACTIVITY_CLEAR_TASK + | Intent.FLAG_ACTIVITY_NO_ANIMATION); + + activity.startActivity(intent); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/Localization.java b/app/src/main/java/org/schabi/newpipe/Localization.java new file mode 100644 index 000000000..b38c52a90 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/Localization.java @@ -0,0 +1,77 @@ +package org.schabi.newpipe; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.preference.PreferenceManager; + +import java.text.DateFormat; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +/** + * Created by chschtsch on 12/29/15. + */ + +public class Localization { + + public static Locale getPreferredLocale(Context context) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + + String languageCode = sp.getString(String.valueOf(R.string.searchLanguage), "en"); + + if(languageCode.length() == 2) { + return new Locale(languageCode); + } + else if(languageCode.contains("_")) { + String country = languageCode + .substring(languageCode.indexOf("_"), languageCode.length()); + return new Locale(languageCode.substring(0, 2), country); + } + return Locale.getDefault(); + } + + public static String localizeViewCount(long viewCount, Context context) { + Locale locale = getPreferredLocale(context); + + Resources res = context.getResources(); + String viewsString = res.getString(R.string.viewCountText); + + NumberFormat nf = NumberFormat.getInstance(locale); + String formattedViewCount = nf.format(viewCount); + return String.format(viewsString, formattedViewCount); + } + + public static String localizeNumber(long number, Context context) { + Locale locale = getPreferredLocale(context); + NumberFormat nf = NumberFormat.getInstance(locale); + return nf.format(number); + } + + private static String formatDate(String date, Context context) { + Locale locale = getPreferredLocale(context); + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + Date datum = null; + try { + datum = formatter.parse(date); + } catch (ParseException e) { + e.printStackTrace(); + } + + DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, locale); + + return df.format(datum); + } + + public static String localizeDate(String date, Context context) { + Resources res = context.getResources(); + String dateString = res.getString(R.string.uploadDateText); + + String formattedDate = formatDate(date, context); + return String.format(dateString, formattedDate); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/PanicResponderActivity.java b/app/src/main/java/org/schabi/newpipe/PanicResponderActivity.java new file mode 100644 index 000000000..e9bf0e985 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/PanicResponderActivity.java @@ -0,0 +1,32 @@ + +package org.schabi.newpipe; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; + +public class PanicResponderActivity extends Activity { + + public static final String PANIC_TRIGGER_ACTION = "info.guardianproject.panic.action.TRIGGER"; + + @SuppressLint("NewApi") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) { + // TODO explicitly clear the search results once they are restored when the app restarts + // or if the app reloads the current video after being killed, that should be cleared also + ExitActivity.exitAndRemoveFromRecentApps(this); + } + + if (Build.VERSION.SDK_INT >= 21) { + finishAndRemoveTask(); + } else { + finish(); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/PlayVideoActivity.java b/app/src/main/java/org/schabi/newpipe/PlayVideoActivity.java index d5fea8c3e..13a1cefed 100644 --- a/app/src/main/java/org/schabi/newpipe/PlayVideoActivity.java +++ b/app/src/main/java/org/schabi/newpipe/PlayVideoActivity.java @@ -187,6 +187,18 @@ public class PlayVideoActivity extends AppCompatActivity { videoView.pause(); } + @Override + public void onResume() { + super.onResume(); + App.checkStartTor(this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + prefs = getPreferences(Context.MODE_PRIVATE); + } + @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); diff --git a/app/src/main/java/org/schabi/newpipe/SettingsActivity.java b/app/src/main/java/org/schabi/newpipe/SettingsActivity.java index b434b53d8..f599ebd8b 100644 --- a/app/src/main/java/org/schabi/newpipe/SettingsActivity.java +++ b/app/src/main/java/org/schabi/newpipe/SettingsActivity.java @@ -1,13 +1,14 @@ package org.schabi.newpipe; +import android.app.Activity; import android.content.Context; -import android.content.SharedPreferences; +import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; -import android.os.Environment; +import android.preference.CheckBoxPreference; +import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceFragment; -import android.preference.PreferenceManager; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.v7.app.ActionBar; @@ -17,6 +18,8 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import info.guardianproject.netcipher.proxy.OrbotHelper; + /** * Created by Christian Schabesberger on 31.08.15. * @@ -39,6 +42,7 @@ import android.view.ViewGroup; public class SettingsActivity extends PreferenceActivity { + private static final int REQUEST_INSTALL_ORBOT = 0x1234; private AppCompatDelegate mDelegate = null; @Override @@ -56,13 +60,48 @@ public class SettingsActivity extends PreferenceActivity { } public static class SettingsFragment extends PreferenceFragment { + private CheckBoxPreference useTorCheckBox; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.settings_screen); + + // if Orbot is installed, then default to using Tor, the user can still override + useTorCheckBox = (CheckBoxPreference) findPreference(getString(R.string.useTor)); + final Activity activity = getActivity(); + final boolean useTor = OrbotHelper.isOrbotInstalled(activity); + useTorCheckBox.setDefaultValue(useTor); + useTorCheckBox.setChecked(useTor); + useTorCheckBox.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object o) { + boolean useTor = (Boolean) o; + if (useTor) { + if (OrbotHelper.isOrbotInstalled(activity)) { + App.configureTor(true); + } else { + Intent intent = OrbotHelper.getOrbotInstallIntent(activity); + activity.startActivityForResult(intent, REQUEST_INSTALL_ORBOT); + } + } else { + App.configureTor(false); + } + return true; + } + }); } } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + // try to start tor regardless of resultCode since clicking back after + // installing the app does not necessarily return RESULT_OK + App.configureTor(requestCode == REQUEST_INSTALL_ORBOT + && OrbotHelper.requestStartTor(this)); + } + @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); @@ -148,17 +187,4 @@ public class SettingsActivity extends PreferenceActivity { } return true; } - - public static void initSettings(Context context) { - PreferenceManager.setDefaultValues(context, R.xml.settings_screen, false); - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - if(sp.getString(context.getString(R.string.downloadPathPreference), "").isEmpty()){ - SharedPreferences.Editor spEditor = sp.edit(); - String newPipeDownloadStorage = - Environment.getExternalStorageDirectory().getAbsolutePath() + "/NewPipe"; - spEditor.putString(context.getString(R.string.downloadPathPreference) - , newPipeDownloadStorage); - spEditor.apply(); - } - } } diff --git a/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java b/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java index b7eaa4285..c2bbb069e 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java +++ b/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java @@ -1,5 +1,6 @@ package org.schabi.newpipe; +import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -33,7 +34,7 @@ class VideoInfoItemViewCreator { this.inflater = inflater; } - public View getViewFromVideoInfoItem(View convertView, ViewGroup parent, VideoPreviewInfo info) { + public View getViewFromVideoInfoItem(View convertView, ViewGroup parent, VideoPreviewInfo info, Context context) { ViewHolder holder; if(convertView == null) { convertView = inflater.inflate(R.layout.video_item, parent, false); @@ -59,8 +60,7 @@ class VideoInfoItemViewCreator { if(!info.upload_date.isEmpty()) { holder.itemUploadDateView.setText(info.upload_date); } else { - //tweak if necessary: This is a hack to prevent having white space in the layout :P - holder.itemUploadDateView.setText(String.format("%d", info.view_count)); + holder.itemUploadDateView.setText(Localization.localizeViewCount(info.view_count, context)); } return convertView; diff --git a/app/src/main/java/org/schabi/newpipe/VideoItemDetailActivity.java b/app/src/main/java/org/schabi/newpipe/VideoItemDetailActivity.java index 4a553241f..f93ca4b9e 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoItemDetailActivity.java +++ b/app/src/main/java/org/schabi/newpipe/VideoItemDetailActivity.java @@ -113,6 +113,12 @@ public class VideoItemDetailActivity extends AppCompatActivity { .commit(); } + @Override + public void onResume() { + super.onResume(); + App.checkStartTor(this); + } + @Override public void onSaveInstanceState(Bundle outState) { outState.putString(VideoItemDetailFragment.VIDEO_URL, videoUrl); diff --git a/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java b/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java index 0173c07de..ddec1e36e 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java @@ -1,9 +1,7 @@ package org.schabi.newpipe; -import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; -import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -35,13 +33,7 @@ import android.view.MenuItem; import android.widget.Toast; import java.net.URL; -import java.text.DateFormat; -import java.text.NumberFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Date; -import java.util.Locale; import java.util.Vector; import org.schabi.newpipe.services.VideoExtractor; @@ -235,7 +227,7 @@ public class VideoItemDetailFragment extends Fragment { switch (info.errorCode) { case VideoInfo.NO_ERROR: { View nextVideoView = videoItemViewCreator - .getViewFromVideoInfoItem(null, nextVideoFrame, info.nextVideo); + .getViewFromVideoInfoItem(null, nextVideoFrame, info.nextVideo, getContext()); nextVideoFrame.addView(nextVideoView); @@ -253,31 +245,20 @@ public class VideoItemDetailFragment extends Fragment { uploaderView.setText(info.uploader); actionBarHandler.setChannelName(info.uploader); - Locale locale = getPreferredLocale(); - NumberFormat nf = NumberFormat.getInstance(locale); - String localisedViewCount = nf.format(info.view_count); - viewCountView.setText( - String.format( - res.getString(R.string.viewCountText), localisedViewCount)); + String localizedViewCount = Localization.localizeViewCount(info.view_count, getContext()); + viewCountView.setText(localizedViewCount); - thumbsUpView.setText(nf.format(info.like_count)); - thumbsDownView.setText(nf.format(info.dislike_count)); + String localizedLikeCount = Localization.localizeNumber(info.like_count, getContext()); + thumbsUpView.setText(localizedLikeCount); - @SuppressLint("SimpleDateFormat") - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); - Date datum = null; - try { - datum = formatter.parse(info.upload_date); - } catch (ParseException e) { - e.printStackTrace(); - } + String localizedDislikeCount = Localization.localizeNumber(info.dislike_count, getContext()); + thumbsDownView.setText(localizedDislikeCount); - DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, locale); + String localizedDate = Localization.localizeDate(info.upload_date, getContext()); + uploadDateView.setText(localizedDate); - String localisedDate = df.format(datum); - uploadDateView.setText( - String.format(res.getString(R.string.uploadDateText), localisedDate)); descriptionView.setText(Html.fromHtml(info.description)); + descriptionView.setMovementMethod(LinkMovementMethod.getInstance()); actionBarHandler.setServiceId(streamingServiceId); @@ -306,7 +287,7 @@ public class VideoItemDetailFragment extends Fragment { VideoItemDetailFragment.ARG_ITEM_ID, currentVideoInfo.nextVideo.id); */ detailIntent.putExtra( VideoItemDetailFragment.VIDEO_URL, currentVideoInfo.nextVideo.webpage_url); - //todo: make id dynamic the following line is crap + detailIntent.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId); startActivity(detailIntent); } @@ -315,7 +296,7 @@ public class VideoItemDetailFragment extends Fragment { break; case VideoInfo.ERROR_BLOCKED_BY_GEMA: thumbnailView.setImageBitmap(BitmapFactory.decodeResource( - getResources(), R.drawable.gruese_die_gema_unangebracht)); + getResources(), R.drawable.gruese_die_gema)); backgroundButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -458,30 +439,6 @@ public class VideoItemDetailFragment extends Fragment { } } - - - /**Returns the java.util.Locale object which corresponds to the locale set in NewPipe's preferences. - * Currently not affected by the device's locale.*/ - private Locale getPreferredLocale() { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); - String languageKey = getContext().getString(R.string.searchLanguage); - //i know the following line defaults languageCode to "en", but java is picky about uninitialised values - // Schabi: well lint tels me the value is redundant. I'll suppress it for now. - @SuppressWarnings("UnusedAssignment") - String languageCode = "en"; - languageCode = sp.getString(languageKey, "en"); - - if(languageCode.length() == 2) { - return new Locale(languageCode); - } - else if(languageCode.contains("_")) { - String country = languageCode - .substring(languageCode.indexOf("_"), languageCode.length()); - return new Locale(languageCode.substring(0, 2), country); - } - return Locale.getDefault(); - } - private boolean checkIfLandscape() { DisplayMetrics displayMetrics = new DisplayMetrics(); getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); diff --git a/app/src/main/java/org/schabi/newpipe/VideoItemListActivity.java b/app/src/main/java/org/schabi/newpipe/VideoItemListActivity.java index 6aaf10d41..8c4f5c57e 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoItemListActivity.java +++ b/app/src/main/java/org/schabi/newpipe/VideoItemListActivity.java @@ -3,6 +3,7 @@ package org.schabi.newpipe; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.preference.PreferenceManager; import android.support.v4.app.NavUtils; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.SearchView; @@ -171,7 +172,13 @@ public class VideoItemListActivity extends AppCompatActivity } } - SettingsActivity.initSettings(this); + PreferenceManager.setDefaultValues(this, R.xml.settings_screen, false); + } + + @Override + public void onResume() { + super.onResume(); + App.checkStartTor(this); } /** diff --git a/app/src/main/java/org/schabi/newpipe/VideoListAdapter.java b/app/src/main/java/org/schabi/newpipe/VideoListAdapter.java index e85e74e22..70672d040 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/VideoListAdapter.java @@ -96,7 +96,7 @@ class VideoListAdapter extends BaseAdapter { @Override public View getView(int position, View convertView, ViewGroup parent) { - convertView = viewCreator.getViewFromVideoInfoItem(convertView, parent, videoList.get(position)); + convertView = viewCreator.getViewFromVideoInfoItem(convertView, parent, videoList.get(position), context); if(listView.isItemChecked(position)) { convertView.setBackgroundColor(ContextCompat.getColor(context,R.color.primaryColorYoutube)); diff --git a/app/src/main/res/drawable-hdpi/ic_close_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_close_white_24dp.png new file mode 100644 index 000000000..ceb1a1eeb Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_close_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_close_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_close_white_24dp.png new file mode 100644 index 000000000..af7f8288d Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_close_white_24dp.png differ diff --git a/app/src/main/res/drawable-nodpi/gruese_die_gema.png b/app/src/main/res/drawable-nodpi/gruese_die_gema.png new file mode 100644 index 000000000..d6e2af3d5 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/gruese_die_gema.png differ diff --git a/app/src/main/res/drawable-nodpi/gruese_die_gema_unangebracht.png b/app/src/main/res/drawable-nodpi/gruese_die_gema_unangebracht.png deleted file mode 100644 index e19491b7b..000000000 Binary files a/app/src/main/res/drawable-nodpi/gruese_die_gema_unangebracht.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_close_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_close_white_24dp.png new file mode 100644 index 000000000..b7c7ffd0e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_close_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_close_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_close_white_24dp.png new file mode 100644 index 000000000..6b717e0dd Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_close_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_close_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_close_white_24dp.png new file mode 100644 index 000000000..396419219 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_close_white_24dp.png differ diff --git a/app/src/main/res/layout/paginate_footer.xml b/app/src/main/res/layout/paginate_footer.xml index 8e5d7571d..b3757c1af 100644 --- a/app/src/main/res/layout/paginate_footer.xml +++ b/app/src/main/res/layout/paginate_footer.xml @@ -8,7 +8,7 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/player_notification_expanded.xml b/app/src/main/res/layout/player_notification_expanded.xml new file mode 100644 index 000000000..3fd379a6b --- /dev/null +++ b/app/src/main/res/layout/player_notification_expanded.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 4800a7493..227877c52 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -49,4 +49,9 @@ Avatar de l\'utilisateur Utiliser un lecteur vidéo externe Utiliser un lecteur audio externe + Lecture en arrière-plan + Lecteur en arrière-plan NewPipe + Chargement + Lecture + diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml new file mode 100644 index 000000000..a11e14119 --- /dev/null +++ b/app/src/main/res/values-he/strings.xml @@ -0,0 +1,10 @@ + +%1$s צפיות + הועלה בתאריך %1$s + שתף + חפש + הבא + הורדה + הגדרות + הגדרות + diff --git a/app/src/main/res/values-id b/app/src/main/res/values-id new file mode 120000 index 000000000..9ea8dda4b --- /dev/null +++ b/app/src/main/res/values-id @@ -0,0 +1 @@ +values-in \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml new file mode 100644 index 000000000..9c5ad89af --- /dev/null +++ b/app/src/main/res/values-in/strings.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-iw b/app/src/main/res/values-iw new file mode 120000 index 000000000..57bf91954 --- /dev/null +++ b/app/src/main/res/values-iw @@ -0,0 +1 @@ +values-he \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 558918270..c09605941 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -50,4 +50,8 @@ 外部オーディオ プレイヤーを使用する バックグラウンドで再生しています + NewPipe バックグラウンド プレーヤー + 読み込み中 + 再生 + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a2dbdd50f..36368fe09 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -51,4 +51,7 @@ Миниатюра аватара пользователся Дислайки Лайки - +Использовать внешний проигрыватель для видео + Использовать внешний проигрыватель для аудио + Проигрывание в фоновом режиме + diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 91f54dc11..412d9a56c 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -47,4 +47,9 @@ Všeč mi je Ni mi všeč + Ozadnji predvajalnik NewPipe + Nalaganje ... + Predvajanje v ozadju + Predvajaj + diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 7ab3d8eaa..e0c092d25 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -54,4 +54,10 @@ Користи спољашњи аудио плејер Пуштам у позадини - + Позадински плејер за Јутјуб цев + Учитавам + Пусти + + Користи Тор + Принудно преусмерење саобраћаја кроз Тор за доданту приватност (токови још нису подржани) + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index e14ee9606..7e0d71406 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,4 +7,5 @@ #efff #6000 #EEEEEE + #323232 \ No newline at end of file diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 8d33b8cbe..0ff359a46 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -71,6 +71,7 @@ sl fi sv + bo vi tr bg @@ -149,6 +150,7 @@ Slovenščina Suomi Svenska + Tibetan བོད་སྐད། Tiếng Việt Türkçe Български diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 177280277..c7058759e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,7 @@ NewPipe + NewPipe Background Player NewPipe %1$s views Uploaded on %1$s @@ -10,6 +11,7 @@ https://f-droid.org/repository/browse/?fdfilter=vlc&fdid=org.videolan.vlc Open in browser Share + Loading Download Search Settings @@ -53,6 +55,7 @@ %1$s - NewPipe Playing in background https://www.c3s.cc/ + Play Video preview thumbnail @@ -60,4 +63,6 @@ Uploader thumbnail Dislikes Likes + Use Tor + Force download traffic through Tor for increased privacy (streaming videos not yet supported) diff --git a/app/src/main/res/xml/settings_screen.xml b/app/src/main/res/xml/settings_screen.xml index 98399f778..4d21b457b 100644 --- a/app/src/main/res/xml/settings_screen.xml +++ b/app/src/main/res/xml/settings_screen.xml @@ -64,8 +64,7 @@ android:key="@string/downloadPathPreference" android:title="@string/downloadLocation" android:summary="@string/downloadLocationSummary" - android:dialogTitle="@string/downloadLocationDialogTitle" - android:defaultValue=""/> + android:dialogTitle="@string/downloadLocationDialogTitle" /> + + - \ No newline at end of file + diff --git a/assets/gruese_die_gema.svg b/assets/gruese_die_gema.svg index 48ebc8db0..eace7bf44 100644 --- a/assets/gruese_die_gema.svg +++ b/assets/gruese_die_gema.svg @@ -7,7 +7,6 @@ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="426" @@ -16,7 +15,10 @@ id="svg2" version="1.1" inkscape:version="0.91 r13725" - sodipodi:docname="gruese_die_gema_unangebracht.svg"> + sodipodi:docname="gruese_die_gema.svg" + inkscape:export-filename="/home/the-scrabi/Projects/NewPipe/assets/gruese_die_gema.png" + inkscape:export-xdpi="216.33803" + inkscape:export-ydpi="216.33803"> + inkscape:window-width="1015" + inkscape:window-height="417" + inkscape:window-x="287" + inkscape:window-y="491" + inkscape:window-maximized="0" /> @@ -46,7 +48,7 @@ image/svg+xml - + @@ -56,129 +58,155 @@ id="layer1" transform="translate(0,-812.36212)"> - - - - - - - - - - - - - - - - - - - - - + id="g4230" + transform="matrix(0.67525011,0,0,0.67525011,-14.086916,282.00859)"> + + + + + + + + + + + + + + + + + + + + + + + + + - + Genervt? Gib der Alternative c3s + + eine Chance. diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index 1d3591c8a..000000000 --- a/gradle.properties +++ /dev/null @@ -1,18 +0,0 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -# Default value: -Xmx10248m -XX:MaxPermSize=256m -# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true \ No newline at end of file