diff --git a/app/build.gradle b/app/build.gradle index a2e167e4..f8aee685 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,6 +37,7 @@ dependencies { // More libraries compile 'com.getbase:floatingactionbutton:1.9.1' compile 'com.jakewharton:butterknife:8.0.1' + compile 'info.guardianproject.netcipher:netcipher:1.2.1' apt 'com.jakewharton:butterknife-compiler:8.0.1' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d89beb83..53d03bd7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -70,6 +70,12 @@ android:exported="false" > + + + + + + diff --git a/app/src/main/java/com/github/dfa/diaspora_android/activity/MainActivity.java b/app/src/main/java/com/github/dfa/diaspora_android/activity/MainActivity.java index 95eea1d8..10ccd55e 100644 --- a/app/src/main/java/com/github/dfa/diaspora_android/activity/MainActivity.java +++ b/app/src/main/java/com/github/dfa/diaspora_android/activity/MainActivity.java @@ -82,6 +82,7 @@ import com.github.dfa.diaspora_android.listener.WebUserProfileChangedListener; import com.github.dfa.diaspora_android.ui.ContextMenuWebView; import com.github.dfa.diaspora_android.ui.CustomWebViewClient; import com.github.dfa.diaspora_android.util.Helpers; +import com.github.dfa.diaspora_android.util.OrbotStatusReceiver; import org.json.JSONException; @@ -97,6 +98,7 @@ import java.util.Locale; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; +import info.guardianproject.netcipher.proxy.OrbotHelper; public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, WebUserProfileChangedListener { @@ -176,6 +178,19 @@ public class MainActivity extends AppCompatActivity podUserProfile.setCallbackHandler(uiHandler); podUserProfile.setListener(this); + //Orbot integration + OrbotStatusReceiver.setMainActivity(this); + OrbotHelper.requestStartTor(getApplicationContext()); + if(appSettings.isProxyOrbot()) { + if(!OrbotHelper.isOrbotInstalled(getApplicationContext())) { + appSettings.setProxyOrbot(false); + promptInstallOrbot(); + } else { + //precautionary set Proxy + OrbotStatusReceiver.setProxy(getApplicationContext(), OrbotStatusReceiver.DEFAULT_HOST, OrbotStatusReceiver.DEFAULT_PORT); + } + } + this.registerForContextMenu(webView); webView.setParentActivity(this); webView.setOverScrollMode(WebView.OVER_SCROLL_ALWAYS); @@ -308,9 +323,9 @@ public class MainActivity extends AppCompatActivity } }); - + OrbotHelper.requestStartTor(getApplicationContext()); if (savedInstanceState == null) { - if (Helpers.isOnline(MainActivity.this)) { + if (Helpers.isOnline(this)) { webView.loadData("", "text/html", null); webView.loadUrl("https://" + podDomain); } else { @@ -929,7 +944,8 @@ public class MainActivity extends AppCompatActivity case R.id.nav_settings_app: { final CharSequence[] options = {getString(R.string.settings_font), getString(R.string.settings_view), appSettings.isLoadImages() ? - getString(R.string.settings_images_switch_off) : getString(R.string.settings_images_switch_on), getString(R.string.jb_pod)}; + getString(R.string.settings_images_switch_off) : getString(R.string.settings_images_switch_on), getString(R.string.jb_pod), + appSettings.isProxyOrbot() ? getString(R.string.orbot_proxy_enabled) : getString(R.string.orbot_proxy_disabled)}; if (Helpers.isOnline(MainActivity.this)) { new AlertDialog.Builder(MainActivity.this) @@ -962,6 +978,17 @@ public class MainActivity extends AppCompatActivity }) .show(); break; + case 4: + boolean before = appSettings.isProxyOrbot(); + appSettings.setProxyOrbot(!before); + if(before) { + OrbotStatusReceiver.resetProxy(getApplicationContext()); + } else { + OrbotHelper.requestStartTor(getApplicationContext()); + webView.reload(); + } + break; + } } }).show(); @@ -1032,7 +1059,7 @@ public class MainActivity extends AppCompatActivity } DrawerLayout drawer = (DrawerLayout) findViewById(R.id.main__layout); - drawer.closeDrawer(GravityCompat.START); + if(drawer != null) drawer.closeDrawer(GravityCompat.START); return true; } @@ -1052,4 +1079,34 @@ public class MainActivity extends AppCompatActivity grantResults); } } + + /** + * Ask the user whether to install Orbot or not. Check if installing from + * F-Droid or Google Play, otherwise take the user to the Orbot download + * page on f-droid.org. + */ + void promptInstallOrbot() { + final Intent intent = OrbotHelper.getOrbotInstallIntent(MainActivity.this); + String message = this.getString(R.string.you_must_have_orbot) + " " + + (intent.getPackage() == null ? getString(R.string.download_orbot_from_fdroid) + : getString(R.string.get_orbot_from_fdroid)); + new AlertDialog.Builder(this).setTitle(R.string.install_orbot_).setMessage(message).setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + MainActivity.this.startActivity(intent); + } + }).setNegativeButton(android.R.string.no, null).show(); + } + + public void requestOrbotStart(boolean backgroundStartsDisabled) { + AlertDialog.Builder startDialog = new AlertDialog.Builder(this).setTitle(R.string.start_orbot_) + .setMessage((backgroundStartsDisabled ? R.string.orbot_starts_disabled_message + : R.string.orbot_doesn_t_appear_to_be_running_would_you_like_to_start_it_up_and_connect_to_tor_)); + startDialog.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + startActivityForResult(OrbotHelper.getShowOrbotStartIntent(), 1); + } + }).setNegativeButton(android.R.string.no, null).show(); + } } \ No newline at end of file diff --git a/app/src/main/java/com/github/dfa/diaspora_android/data/AppSettings.java b/app/src/main/java/com/github/dfa/diaspora_android/data/AppSettings.java index 5b11799f..d10ea43f 100644 --- a/app/src/main/java/com/github/dfa/diaspora_android/data/AppSettings.java +++ b/app/src/main/java/com/github/dfa/diaspora_android/data/AppSettings.java @@ -74,6 +74,7 @@ public class AppSettings { private static final String PODUSERPROFILE_ID = "podUserProfile_guid"; private static final String PODDOMAIN = "podDomain"; private static final String PODUSERPROFILE_ASPECTS = "podUserProfile_aspects"; + private static final String IS_PROXY_ORBOT = "proxyViaOrbot"; } @@ -142,6 +143,13 @@ public class AppSettings { setStringArray(prefApp, PREF.PREVIOUS_PODLIST, pods); } + public boolean isProxyOrbot() { + return prefApp.getBoolean(PREF.IS_PROXY_ORBOT, false); + } + + public void setProxyOrbot(boolean active) { + prefApp.edit().putBoolean(PREF.IS_PROXY_ORBOT, active).commit(); + } public void setPodAspects(PodAspect[] aspects) { setStringArray(prefPod, PREF.PODUSERPROFILE_ASPECTS, aspects); } diff --git a/app/src/main/java/com/github/dfa/diaspora_android/task/GetPodsService.java b/app/src/main/java/com/github/dfa/diaspora_android/task/GetPodsService.java index 8791ac00..2f643a83 100644 --- a/app/src/main/java/com/github/dfa/diaspora_android/task/GetPodsService.java +++ b/app/src/main/java/com/github/dfa/diaspora_android/task/GetPodsService.java @@ -28,12 +28,6 @@ import android.util.Log; import com.github.dfa.diaspora_android.App; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.DefaultHttpClient; import org.json.JSONArray; import org.json.JSONObject; @@ -44,6 +38,10 @@ import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; +import javax.net.ssl.HttpsURLConnection; + +import info.guardianproject.netcipher.NetCipher; + public class GetPodsService extends Service { public static final String MESSAGE_PODS_RECEIVED = "com.github.dfa.diaspora.podsreceived"; private static final String TAG = App.TAG; @@ -73,24 +71,28 @@ public class GetPodsService extends Service { // TODO: Update deprecated code StringBuilder builder = new StringBuilder(); - HttpClient client = new DefaultHttpClient(); + //HttpClient client = new DefaultHttpClient(); List list = null; + HttpsURLConnection connection; + InputStream inStream; try { - HttpGet httpGet = new HttpGet("http://podupti.me/api.php?key=4r45tg&format=json"); - HttpResponse response = client.execute(httpGet); - StatusLine statusLine = response.getStatusLine(); - int statusCode = statusLine.getStatusCode(); + connection = NetCipher.getHttpsURLConnection("https://podupti.me/api.php?key=4r45tg&format=json"); + int statusCode = connection.getResponseCode(); if (statusCode == 200) { - HttpEntity entity = response.getEntity(); - InputStream content = entity.getContent(); + inStream = connection.getInputStream(); BufferedReader reader = new BufferedReader( - new InputStreamReader(content)); + new InputStreamReader(inStream)); String line; while ((line = reader.readLine()) != null) { builder.append(line); } + + try { + inStream.close(); + } catch (IOException e) {} + + connection.disconnect(); } else { - //TODO Notify User about failure Log.e(TAG, "Failed to download list of pods"); } } catch (IOException e) { diff --git a/app/src/main/java/com/github/dfa/diaspora_android/task/ImageDownloadTask.java b/app/src/main/java/com/github/dfa/diaspora_android/task/ImageDownloadTask.java index 2b5a3d23..31cec3c8 100644 --- a/app/src/main/java/com/github/dfa/diaspora_android/task/ImageDownloadTask.java +++ b/app/src/main/java/com/github/dfa/diaspora_android/task/ImageDownloadTask.java @@ -13,6 +13,10 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import javax.net.ssl.HttpsURLConnection; + +import info.guardianproject.netcipher.NetCipher; + /** * Created by Gregor Santner (gsantner) on 24.03.16. */ @@ -35,9 +39,12 @@ public class ImageDownloadTask extends AsyncTask { String url = urls[0]; Bitmap bitmap = null; FileOutputStream out = null; + InputStream inStream; + HttpsURLConnection connection; try { - InputStream in = new java.net.URL(url).openStream(); - bitmap = BitmapFactory.decodeStream(in); + connection = NetCipher.getHttpsURLConnection(url); + inStream = connection.getInputStream(); + bitmap = BitmapFactory.decodeStream(inStream); // Save to file if not null if (savePath != null) { @@ -45,6 +52,12 @@ public class ImageDownloadTask extends AsyncTask { bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); } + try { + inStream.close(); + } catch (IOException e) {} + + connection.disconnect(); + } catch (Exception e) { Log.e(App.TAG, e.getMessage()); } finally { diff --git a/app/src/main/java/com/github/dfa/diaspora_android/task/ProfileFetchTask.java b/app/src/main/java/com/github/dfa/diaspora_android/task/ProfileFetchTask.java index f54774f0..beab5eff 100644 --- a/app/src/main/java/com/github/dfa/diaspora_android/task/ProfileFetchTask.java +++ b/app/src/main/java/com/github/dfa/diaspora_android/task/ProfileFetchTask.java @@ -10,10 +10,14 @@ import com.github.dfa.diaspora_android.data.PodUserProfile; import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; -import java.net.HttpURLConnection; import java.net.URL; +import javax.net.ssl.HttpsURLConnection; + +import info.guardianproject.netcipher.NetCipher; + /** * Created by Gregor Santner (gsantner) on 30.03.16. */ @@ -37,18 +41,21 @@ public class ProfileFetchTask extends AsyncTask { String cookies = cookieManager.getCookie("https://" + app.getSettings().getPodDomain()); Log.d(App.TAG, cookies); + HttpsURLConnection connection; + InputStream inStream; try { URL url = new URL("https://" + app.getSettings().getPodDomain() + "/stream"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setReadTimeout(10000); - conn.setConnectTimeout(15000); - conn.setRequestMethod("GET"); + connection = NetCipher.getHttpsURLConnection(url); + connection.setReadTimeout(10000); + connection.setConnectTimeout(15000); + connection.setRequestMethod("GET"); if (cookies != null) { - conn.setRequestProperty("Cookie", cookies); + connection.setRequestProperty("Cookie", cookies); } - conn.connect(); + connection.connect(); - BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); + inStream = connection.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(inStream)); String line; final String TARGET_TAG = "window.gon={};gon.user="; while ((line = br.readLine()) != null && !line.startsWith(" { break; } } + + try{ + br.close(); + inStream.close(); + } catch (IOException e){} + + connection.disconnect(); + } catch (IOException e) { e.printStackTrace(); } diff --git a/app/src/main/java/com/github/dfa/diaspora_android/ui/ContextMenuWebView.java b/app/src/main/java/com/github/dfa/diaspora_android/ui/ContextMenuWebView.java index 6b42b313..b7841ab7 100644 --- a/app/src/main/java/com/github/dfa/diaspora_android/ui/ContextMenuWebView.java +++ b/app/src/main/java/com/github/dfa/diaspora_android/ui/ContextMenuWebView.java @@ -20,9 +20,13 @@ import android.widget.Toast; import com.github.dfa.diaspora_android.R; import com.github.dfa.diaspora_android.activity.MainActivity; +import com.github.dfa.diaspora_android.util.OrbotStatusReceiver; import java.io.File; +import info.guardianproject.netcipher.NetCipher; +import info.guardianproject.netcipher.proxy.OrbotHelper; + /** * Subclass of WebView which adds a context menu for long clicks on images or links to share, save * or open with another browser @@ -149,4 +153,17 @@ public class ContextMenuWebView extends NestedWebView { public void setParentActivity(Activity activity) { this.parentActivity = activity; } + + @Override + public void reload() { + OrbotHelper.requestStartTor(context.getApplicationContext()); + super.reload(); + } + + @Override + public void loadUrl(String url) { + if(!OrbotStatusReceiver.isProxySet()) + OrbotHelper.requestStartTor(context.getApplicationContext()); + super.loadUrl(url); + } } \ No newline at end of file diff --git a/app/src/main/java/com/github/dfa/diaspora_android/util/OrbotStatusReceiver.java b/app/src/main/java/com/github/dfa/diaspora_android/util/OrbotStatusReceiver.java new file mode 100644 index 00000000..d7073f58 --- /dev/null +++ b/app/src/main/java/com/github/dfa/diaspora_android/util/OrbotStatusReceiver.java @@ -0,0 +1,141 @@ +/* + This file is part of the Diaspora for Android. + + Diaspora for Android is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Diaspora for Android is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the Diaspora for Android. + + If not, see . + */ + +package com.github.dfa.diaspora_android.util; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import com.github.dfa.diaspora_android.App; +import com.github.dfa.diaspora_android.activity.MainActivity; +import com.github.dfa.diaspora_android.data.AppSettings; + +import info.guardianproject.netcipher.NetCipher; +import info.guardianproject.netcipher.proxy.OrbotHelper; +import info.guardianproject.netcipher.web.WebkitProxy; + +/** + * BroadcastReceiver that handles Orbot status intents and sets the proxy. + * Created by vanitas on 06.06.16. + */ +public class OrbotStatusReceiver extends BroadcastReceiver { + + public static final String EXTRA_HTTP_HOST = "org.torproject.android.intent.extra.HTTP_PROXY_HOST"; + public static final String EXTRA_HTTP_PORT = "org.torproject.android.intent.extra.HTTP_PROXY_PORT"; + public static final String DEFAULT_HOST = "127.0.0.1"; + public static final int DEFAULT_PORT = 8118; + + private static String host = ""; + private static int port = 0; + private Intent lastStatus; + private Context lastContext; + private static MainActivity mainActivity; + private AppSettings appSettings; + private static boolean proxySet = false; + + public OrbotStatusReceiver() { + } + + @Override + public void onReceive(Context context, Intent intent) { + if(OrbotHelper.ACTION_STATUS.equals(intent.getAction())) { + lastStatus = intent; + lastContext = context; + if(appSettings == null) appSettings = new AppSettings(context.getApplicationContext()); + String orbotStatus = intent.getExtras().getString(OrbotHelper.EXTRA_STATUS); + if(appSettings.isProxyOrbot()) { + if (orbotStatus.equals(OrbotHelper.STATUS_ON)) { + setProxy(lastContext, lastStatus); + } else if(orbotStatus.equals(OrbotHelper.STATUS_OFF)) { + Log.d(App.TAG, "Warning: Orbot reports status off."); + OrbotHelper.requestStartTor(context.getApplicationContext()); + } else if(orbotStatus.equals(OrbotHelper.STATUS_STARTS_DISABLED)) { + Log.d(App.TAG, "Warning: Orbot has background starts disabled."); + if(mainActivity != null) mainActivity.requestOrbotStart(true); + } + } + } else { + Log.e(App.TAG, "Warning: Intents action "+intent.getAction()+ " does not equal "+OrbotHelper.ACTION_STATUS); + } + } + + public static void setProxy(Context context, Intent intent) { + if(intent != null) { + String status = intent.getStringExtra(OrbotHelper.EXTRA_STATUS); + if(status.equals(OrbotHelper.STATUS_ON)) { + String nHost = intent.getExtras().getString(EXTRA_HTTP_HOST, null); + int nPort = intent.getIntExtra(EXTRA_HTTP_PORT, -1); + //Got no values from intent + if((nHost == null || nPort == -1)) { + if(host.equals("") || port == 0) { + setProxy(context, DEFAULT_HOST, DEFAULT_PORT); + } + } else { + setProxy(context, nHost, nPort); + } + } + } else { + Log.e(App.TAG, "OrbotStatusReceiver: lastStatus intent is null. Cannot set Proxy."); + } + } + + public static void setProxy(Context context, String host, int port) { + try { + OrbotStatusReceiver.host = host; + OrbotStatusReceiver.port = port; + NetCipher.setProxy(host, port); + WebkitProxy.setProxy(MainActivity.class.getName(), context.getApplicationContext(), null, host, port); + Log.d(App.TAG, "Proxy successfully set."); + proxySet = true; + } catch(Exception e) { + Log.e(App.TAG, "setProxy failed: "); + e.printStackTrace(); + } + } + + public static void resetProxy(Context context) { + try { + OrbotStatusReceiver.host = ""; + OrbotStatusReceiver.port = 0; + NetCipher.clearProxy(); + WebkitProxy.resetProxy(MainActivity.class.getName(), context.getApplicationContext()); + } catch (Exception e) { + //Fails in any case on android 6. Ignore it and restart application. + } + proxySet = false; + //Restart application + Intent restartActivity = new Intent(context, MainActivity.class); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 12374, restartActivity, PendingIntent.FLAG_CANCEL_CURRENT); + AlarmManager mgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pendingIntent); + System.exit(0); + } + + public static boolean isProxySet() { + return proxySet; + } + + public static void setMainActivity(MainActivity main) { + mainActivity = main; + } +} diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 977b8857..fc3c09c6 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -166,5 +166,14 @@ along with this program. If not, see http://www.gnu.org/licenses.<br> <br Speichere Bild als Linkadresse kopiert … Teilen… + Möchten Sie Orbot nun von f-droid.org herunterladen? + Möchten Sie Orbot nun über F-Droid installieren? + Orbot installieren? + Orbot scheint nicht aktiv zu sein. Möchten Sie es nun starten um sich mit dem Tor-Netzwerk zu verbinden? + Orbot-Proxy: Deaktiviert + Orbot-Proxy: Aktiviert + Orbot scheint Hintergrund-Starts deaktiviert zu haben. Möchten Sie Orbot jetzt öffnen? + Orbot starten? + Um das Tor-Netzwerk als Proxy nutzen zu können, müssen Sie Orbot installieren. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ac6920ca..2fca7364 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -211,4 +211,15 @@ https:// Share… #DiasporaForAndroid + + Install Orbot? + You must have Orbot installed and activated to proxy traffic through it. + Would you like to install it from F-Droid? + Would you like to download it from f-droid.org? + Start Orbot? + Orbot doesn\'t appear to be running. Would you like to start it up and connect to Tor? + Orbot has background starts disabled. Would you like to open Orbot? + Orbot Proxy: Enabled + Orbot Proxy: Disabled +