diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml deleted file mode 100644 index 134ddf78..00000000 --- a/.idea/deploymentTargetDropDown.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index cd9e203c..7324f179 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -78,6 +78,7 @@ + diff --git a/app/build.gradle b/app/build.gradle index 571b2479..e0b358e3 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,4 @@ -import com.android.build.OutputFile +//import com.android.build.OutputFile plugins { id "com.jetbrains.python.envs" version "0.0.26" @@ -110,14 +110,18 @@ dependencies { implementation 'com.applovin:applovin-sdk:+' implementation 'com.applovin.mediation:mytarget-adapter:+' + implementation 'com.applovin.mediation:adcolony-adapter:4.8.0.2' implementation 'com.adcolony:sdk:4.8.0' + implementation 'com.facebook.android:audience-network-sdk:6.8.0' + implementation 'com.applovin.mediation:facebook-adapter:6.8.0.12' /* Orbot Service */ - implementation project(path: ':orbotmanager') /* Helper Libraries */ + implementation "android.arch.lifecycle:extensions:1.1.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.0.0" implementation 'com.coolerfall:android-http-download-manager:1.6.3' implementation 'com.android.volley:volley:1.2.1' implementation "net.zetetic:android-database-sqlcipher:4.4.3" @@ -129,7 +133,7 @@ dependencies { /* Automated APK Generation */ -android.applicationVariants.all { variant -> +/*android.applicationVariants.all { variant -> def buildType = variant.buildType.name @@ -153,7 +157,7 @@ android.applicationVariants.all { variant -> output.versionCodeOverride = versionCodeOverride } } -} +}*/ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2f6028e0..e259d84a 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,7 +16,7 @@ { - startService(new Intent(this, activityStateManager.class)); + if(!mBackground){ + startService(new Intent(this, activityStateManager.class)); + } }, 500); } }catch (Exception ex){ diff --git a/app/src/main/java/com/hiddenservices/onionservices/appManager/tabManager/tabController.java b/app/src/main/java/com/hiddenservices/onionservices/appManager/tabManager/tabController.java index b5ac3440..754390f8 100644 --- a/app/src/main/java/com/hiddenservices/onionservices/appManager/tabManager/tabController.java +++ b/app/src/main/java/com/hiddenservices/onionservices/appManager/tabManager/tabController.java @@ -243,6 +243,9 @@ public class tabController extends Fragment { return; } int orientation = activityContextManager.getInstance().getHomeController().getResources().getConfiguration().orientation; + if (mRecycleView == null){ + return; + } if (orientation == Configuration.ORIENTATION_PORTRAIT) { maxScroll = mRecycleView.computeVerticalScrollRange() - mScreenHeight * 0.350f + helperMethod.pxFromDp(helperMethod.getNavigationBarSize(getContext()).y); } else { @@ -258,7 +261,6 @@ public class tabController extends Fragment { mScrolled = false; if (mRecycleView.getChildAt(mRecycleView.getChildCount() - 1) != null) { if ((scrollY >= (mRecycleView.getChildAt(mRecycleView.getChildCount() - 1).getMeasuredHeight() - mRecycleView.getMeasuredHeight())) && scrollY > oldScrollY) { - Log.i("FUCK2:::::::", scrollY + ""); onSwipeBounce(0); } } diff --git a/app/src/main/java/com/hiddenservices/onionservices/pluginManager/adPluginManager/appLovinManager.java b/app/src/main/java/com/hiddenservices/onionservices/pluginManager/adPluginManager/appLovinManager.java index 3a6bb085..6aa8e613 100644 --- a/app/src/main/java/com/hiddenservices/onionservices/pluginManager/adPluginManager/appLovinManager.java +++ b/app/src/main/java/com/hiddenservices/onionservices/pluginManager/adPluginManager/appLovinManager.java @@ -2,20 +2,20 @@ package com.hiddenservices.onionservices.pluginManager.adPluginManager; import android.content.Context; import android.os.Handler; - +import android.util.Log; import com.adcolony.sdk.AdColony; import com.adcolony.sdk.AdColonyAppOptions; import com.applovin.mediation.MaxAd; import com.applovin.mediation.MaxAdViewAdListener; import com.applovin.mediation.MaxError; +import com.applovin.mediation.adapters.AdColonyMediationAdapter; import com.applovin.mediation.ads.MaxAdView; import com.applovin.sdk.AppLovinSdk; +import com.facebook.ads.AdSettings; import com.hiddenservices.onionservices.appManager.activityContextManager; import com.hiddenservices.onionservices.eventObserver; import com.hiddenservices.onionservices.pluginManager.pluginEnums; - import java.lang.ref.WeakReference; - import static com.hiddenservices.onionservices.pluginManager.pluginEnums.eAdManagerCallbacks.M_ON_AD_LOAD; public class appLovinManager implements MaxAdViewAdListener { @@ -26,6 +26,7 @@ public class appLovinManager implements MaxAdViewAdListener { private int mRequestCount = 0; private boolean bannerAdRequested = false; + private boolean bannerAdsLoaded = false; /*Initializations*/ @@ -36,7 +37,10 @@ public class appLovinManager implements MaxAdViewAdListener { } private void initializeBannerAds(Context pContext) { + AdSettings.setDataProcessingOptions(new String[]{}); AdColonyAppOptions appOptions = new AdColonyAppOptions(); + appOptions.setPrivacyFrameworkRequired(AdColonyAppOptions.GDPR, true); + appOptions.setPrivacyConsentString(AdColonyAppOptions.GDPR, "1"); AdColony.configure(activityContextManager.getInstance().getHomeController(), appOptions,"app3b56c67c45544c5c89"); AppLovinSdk.getInstance(pContext).setMediationProvider("max"); @@ -55,7 +59,7 @@ public class appLovinManager implements MaxAdViewAdListener { } private boolean isAdvertLoaded() { - return false; + return bannerAdsLoaded; } /* Overriden Methods */ @@ -72,12 +76,13 @@ public class appLovinManager implements MaxAdViewAdListener { @Override public void onAdLoaded(MaxAd ad) { + bannerAdsLoaded = true; mEvent.invokeObserver(null, M_ON_AD_LOAD); } @Override public void onAdDisplayed(MaxAd ad) { - + Log.i("ads load", "ad has been loaded"); } @Override diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..ca3826a4 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index f4e360fa..3092f837 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png new file mode 100644 index 00000000..2eebc2ad Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png index 7cc343b2..df62f624 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 2914f9f2..4fed28ae 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index a3a012f0..e51afa62 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png new file mode 100644 index 00000000..eb48be20 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png index 96ccac0c..30b0fca3 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index 7bf84e16..3204134a 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 90e440bc..7d1e73ac 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png new file mode 100644 index 00000000..04c585bc Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png index 89ae39d8..e99d9da1 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index 9d9593e2..0afb90c3 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 09078f16..eff7f71b 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png new file mode 100644 index 00000000..48e52158 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png index 5c71b512..c83177db 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index 989eb8c0..a250a3af 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 9fbb2f60..9bc99644 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png new file mode 100644 index 00000000..a646608c Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png index ba4d972b..af3ace9b 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index bf7c097a..c20a69f7 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml index 4e823a09..6686cf46 100644 --- a/app/src/main/res/values/ic_launcher_background.xml +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -1,4 +1,4 @@ - #3DDC84 + #FF6A47 \ No newline at end of file diff --git a/app/variables.gradle b/app/variables.gradle index 4c3c181a..86771129 100755 --- a/app/variables.gradle +++ b/app/variables.gradle @@ -1,6 +1,6 @@ /* Version */ -project.ext.vname = 'Build | Dark-Origin 1.0.5.6' -project.ext.vcode = 405 +project.ext.vname = 'Build | Dark-Origin 1.0.5.7' +project.ext.vcode = 410 project.ext.buildType = 'release' /* dimension */ diff --git a/orbotmanager/src/main/java/org/torproject/android/service/OrbotRawEventListener.java b/orbotmanager/src/main/java/org/torproject/android/service/OrbotRawEventListener.java index a9fd22a0..807a97b8 100644 --- a/orbotmanager/src/main/java/org/torproject/android/service/OrbotRawEventListener.java +++ b/orbotmanager/src/main/java/org/torproject/android/service/OrbotRawEventListener.java @@ -76,17 +76,18 @@ public class OrbotRawEventListener implements RawEventListener { } private void handleBandwidth(long read, long written) { - String message = OrbotService.formatBandwidthCount(mService, read) + " \u2193" + " / " + - OrbotService.formatBandwidthCount(mService, written) + " \u2191"; + try { + String message = OrbotService.formatBandwidthCount(mService, read) + " \u2193" + " / " + + OrbotService.formatBandwidthCount(mService, written) + " \u2191"; - if (mService.getCurrentStatus().equals(TorService.STATUS_ON)) - mService.showBandwidthNotification(message, read != 0 || written != 0); + if (mService.getCurrentStatus().equals(TorService.STATUS_ON)) + mService.showBandwidthNotification(message, read != 0 || written != 0); - mTotalBandwidthWritten += written; - mTotalBandwidthRead += read; - - mService.sendCallbackBandwidth(written, read, mTotalBandwidthWritten, mTotalBandwidthRead); + mTotalBandwidthWritten += written; + mTotalBandwidthRead += read; + mService.sendCallbackBandwidth(written, read, mTotalBandwidthWritten, mTotalBandwidthRead); + }catch (Exception ex){} } private void handleNewDescriptors(String[] descriptors) { diff --git a/orbotmanager/src/main/java/org/torproject/android/service/OrbotService.java b/orbotmanager/src/main/java/org/torproject/android/service/OrbotService.java index 13a5ffcf..ee4afa7d 100644 --- a/orbotmanager/src/main/java/org/torproject/android/service/OrbotService.java +++ b/orbotmanager/src/main/java/org/torproject/android/service/OrbotService.java @@ -746,9 +746,13 @@ public class OrbotService extends VpnService implements OrbotConstants { debug("torrc.custom=" + extraLines); - File fileTorRcCustom = TorService.getTorrc(this); - updateTorConfigCustom(fileTorRcCustom, extraLines.toString()); - return fileTorRcCustom; + try { + File fileTorRcCustom = TorService.getTorrc(this); + updateTorConfigCustom(fileTorRcCustom, extraLines.toString()); + return fileTorRcCustom; + + }catch (Exception ex){} + return null; } private String checkPortOrAuto(String portString) { @@ -875,10 +879,12 @@ public class OrbotService extends VpnService implements OrbotConstants { } private synchronized void startTorService() throws Exception { - updateTorConfigCustom(TorService.getDefaultsTorrc(this), - "DNSPort 0\n" + - "TransPort 0\n" + - "DisableNetwork 1\n"); + try { + updateTorConfigCustom(TorService.getDefaultsTorrc(this), + "DNSPort 0\n" + + "TransPort 0\n" + + "DisableNetwork 1\n"); + }catch (Exception ex){} var fileTorrcCustom = updateTorrcCustomFile(); if ((!fileTorrcCustom.exists()) || (!fileTorrcCustom.canRead())) @@ -892,50 +898,52 @@ public class OrbotService extends VpnService implements OrbotConstants { new Thread(){ public void run(){ - TorService torService = ((TorService.LocalBinder) iBinder).getService(); + try { + TorService torService = ((TorService.LocalBinder) iBinder).getService(); + + while ((conn = torService.getTorControlConnection())==null) + { + try { + sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } - while ((conn = torService.getTorControlConnection())==null) - { try { - sleep(500); + sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } - } - try { - sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } + mOrbotRawEventListener = new OrbotRawEventListener(OrbotService.this); - mOrbotRawEventListener = new OrbotRawEventListener(OrbotService.this); - - ArrayList events = new ArrayList<>(Arrays.asList(TorControlCommands.EVENT_OR_CONN_STATUS, - TorControlCommands.EVENT_CIRCUIT_STATUS, TorControlCommands.EVENT_NOTICE_MSG, - TorControlCommands.EVENT_WARN_MSG, TorControlCommands.EVENT_ERR_MSG, - TorControlCommands.EVENT_BANDWIDTH_USED, TorControlCommands.EVENT_NEW_DESC, - TorControlCommands.EVENT_ADDRMAP)); - if (Prefs.useDebugLogging()) { - events.add(TorControlCommands.EVENT_DEBUG_MSG); - events.add(TorControlCommands.EVENT_INFO_MSG); - } - if (Prefs.useDebugLogging() || Prefs.showExpandedNotifications()) - events.add(TorControlCommands.EVENT_STREAM_STATUS); - - if (conn != null) { - try { - conn.addRawEventListener(mOrbotRawEventListener); - conn.authenticate(new byte[0]); - conn.setEvents(events); - logNotice(getString(R.string.log_notice_added_event_handler)); - } catch (IOException e) { - e.printStackTrace(); + ArrayList events = new ArrayList<>(Arrays.asList(TorControlCommands.EVENT_OR_CONN_STATUS, + TorControlCommands.EVENT_CIRCUIT_STATUS, TorControlCommands.EVENT_NOTICE_MSG, + TorControlCommands.EVENT_WARN_MSG, TorControlCommands.EVENT_ERR_MSG, + TorControlCommands.EVENT_BANDWIDTH_USED, TorControlCommands.EVENT_NEW_DESC, + TorControlCommands.EVENT_ADDRMAP)); + if (Prefs.useDebugLogging()) { + events.add(TorControlCommands.EVENT_DEBUG_MSG); + events.add(TorControlCommands.EVENT_INFO_MSG); } + if (Prefs.useDebugLogging() || Prefs.showExpandedNotifications()) + events.add(TorControlCommands.EVENT_STREAM_STATUS); + + if (conn != null) { + try { + conn.addRawEventListener(mOrbotRawEventListener); + conn.authenticate(new byte[0]); + conn.setEvents(events); + logNotice(getString(R.string.log_notice_added_event_handler)); + } catch (IOException e) { + e.printStackTrace(); + } - initControlConnection(); - } + initControlConnection(); + } + }catch (Exception ex){} } }.start(); //moved torService to a local variable, since we only need it once @@ -960,12 +968,14 @@ public class OrbotService extends VpnService implements OrbotConstants { } }; - Intent serviceIntent = new Intent(this, TorService.class); - if (Build.VERSION.SDK_INT < 29) { - shouldUnbindTorService = bindService(serviceIntent, torServiceConnection, BIND_AUTO_CREATE); - } else { - shouldUnbindTorService = bindService(serviceIntent, BIND_AUTO_CREATE, mExecutor, torServiceConnection); - } + try { + Intent serviceIntent = new Intent(this, TorService.class); + if (Build.VERSION.SDK_INT < 29) { + shouldUnbindTorService = bindService(serviceIntent, torServiceConnection, BIND_AUTO_CREATE); + } else { + shouldUnbindTorService = bindService(serviceIntent, BIND_AUTO_CREATE, mExecutor, torServiceConnection); + } + }catch (Exception ex){} } private void sendLocalStatusOffBroadcast() { diff --git a/orbotmanager/src/main/java/org/torproject/android/service/TorServiceCustom.java b/orbotmanager/src/main/java/org/torproject/android/service/TorServiceCustom.java new file mode 100644 index 00000000..2db45f2f --- /dev/null +++ b/orbotmanager/src/main/java/org/torproject/android/service/TorServiceCustom.java @@ -0,0 +1,501 @@ +package org.torproject.android.service; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.FileObserver; +import android.os.IBinder; +import android.os.Process; +import android.util.Log; + +import net.freehaven.tor.control.RawEventListener; +import net.freehaven.tor.control.TorControlCommands; +import net.freehaven.tor.control.TorControlConnection; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +import androidx.annotation.Nullable; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +/** + * A {@link Service} that runs Tor. To control Tor via the {@code ControlPort}, + * first bind to this using {@link #bindService(Intent, android.content.ServiceConnection, int)}, + * then use {@link #getTorControlConnection()} to get an instance of + * {@link TorControlConnection} from {@code jtorctl}. If + * {@link TorControlCommands#EVENT_CIRCUIT_STATUS} is not included in + * {@link TorControlConnection#setEvents(java.util.List)}, then this service + * will not be able to function properly since it relies on those events to + * detect the state of Tor. + */ +public class TorServiceCustom extends Service { + + public static final String TAG = "TorService"; + + public static final String VERSION_NAME = org.torproject.jni.BuildConfig.VERSION_NAME; + + /** + * Request to transparently start Tor services. + */ + public static final String ACTION_START = "org.torproject.android.intent.action.START"; + + /** + * Internal request to stop Tor services. + */ + private static final String ACTION_STOP = "org.torproject.android.intent.action.STOP"; + + /** + * {@link Intent} sent by this app with {@code ON/OFF/STARTING/STOPPING} status + * included as an {@link #EXTRA_STATUS} {@code String}. Your app should + * always receive {@code ACTION_STATUS Intent}s since any other app could + * start Orbot. Also, user-triggered starts and stops will also cause + * {@code ACTION_STATUS Intent}s to be broadcast. + */ + public static final String ACTION_STATUS = "org.torproject.android.intent.action.STATUS"; + + public static final String ACTION_ERROR = "org.torproject.android.intent.action.ERROR"; + + /** + * {@code String} that contains a status constant: {@link #STATUS_ON}, + * {@link #STATUS_OFF}, {@link #STATUS_STARTING}, or + * {@link #STATUS_STOPPING} + */ + public static final String EXTRA_STATUS = "org.torproject.android.intent.extra.STATUS"; + + /** + * A {@link String} {@code packageName} for {@code TorService} to direct its + * status reply to, after receiving an {@link #ACTION_START}, + * {@link #ACTION_STOP}, or {@link #ACTION_STATUS} {@link Intent}. This allows + * {@code TorService} to send redundant replies to that single app, rather than + * broadcasting to all apps after every request. + */ + public final static String EXTRA_PACKAGE_NAME = "org.torproject.android.intent.extra.PACKAGE_NAME"; + + /** + * The {@link String} {@code packageName} of the app to which this {@code TorService} belongs. + * This allows broadcast receivers to distinguish between broadcasts from different apps that + * use {@code TorService}. + */ + public final static String EXTRA_SERVICE_PACKAGE_NAME = "org.torproject.android.intent.extra.SERVICE_PACKAGE_NAME"; + + /** + * All tor-related services and daemons are stopped + */ + public static final String STATUS_OFF = "OFF"; + /** + * All tor-related services and daemons have completed starting + */ + public static final String STATUS_ON = "ON"; + public static final String STATUS_STARTING = "STARTING"; + public static final String STATUS_STOPPING = "STOPPING"; + + /** + * @return a {@link File} pointing to the location of the optional + * {@code torrc} file. + * @see Tor configuration file format + */ + public static File getTorrc(Context context) { + return new File(getAppTorServiceDir(context), "torrc"); + } + + /** + * @return a {@link File} pointing to the location of the optional + * {@code torrc-defaults} file. + * @see Tor configuration file format + */ + public static File getDefaultsTorrc(Context context) { + return new File(getAppTorServiceDir(context), "torrc-defaults"); + } + + private static File getControlSocket(Context context) { + if (controlSocket == null) { + controlSocket = new File(getAppTorServiceDataDir(context), CONTROL_SOCKET_NAME); + } + return controlSocket; + } + + /** + * Get the directory that {@link TorServiceCustom} uses for: + *
    + *
  • writing {@code ControlPort.txt} // TODO + *
  • reading {@code torrc} and {@code torrc-defaults} + *
  • {@code DataDirectory} and {@code CacheDirectory} + *
  • the debug log file + *
+ */ + private static File getAppTorServiceDir(Context context) { + if (appTorServiceDir == null) { + appTorServiceDir = context.getDir(TorServiceCustom.class.getSimpleName(), MODE_PRIVATE); + } + return appTorServiceDir; + } + + /** + * Tor stores private, internal data in this directory. + */ + private static File getAppTorServiceDataDir(Context context) { + File dir = new File(getAppTorServiceDir(context), "data"); + dir.mkdir(); + if (!(dir.setReadable(true, true) && dir.setWritable(true, true) && dir.setExecutable(true, true))) { + throw new IllegalStateException("Cannot create " + dir); + } + return dir; + } + + static { + System.loadLibrary("tor"); + } + + volatile static String currentStatus = STATUS_OFF; + + private static File appTorServiceDir = null; + private static File controlSocket = null; + private static final String CONTROL_SOCKET_NAME = "ControlSocket"; + + public static int socksPort = -1; + public static int httpTunnelPort = -1; + + // Store the opaque reference as a long (pointer) for the native code + private long torConfiguration = -1; + private int torControlFd = -1; + + private TorControlConnection torControlConnection; + + /** + * This lock must be acquired before calling createTorConfiguration() and + * held until mainConfigurationFree() has been called. + */ + private static final ReentrantLock runLock = new ReentrantLock(); + + private native String apiGetProviderVersion(); + + private native boolean createTorConfiguration(); + + private native void mainConfigurationFree(); + + private native static FileDescriptor prepareFileDescriptor(String path); + + private native boolean mainConfigurationSetCommandLine(String[] args); + + private native boolean mainConfigurationSetupControlSocket(); + + private native int runMain(); + + + public class LocalBinder extends Binder { + public TorServiceCustom getService() { + return TorServiceCustom.this; + } + } + + private final IBinder binder = new LocalBinder(); + + @Override + public IBinder onBind(Intent intent) { + // TODO send broadcastStatus() here? + return binder; + } + + @Override + public void onCreate() { + super.onCreate(); + broadcastStatus(this, STATUS_STARTING); + startTorServiceThread(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + sendBroadcastStatusIntent(this); + return super.onStartCommand(intent, flags, startId); + } + + /** + * Announce Tor is available for connections once the first circuit is complete + */ + private final RawEventListener startedEventListener = new RawEventListener() { + @Override + public void onEvent(String keyword, String data) { + if (TorServiceCustom.STATUS_STARTING.equals(TorServiceCustom.currentStatus) + && TorControlCommands.EVENT_CIRCUIT_STATUS.equals(keyword) + && data != null && data.length() > 0) { + String[] tokenArray = data.split(" "); + if (tokenArray.length > 1 && TorControlCommands.CIRC_EVENT_BUILT.equals(tokenArray[1])) { + TorServiceCustom.broadcastStatus(TorServiceCustom.this, TorServiceCustom.STATUS_ON); + } + } + } + }; + + /** + * This waits for {@link #CONTROL_SOCKET_NAME} to be created by {@code tor}, + * then continues on to connect to the {@code ControlSocket} as described in + * {@link #getControlSocket(Context)}. As a failsafe, this will only wait + * 10 seconds, after that it will check whether the {@code ControlSocket} + * file exists, and if not, throw a {@link IllegalStateException}. + */ + private Thread controlPortThread = new Thread(CONTROL_SOCKET_NAME) { + @Override + public void run() { + android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + try { + final CountDownLatch countDownLatch = new CountDownLatch(1); + final String observeDir = getAppTorServiceDataDir(TorServiceCustom.this).getAbsolutePath(); + FileObserver controlPortFileObserver = new FileObserver(observeDir) { + @Override + public void onEvent(int event, @Nullable String name) { + if ((event & FileObserver.CREATE) > 0 && CONTROL_SOCKET_NAME.equals(name)) { + countDownLatch.countDown(); + } + } + }; + controlPortFileObserver.startWatching(); + controlPortThreadStarted.countDown(); + countDownLatch.await(10, TimeUnit.SECONDS); + controlPortFileObserver.stopWatching(); + File controlSocket = new File(observeDir, CONTROL_SOCKET_NAME); + if (!controlSocket.canRead()) { + return; + } + + FileDescriptor controlSocketFd = prepareFileDescriptor(getControlSocket(TorServiceCustom.this).getAbsolutePath()); + InputStream is = new FileInputStream(controlSocketFd); + OutputStream os = new FileOutputStream(controlSocketFd); + torControlConnection = new TorControlConnection(is, os); + torControlConnection.launchThread(true); + torControlConnection.authenticate(new byte[0]); + torControlConnection.addRawEventListener(startedEventListener); + torControlConnection.setEvents(Arrays.asList(TorControlCommands.EVENT_CIRCUIT_STATUS)); + + socksPort = getPortFromGetInfo("net/listeners/socks"); + httpTunnelPort = getPortFromGetInfo("net/listeners/httptunnel"); + + } catch (IOException | ArrayIndexOutOfBoundsException | InterruptedException e) { + e.printStackTrace(); + broadcastStatus(TorServiceCustom.this, STATUS_STOPPING); + TorServiceCustom.this.stopSelf(); + } + } + }; + + private CountDownLatch controlPortThreadStarted; + + private int getPortFromGetInfo(String key) { + final String value = getInfo(key); + return Integer.parseInt(value.substring(value.lastIndexOf(':') + 1, value.length() - 1)); + } + + /** + * Start Tor in a {@link Thread} with the minimum required config. The + * rest of the config should happen via the Control Port. First Tor + * runs with {@code --verify-config} to check the command line flags and + * any {@code torrc} config. If they are correct, then this waits for the + * {@link #controlPortThread} to start so it is running before Tor could + * potentially create the {@code ControlSocket}. Then finally Tor is + * started in its own {@code Thread}. + *

+ * Tor daemon does not output early debug messages to logcat, only after it + * tries to connect to the ports. So it is important that Tor does not run + * into port conflicts when first starting. + * + * @see #32036 output debug logs to logcat as early as possible on Android + * @see options that must be on the command line + */ + private void startTorServiceThread() { + final Context context = this.getApplicationContext(); + new Thread("tor") { + @Override + public void run() { + try { + String socksPort = "auto"; + if (isPortAvailable(9050)) { + socksPort = Integer.toString(9050); + } + String httpTunnelPort = "auto"; + if (isPortAvailable(8118)) { + httpTunnelPort = Integer.toString(8118); + } + + if (runLock.isLocked()) { + Log.i(TAG, "Waiting for lock"); + } + runLock.lock(); + Log.i(TAG, "Acquired lock"); + createTorConfiguration(); + ArrayList lines = new ArrayList<>(Arrays.asList("tor", "--verify-config", // must always be here + "--RunAsDaemon", "0", + "-f", getTorrc(context).getAbsolutePath(), + "--defaults-torrc", getDefaultsTorrc(context).getAbsolutePath(), + "--ignore-missing-torrc", + "--SyslogIdentityTag", TAG, + "--CacheDirectory", new File(getCacheDir(), TAG).getAbsolutePath(), + "--DataDirectory", getAppTorServiceDataDir(context).getAbsolutePath(), + "--ControlSocket", getControlSocket(context).getAbsolutePath(), + "--CookieAuthentication", "0", + "--SOCKSPort", socksPort, + "--HTTPTunnelPort", httpTunnelPort, + + // can be moved to ControlPort messages + "--LogMessageDomains", "1", + "--TruncateLogFile", "1" + )); + String[] verifyLines = lines.toArray(new String[0]); + if (!mainConfigurationSetCommandLine(verifyLines)) { + throw new IllegalArgumentException("Setting command line failed: " + Arrays.toString(verifyLines)); + } + int result = runMain(); // run verify + if (result != 0) { + throw new IllegalArgumentException("Bad command flags: " + Arrays.toString(verifyLines)); + } + + controlPortThreadStarted = new CountDownLatch(1); + controlPortThread.start(); + controlPortThreadStarted.await(); + + String[] runLines = new String[lines.size() - 1]; + runLines[0] = "tor"; + for (int i = 2; i < lines.size(); i++) { + runLines[i - 1] = lines.get(i); + } + if (!mainConfigurationSetCommandLine(runLines)) { + throw new IllegalArgumentException("Setting command line failed: " + Arrays.toString(runLines)); + } + if (!mainConfigurationSetupControlSocket()) { + throw new IllegalStateException("Setting up ControlPort failed!"); + } + if (runMain() != 0) { + throw new IllegalStateException("Tor could not start!"); + } + + } catch (IllegalStateException | IllegalArgumentException | InterruptedException e) { + e.printStackTrace(); + broadcastError(context, e); + } finally { + broadcastStatus(context, STATUS_STOPPING); + mainConfigurationFree(); + Log.i(TAG, "Releasing lock"); + runLock.unlock(); + TorServiceCustom.this.stopSelf(); + } + } + }.start(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (torControlConnection != null) { + torControlConnection.removeRawEventListener(startedEventListener); + } + shutdownTor(TorControlCommands.SIGNAL_SHUTDOWN); + broadcastStatus(TorServiceCustom.this, STATUS_OFF); + } + + public int getSocksPort() { + return socksPort; + } + + public int getHttpTunnelPort() { + return httpTunnelPort; + } + + /** + * @return the value or null on error + * @see control-spec GETINFO + */ + public String getInfo(String key) { + try { + return torControlConnection.getInfo(key); + } catch (IOException | NullPointerException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Send a signal to the Tor process to shut it down or halt it. + * Does not wait for a response, or report errors. + * + * @see TorControlConnection#shutdownTor(String) + */ + private void shutdownTor(String signal) { + try { + if (torControlConnection != null) { + torControlConnection.shutdownTor(signal); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Get an instance of {@link TorControlConnection} to control this instance + * of Tor, and configure it to set events. + * + * @see TorControlConnection#setEvents(java.util.List) + */ + public TorControlConnection getTorControlConnection() { + return torControlConnection; + } + + /** + * Broadcasts the current status to any apps following the status of TorService. + */ + static void sendBroadcastStatusIntent(Context context) { + Intent intent = getBroadcastIntent(ACTION_STATUS, currentStatus); + intent.putExtra(EXTRA_SERVICE_PACKAGE_NAME, context.getPackageName()); + context.sendBroadcast(intent); + } + + /** + * Sends the current status both internally and for any apps that need to + * follow the status of TorService. + */ + static void broadcastStatus(Context context, String currentStatus) { + TorServiceCustom.currentStatus = currentStatus; + Intent intent = getBroadcastIntent(ACTION_STATUS, currentStatus); + intent.putExtra(EXTRA_SERVICE_PACKAGE_NAME, context.getPackageName()); + LocalBroadcastManager.getInstance(context).sendBroadcast(intent); + context.sendBroadcast(intent); + } + + /** + * This might be better handled by {@link android.content.ServiceConnection} + * but there is no way to write tests for {@code ServiceConnection}. + */ + static void broadcastError(Context context, Throwable e) { + Intent intent = new Intent(ACTION_ERROR); + if (e != null) { + intent.putExtra(Intent.EXTRA_TEXT, e.getLocalizedMessage()); + } + LocalBroadcastManager.getInstance(context).sendBroadcast(intent); + context.sendBroadcast(intent); + } + + private static Intent getBroadcastIntent(String action, String currentStatus) { + Intent intent = new Intent(action); + intent.putExtra(EXTRA_STATUS, currentStatus); + return intent; + } + + private static boolean isPortAvailable(int port) { + try { + (new ServerSocket(port)).close(); + return true; + } catch (IOException e) { + // Could not connect. + return false; + } + } +}