bug fixes
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="JDK" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
|
|
||||||
apply plugin: 'kotlin-android-extensions'
|
|
||||||
apply plugin: 'maven'
|
apply plugin: 'maven'
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
|
@ -44,25 +41,15 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
|
||||||
implementation 'com.android.support:appcompat-v7:28.0.0'
|
implementation 'com.android.support:appcompat-v7:28.0.0'
|
||||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||||
testImplementation 'junit:junit:4.12'
|
|
||||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
|
||||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
|
||||||
implementation 'com.google.android.gms:play-services-ads:17.1.1'
|
implementation 'com.google.android.gms:play-services-ads:17.1.1'
|
||||||
implementation "cz.msebera.android:httpclient:4.4.1.2"
|
implementation "cz.msebera.android:httpclient:4.4.1.2"
|
||||||
implementation 'info.guardianproject.netcipher:netcipher:2.0.0-alpha1'
|
implementation 'info.guardianproject.netcipher:netcipher:2.0.0-alpha1'
|
||||||
implementation 'info.guardianproject.netcipher:netcipher-okhttp3:2.0.0-alpha1'
|
implementation 'info.guardianproject.netcipher:netcipher-okhttp3:2.0.0-alpha1'
|
||||||
implementation 'com.squareup.okhttp3:okhttp:3.4.2'
|
|
||||||
implementation 'org.apache.httpcomponents:httpcore:4.4.1'
|
|
||||||
x86Implementation "org.mozilla.geckoview:geckoview-${geckoviewChannel}-x86:${geckoviewVersion}"
|
|
||||||
x86_64Implementation "org.mozilla.geckoview:geckoview-${geckoviewChannel}-x86_64:${geckoviewVersion}"
|
|
||||||
armImplementation "org.mozilla.geckoview:geckoview-${geckoviewChannel}-armeabi-v7a:${geckoviewVersion}"
|
armImplementation "org.mozilla.geckoview:geckoview-${geckoviewChannel}-armeabi-v7a:${geckoviewVersion}"
|
||||||
aarch64Implementation "org.mozilla.geckoview:geckoview-${geckoviewChannel}-arm64-v8a:${geckoviewVersion}"
|
aarch64Implementation "org.mozilla.geckoview:geckoview-${geckoviewChannel}-arm64-v8a:${geckoviewVersion}"
|
||||||
implementation 'com.yarolegovich:lovely-dialog:1.1.0'
|
implementation 'com.yarolegovich:lovely-dialog:1.1.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
apply plugin: 'kotlin-android-extensions'
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<uses-library
|
<uses-library
|
||||||
android:name="org.apache.http.legacy"
|
android:name="org.apache.http.legacy"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
<activity android:name=".applicationController"
|
<activity android:name=".application_controller"
|
||||||
android:screenOrientation="portrait" >
|
android:screenOrientation="portrait" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
|
|
@ -1,639 +0,0 @@
|
||||||
package com.example.myapplication;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.net.ConnectivityManager;
|
|
||||||
import android.net.NetworkInfo;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.support.constraint.ConstraintLayout;
|
|
||||||
import android.support.v7.app.AppCompatActivity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Patterns;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.webkit.*;
|
|
||||||
import android.widget.*;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Stack;
|
|
||||||
|
|
||||||
import com.yarolegovich.lovelydialog.LovelyInfoDialog;
|
|
||||||
import com.yarolegovich.lovelydialog.LovelyStandardDialog;
|
|
||||||
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
|
||||||
import org.mozilla.gecko.PrefsHelper;
|
|
||||||
import org.mozilla.geckoview.GeckoRuntime;
|
|
||||||
import org.mozilla.geckoview.GeckoSession;
|
|
||||||
import org.mozilla.geckoview.GeckoView;
|
|
||||||
|
|
||||||
import static java.lang.Thread.sleep;
|
|
||||||
|
|
||||||
public class applicationController extends AppCompatActivity
|
|
||||||
{
|
|
||||||
|
|
||||||
/*View Objects*/
|
|
||||||
private WebView webView1;
|
|
||||||
private WebView webView2;
|
|
||||||
private GeckoView webLoader;
|
|
||||||
private ProgressBar progressBar;
|
|
||||||
private ConstraintLayout requestFailure;
|
|
||||||
private ConstraintLayout splashScreen;
|
|
||||||
private Button reloadButton;
|
|
||||||
private ImageButton homeButton;
|
|
||||||
private EditText searchbar;
|
|
||||||
private LinearLayout topbar;
|
|
||||||
private GeckoSession session1;
|
|
||||||
private GeckoRuntime runtime1;
|
|
||||||
|
|
||||||
/*helper Variables*/
|
|
||||||
String currentURL = "http://boogle.store/";
|
|
||||||
boolean isRequestError = false;
|
|
||||||
boolean hasApplicationLoaded = false;
|
|
||||||
Stack urlList = new Stack<String>();
|
|
||||||
boolean wasBackPressed = false;
|
|
||||||
boolean isFirstLaunch = true;
|
|
||||||
|
|
||||||
/*Initialization*/
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState)
|
|
||||||
{
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.applicationView);
|
|
||||||
initializeProxy();
|
|
||||||
initializeView();
|
|
||||||
initializeAds();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initializeAds()
|
|
||||||
{
|
|
||||||
|
|
||||||
admanager.getInstance().initialize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initializeProxy()
|
|
||||||
{
|
|
||||||
PrefsHelper.setPref("network.proxy.type",1); //manual proxy settings
|
|
||||||
PrefsHelper.setPref("network.proxy.socks","127.0.0.1"); //manual proxy settings
|
|
||||||
PrefsHelper.setPref("network.proxy.socks_port",9050); //manual proxy settings
|
|
||||||
PrefsHelper.setPref("network.proxy.socks_version",5); //manual proxy settings
|
|
||||||
PrefsHelper.setPref("network.proxy.socks_remote_dns",true); //manual proxy settings
|
|
||||||
PrefsHelper.setPref("browser.cache.disk.enable",false);
|
|
||||||
PrefsHelper.setPref("browser.cache.memory.enable",true);
|
|
||||||
PrefsHelper.setPref("browser.cache.disk.capacity",0);
|
|
||||||
PrefsHelper.setPref("general.useragent.override", "Mozilla/5.0 (Windows NT 6.1; rv:17.0) Gecko/20100101 Firefox/17.0");
|
|
||||||
PrefsHelper.setPref("privacy.donottrackheader.enabled",false);
|
|
||||||
PrefsHelper.setPref("privacy.donottrackheader.value",1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Initialization*/
|
|
||||||
public void initializeView()
|
|
||||||
{
|
|
||||||
/*if (android.os.Build.VERSION.SDK_INT > 9)
|
|
||||||
{
|
|
||||||
StrictMode.ThreadPolicy policy = new
|
|
||||||
StrictMode.ThreadPolicy.Builder().permitAll().build();
|
|
||||||
StrictMode.setThreadPolicy(policy);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
webView1 = (WebView) findViewById(R.id.pageLoader1);
|
|
||||||
webView2 = (WebView) findViewById(R.id.pageLoader2);
|
|
||||||
progressBar = (ProgressBar) findViewById(R.id.progressBar);
|
|
||||||
requestFailure = (ConstraintLayout) findViewById(R.id.requestFailure);
|
|
||||||
splashScreen = (ConstraintLayout) findViewById(R.id.splashScreen);
|
|
||||||
reloadButton = (Button) findViewById(R.id.reloadButton);
|
|
||||||
homeButton = (ImageButton) findViewById(R.id.home);
|
|
||||||
searchbar = (EditText) findViewById(R.id.search);
|
|
||||||
topbar = (LinearLayout) findViewById(R.id.topbar);
|
|
||||||
webRequestHandler.getInstance().initialization(webView1,webView2,progressBar,searchbar,requestFailure,this);
|
|
||||||
webView1.bringToFront();
|
|
||||||
progressBar.animate().alpha(0f);
|
|
||||||
|
|
||||||
Intent orbot = OrbotHelper.getOrbotStartIntent(getApplicationContext());
|
|
||||||
getApplicationContext().registerReceiver(orbotStatusReceiver,new IntentFilter(OrbotHelper.ACTION_STATUS));
|
|
||||||
getApplicationContext().sendBroadcast(orbot);
|
|
||||||
|
|
||||||
session1 = new GeckoSession();
|
|
||||||
webLoader = (GeckoView) findViewById(R.id.webLoader);
|
|
||||||
runtime1 = GeckoRuntime.create(this);
|
|
||||||
session1.open(runtime1);
|
|
||||||
webLoader.setSession(session1);
|
|
||||||
|
|
||||||
webView2.animate().setDuration(0).alpha(0f);
|
|
||||||
progressBar.setVisibility(View.INVISIBLE);
|
|
||||||
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
|
|
||||||
|
|
||||||
webView1.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
|
|
||||||
webView1.setBackgroundColor(Color.WHITE);
|
|
||||||
webView1.setWebViewClient(loadWebViewClient());
|
|
||||||
webView1.getSettings().setJavaScriptEnabled(true);
|
|
||||||
webView1.getSettings().setUseWideViewPort(true);
|
|
||||||
|
|
||||||
loadURLAnimate("http://boogle.store/");
|
|
||||||
|
|
||||||
webView2.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
|
|
||||||
webView2.setBackgroundColor(Color.WHITE);
|
|
||||||
webView2.setWebViewClient(loadWebViewClient());
|
|
||||||
webView2.getSettings().setJavaScriptEnabled(true);
|
|
||||||
webView2.getSettings().setUseWideViewPort(true);
|
|
||||||
|
|
||||||
requestFailure.animate().setDuration(0).alpha(0.0f);
|
|
||||||
initializeViewClients();
|
|
||||||
progressBar.animate().alpha(0f);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void openSession()
|
|
||||||
{
|
|
||||||
session1 = new GeckoSession();
|
|
||||||
webLoader = (GeckoView) findViewById(R.id.webLoader);
|
|
||||||
GeckoRuntime runtime1 = GeckoRuntime.create(this);
|
|
||||||
session1.open(runtime1);
|
|
||||||
webLoader.setSession(session1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeViewClients()
|
|
||||||
{
|
|
||||||
homeButton.setOnClickListener(new View.OnClickListener(){
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View v)
|
|
||||||
{
|
|
||||||
session1.stop();
|
|
||||||
session1.close();
|
|
||||||
session1.stop();
|
|
||||||
progressBar.animate().alpha(0f);
|
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
|
||||||
progressBar.animate().setDuration(300).alpha(1f);
|
|
||||||
loadURLAnimate("http://boogle.store/");
|
|
||||||
isRequestError = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
searchbar.setOnEditorActionListener(new EditText.OnEditorActionListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
session1.stop();
|
|
||||||
session1.close();
|
|
||||||
String url = v.getText().toString();
|
|
||||||
if(!url.startsWith("www.")&& !url.startsWith("http://")&& !url.startsWith("https://")){
|
|
||||||
url = "www."+url;
|
|
||||||
}
|
|
||||||
if(!url.startsWith("http://")&&!url.startsWith("https://")){
|
|
||||||
url = "http://"+url;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
boolean isUrlValid = Patterns.WEB_URL.matcher(url).matches();
|
|
||||||
|
|
||||||
URL host = new URL(url);
|
|
||||||
if(isUrlValid && host.getHost().replace("www.","").contains("."))
|
|
||||||
{
|
|
||||||
if(host.getHost().contains("boogle.store")||host.getHost().contains("genesis.store"))
|
|
||||||
{
|
|
||||||
loadURLAnimate(v.getText().toString());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if(host.getHost().contains(".onion"))
|
|
||||||
{
|
|
||||||
if(!reinitOrbot())
|
|
||||||
{
|
|
||||||
session1.close();
|
|
||||||
session1 = new GeckoSession();
|
|
||||||
session1.open(runtime1);
|
|
||||||
session1.setProgressDelegate(new ExamplesProgressDelegate());
|
|
||||||
webLoader.releaseSession();
|
|
||||||
webLoader.setSession(session1);
|
|
||||||
|
|
||||||
session1.loadUri(url);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
baseURLError();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
loadURLAnimate("http://boogle.store/search?q="+v.getText().toString().replaceAll(" ","+")+"&p_num=1&s_type=all");
|
|
||||||
}
|
|
||||||
reinitOrbot();
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
loadURLAnimate("http://boogle.store/search?q="+v.getText().toString().replaceAll(" ","+")+"&p_num=1&s_type=all");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
reloadButton.setOnClickListener(new View.OnClickListener(){
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View v)
|
|
||||||
{
|
|
||||||
progressBar.animate().alpha(0f);
|
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
|
||||||
progressBar.animate().setDuration(300).alpha(1f);
|
|
||||||
loadURLAnimate(currentURL);
|
|
||||||
isRequestError = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
session1.setProgressDelegate(new ExamplesProgressDelegate());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getDomainName(String url) throws URISyntaxException
|
|
||||||
{
|
|
||||||
URI uri = new URI(url);
|
|
||||||
String domain = uri.getHost();
|
|
||||||
return domain.startsWith("www.") ? domain.substring(4) : domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.M)
|
|
||||||
private void onChromeClientProgressChanged(int newProgress)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void baseURLError()
|
|
||||||
{
|
|
||||||
new LovelyInfoDialog(this)
|
|
||||||
.setTopColorRes(R.color.header)
|
|
||||||
.setIcon(R.drawable.logo)
|
|
||||||
.setTitle("Surface Web URL Not Allowed")
|
|
||||||
.setMessage("This software can only be used to search hidden web such as \"Onion\" and \"I2P\" for searching in Surface Web use \"Google\" or \"Bing\"")
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startingOrbotInfo()
|
|
||||||
{
|
|
||||||
new LovelyInfoDialog(this)
|
|
||||||
.setTopColorRes(R.color.header)
|
|
||||||
.setIcon(R.drawable.logo)
|
|
||||||
.setTitle("Orbot is Starting")
|
|
||||||
.setMessage("Looks Like Orbot is Installed but not Running. Please wait while we Start Orbot for you")
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean reinitOrbot()
|
|
||||||
{
|
|
||||||
if(!OrbotHelper.isOrbotInstalled(this))
|
|
||||||
{
|
|
||||||
OrbotHelper.getOrbotInstallIntent(this);
|
|
||||||
new LovelyStandardDialog(this)
|
|
||||||
.setTopColorRes(R.color.header)
|
|
||||||
.setIcon(R.drawable.logo)
|
|
||||||
.setTitle("Orbot Proxy Not Installed")
|
|
||||||
.setMessage("Hidden Web can only be access by Special Proxies. Please Install Orbot Proxy from Playstore")
|
|
||||||
.setPositiveButton(android.R.string.ok, new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
final String appPackageName = "org.torproject.android"; // getPackageName() from Context or Activity object
|
|
||||||
try {
|
|
||||||
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + appPackageName)));
|
|
||||||
} catch (android.content.ActivityNotFoundException anfe) {
|
|
||||||
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + appPackageName)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(android.R.string.no, null)
|
|
||||||
.show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if(!isOrbotRunning)
|
|
||||||
{
|
|
||||||
OrbotHelper.get(this).init();
|
|
||||||
startingOrbotInfo();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBackPressed()
|
|
||||||
{
|
|
||||||
reinitOrbot();
|
|
||||||
if(urlList.size()>0)
|
|
||||||
{
|
|
||||||
searchbar.setText(urlList.peek().toString().replaceAll("boogle.store","genesis.onion"));
|
|
||||||
if(urlList.peek().toString().contains("boogle.store"))
|
|
||||||
{
|
|
||||||
if(!currentURL.contains("boogle.store"))
|
|
||||||
{
|
|
||||||
currentURL = urlList.pop().toString();
|
|
||||||
progressBar.animate().alpha(0f);
|
|
||||||
webLoader.animate().setDuration(250).alpha(0);
|
|
||||||
webLoader.setVisibility(View.INVISIBLE);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
loadURLAnimate(urlList.pop().toString());
|
|
||||||
if(urlList.size()<=0)
|
|
||||||
{
|
|
||||||
currentURL = "http://boogle.store/";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
currentURL = urlList.peek().toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wasBackPressed = true;
|
|
||||||
if(urlList.size()<=0 || urlList.peek().toString().contains("boogle.store"))
|
|
||||||
{
|
|
||||||
currentURL = "http://boogle.store/";
|
|
||||||
webLoader.animate().setDuration(250).alpha(0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
webLoader.animate().setDuration(250).alpha(1);
|
|
||||||
currentURL = urlList.peek().toString();
|
|
||||||
}
|
|
||||||
urlList.pop();
|
|
||||||
session1.stop();
|
|
||||||
session1.close();
|
|
||||||
session1.stop();
|
|
||||||
session1.goBack();
|
|
||||||
}
|
|
||||||
if(urlList.size()==0)
|
|
||||||
{
|
|
||||||
searchbar.setText("http://genesis.onion/");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isAnimating = false;
|
|
||||||
private WebViewClient loadWebViewClient()
|
|
||||||
{
|
|
||||||
WebViewClient client = new WebViewClient()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public boolean shouldOverrideUrlLoading(WebView view, String url)
|
|
||||||
{
|
|
||||||
searchbar.setText(url.replaceAll("boogle.store","genesis.onion"));
|
|
||||||
|
|
||||||
if(!url.toString().contains("boogle"))
|
|
||||||
{
|
|
||||||
//admanager.getInstance().showAd();
|
|
||||||
|
|
||||||
boolean init_status=reinitOrbot();
|
|
||||||
if(!init_status)
|
|
||||||
{
|
|
||||||
session1.close();
|
|
||||||
session1 = new GeckoSession();
|
|
||||||
session1.open(runtime1);
|
|
||||||
session1.setProgressDelegate(new ExamplesProgressDelegate());
|
|
||||||
webLoader.releaseSession();
|
|
||||||
webLoader.setSession(session1);
|
|
||||||
|
|
||||||
session1.loadUri(url);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(urlList.size()==0 || !currentURL.equals(urlList.peek()))
|
|
||||||
{
|
|
||||||
urlList.add(currentURL);
|
|
||||||
currentURL = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadURLAnimate(url);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onPageFinished(WebView view, String url)
|
|
||||||
{
|
|
||||||
super.onPageFinished(view, url);
|
|
||||||
|
|
||||||
webView1.animate().setDuration(250).alpha(1f);
|
|
||||||
webView2.animate().setDuration(250).alpha(1f).withEndAction((new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
datamodel.getInstance().setIsLoadingURL(false);
|
|
||||||
requestFailure.animate().alpha(0f).setDuration(300).withEndAction((new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
requestFailure.setVisibility(View.INVISIBLE);
|
|
||||||
}
|
|
||||||
}));;
|
|
||||||
|
|
||||||
}
|
|
||||||
}));;
|
|
||||||
|
|
||||||
progressBar.animate().alpha(0f).withEndAction((new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
progressBar.setVisibility(View.INVISIBLE);
|
|
||||||
}
|
|
||||||
}));;
|
|
||||||
|
|
||||||
if(!hasApplicationLoaded)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
sleep(2000);
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
hasApplicationLoaded = true;
|
|
||||||
splashScreen.animate().alpha(0.0f).setDuration(300).setListener(null).withEndAction((new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
splashScreen.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if(!isRequestError)
|
|
||||||
{
|
|
||||||
requestFailure.animate().alpha(0.0f).setDuration(300).setListener(null).withEndAction((new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
requestFailure.setVisibility(View.GONE);
|
|
||||||
//reloadButton.setEnabled(false);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
isRequestError = false;
|
|
||||||
|
|
||||||
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
||||||
imm.hideSoftInputFromWindow(searchbar.getWindowToken(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl)
|
|
||||||
{
|
|
||||||
//reloadButton.setEnabled(true);
|
|
||||||
System.out.println("SUP2");
|
|
||||||
requestFailure.setVisibility(View.VISIBLE);
|
|
||||||
requestFailure.animate().alpha(1.0f);
|
|
||||||
isRequestError = true;
|
|
||||||
|
|
||||||
super.onReceivedError(view, errorCode, description, failingUrl);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if(!isNetworkAvailable())
|
|
||||||
{
|
|
||||||
hasApplicationLoaded = true;
|
|
||||||
splashScreen.animate().setStartDelay(2000).alpha(0.0f).setDuration(300).setListener(null).withEndAction((new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
splashScreen.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
requestFailure.setVisibility(View.VISIBLE);
|
|
||||||
requestFailure.animate().alpha(1.0f);
|
|
||||||
isRequestError = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadURLAnimate(String url)
|
|
||||||
{
|
|
||||||
if(!isAnimating)
|
|
||||||
{
|
|
||||||
webRequestHandler.getInstance().loadURL(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
boolean isPageLoaded = false;
|
|
||||||
class ExamplesProgressDelegate implements GeckoSession.ProgressDelegate
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onPageStart(GeckoSession session, String url)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
URL host = new URL(url);
|
|
||||||
if(!host.getHost().contains("onion"))
|
|
||||||
{
|
|
||||||
session1.stop();
|
|
||||||
session1.close();
|
|
||||||
session1.stop();
|
|
||||||
baseURLError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (MalformedURLException e)
|
|
||||||
{
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
isPageLoaded = false;
|
|
||||||
datamodel.getInstance().setIsLoadingURL(true);
|
|
||||||
boolean isBlackPage = url.equals("about:blank");
|
|
||||||
if(!isBlackPage &&!wasBackPressed)
|
|
||||||
{
|
|
||||||
urlList.add(currentURL);
|
|
||||||
currentURL = url;
|
|
||||||
}
|
|
||||||
wasBackPressed = false;
|
|
||||||
if(!isBlackPage)
|
|
||||||
{
|
|
||||||
progressBar.setAlpha(0);
|
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
|
||||||
progressBar.animate().setDuration(300).alpha(1f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onPageStop(GeckoSession session, boolean success)
|
|
||||||
{
|
|
||||||
progressBar.animate().alpha(0f);
|
|
||||||
datamodel.getInstance().setIsLoadingURL(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProgressChange(GeckoSession session, int progress)
|
|
||||||
{
|
|
||||||
if(progress>=50 && !isPageLoaded)
|
|
||||||
{
|
|
||||||
isPageLoaded = true;
|
|
||||||
webLoader.bringToFront();
|
|
||||||
webLoader.animate().setDuration(100).alpha(1);
|
|
||||||
webLoader.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
requestFailure.animate().alpha(0f).setDuration(300).withEndAction((new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
requestFailure.setVisibility(View.INVISIBLE);
|
|
||||||
}
|
|
||||||
}));;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isNetworkAvailable()
|
|
||||||
{
|
|
||||||
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
||||||
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
|
|
||||||
if (networkInfo != null && networkInfo.isConnected())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isOrbotRunning = false;
|
|
||||||
private BroadcastReceiver orbotStatusReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
if (TextUtils.equals(intent.getAction(),
|
|
||||||
OrbotHelper.ACTION_STATUS)) {
|
|
||||||
String status = intent.getStringExtra(OrbotHelper.EXTRA_STATUS);
|
|
||||||
if (status.equals(OrbotHelper.STATUS_ON))
|
|
||||||
{
|
|
||||||
isOrbotRunning = true;
|
|
||||||
}
|
|
||||||
else if (status.equals(OrbotHelper.STATUS_OFF))
|
|
||||||
{
|
|
||||||
isOrbotRunning = false;
|
|
||||||
}
|
|
||||||
else if (status.equals(OrbotHelper.STATUS_STARTING))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
else if (status.equals(OrbotHelper.STATUS_STOPPING))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -0,0 +1,481 @@
|
||||||
|
package com.example.myapplication;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.support.constraint.ConstraintLayout;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.util.Patterns;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.webkit.*;
|
||||||
|
import android.widget.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
||||||
|
import org.mozilla.gecko.PrefsHelper;
|
||||||
|
import org.mozilla.geckoview.GeckoRuntime;
|
||||||
|
import org.mozilla.geckoview.GeckoSession;
|
||||||
|
import org.mozilla.geckoview.GeckoView;
|
||||||
|
|
||||||
|
|
||||||
|
import static java.lang.Thread.sleep;
|
||||||
|
|
||||||
|
public class application_controller extends AppCompatActivity
|
||||||
|
{
|
||||||
|
|
||||||
|
/*View Objects*/
|
||||||
|
private WebView webView1;
|
||||||
|
private WebView webView2;
|
||||||
|
private GeckoView webLoader;
|
||||||
|
private ProgressBar progressBar;
|
||||||
|
private ConstraintLayout requestFailure;
|
||||||
|
private ConstraintLayout splashScreen;
|
||||||
|
private Button reloadButton;
|
||||||
|
private ImageButton homeButton;
|
||||||
|
private EditText searchbar;
|
||||||
|
private LinearLayout topbar;
|
||||||
|
private GeckoSession session1;
|
||||||
|
private GeckoRuntime runtime1;
|
||||||
|
private String version_code = "1.0";
|
||||||
|
|
||||||
|
/*helper Variables*/
|
||||||
|
Stack traceUrlList = new Stack<String>();
|
||||||
|
|
||||||
|
/*-------------------------------------------------------INITIALIZATION-------------------------------------------------------*/
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.application_view);
|
||||||
|
initializeProxy();
|
||||||
|
initializeConnections();
|
||||||
|
initializeOrbot();
|
||||||
|
initializeWebViews();
|
||||||
|
initializeView();
|
||||||
|
initializeAds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void versionChecker()
|
||||||
|
{
|
||||||
|
String version = preference_manager.getInstance().getString("version","none",this);
|
||||||
|
if(!version.equals(version_code) && !version.equals("none"))
|
||||||
|
{
|
||||||
|
message_manager.getInstance().versionWarning(this);
|
||||||
|
}
|
||||||
|
if(version.equals("none"))
|
||||||
|
{
|
||||||
|
webRequestHandler.getInstance().getVersion(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initializeAds()
|
||||||
|
{
|
||||||
|
admanager.getInstance().initialize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initializeProxy()
|
||||||
|
{
|
||||||
|
PrefsHelper.setPref("network.proxy.type",1); //manual proxy settings
|
||||||
|
PrefsHelper.setPref("network.proxy.socks","127.0.0.1"); //manual proxy settings
|
||||||
|
PrefsHelper.setPref("network.proxy.socks_port",9050); //manual proxy settings
|
||||||
|
PrefsHelper.setPref("network.proxy.socks_version",5); //manual proxy settings
|
||||||
|
PrefsHelper.setPref("network.proxy.socks_remote_dns",true); //manual proxy settings
|
||||||
|
PrefsHelper.setPref("browser.cache.disk.enable",false);
|
||||||
|
PrefsHelper.setPref("browser.cache.memory.enable",true);
|
||||||
|
PrefsHelper.setPref("browser.cache.disk.capacity",0);
|
||||||
|
PrefsHelper.setPref("general.useragent.override", "Mozilla/5.0 (Windows NT 6.1; rv:17.0) Gecko/20100101 Firefox/17.0");
|
||||||
|
PrefsHelper.setPref("privacy.donottrackheader.enabled",false);
|
||||||
|
PrefsHelper.setPref("privacy.donottrackheader.value",1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initializeConnections()
|
||||||
|
{
|
||||||
|
webView1 = findViewById(R.id.pageLoader1);
|
||||||
|
webView2 = findViewById(R.id.pageLoader2);
|
||||||
|
progressBar = findViewById(R.id.progressBar);
|
||||||
|
requestFailure = findViewById(R.id.requestFailure);
|
||||||
|
splashScreen = findViewById(R.id.splashScreen);
|
||||||
|
reloadButton = findViewById(R.id.reloadButton);
|
||||||
|
homeButton = findViewById(R.id.home);
|
||||||
|
searchbar = findViewById(R.id.search);
|
||||||
|
topbar = findViewById(R.id.topbar);
|
||||||
|
webLoader = findViewById(R.id.webLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initializeOrbot()
|
||||||
|
{
|
||||||
|
Intent orbot = OrbotHelper.getOrbotStartIntent(getApplicationContext());
|
||||||
|
getApplicationContext().registerReceiver(orbot_manager.getInstance().orbotStatusReceiver,new IntentFilter(OrbotHelper.ACTION_STATUS));
|
||||||
|
getApplicationContext().sendBroadcast(orbot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initializeWebViews()
|
||||||
|
{
|
||||||
|
webRequestHandler.getInstance().initialization(webView1,webView2,progressBar,searchbar,requestFailure,this);
|
||||||
|
webView1.bringToFront();
|
||||||
|
progressBar.animate().alpha(0f);
|
||||||
|
|
||||||
|
session1 = new GeckoSession();
|
||||||
|
runtime1 = GeckoRuntime.create(this);
|
||||||
|
session1.open(runtime1);
|
||||||
|
webLoader.setSession(session1);
|
||||||
|
session1.setProgressDelegate(new progressDelegate());
|
||||||
|
webLoader.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Initialization*/
|
||||||
|
public void initializeView()
|
||||||
|
{
|
||||||
|
webView1.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
|
||||||
|
webView1.setBackgroundColor(Color.WHITE);
|
||||||
|
webView1.setWebViewClient(loadWebViewClient());
|
||||||
|
webView1.getSettings().setJavaScriptEnabled(true);
|
||||||
|
webView1.getSettings().setUseWideViewPort(true);
|
||||||
|
|
||||||
|
webView2.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
|
||||||
|
webView2.setBackgroundColor(Color.WHITE);
|
||||||
|
webView2.setWebViewClient(loadWebViewClient());
|
||||||
|
webView2.getSettings().setJavaScriptEnabled(true);
|
||||||
|
webView2.getSettings().setUseWideViewPort(true);
|
||||||
|
|
||||||
|
webView2.animate().setDuration(0).alpha(0f);
|
||||||
|
progressBar.setVisibility(View.INVISIBLE);
|
||||||
|
requestFailure.animate().setDuration(0).alpha(0.0f);
|
||||||
|
progressBar.animate().alpha(0f);
|
||||||
|
|
||||||
|
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
|
||||||
|
loadURLAnimate(constants.backendUrl);
|
||||||
|
initializeViewClients();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeViewClients()
|
||||||
|
{
|
||||||
|
searchbar.setOnEditorActionListener((v, actionId, event) -> {
|
||||||
|
return onEditorClicked(v, actionId, event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*-------------------------------------------------------WEBVIEW LISTENERS-------------------------------------------------------*/
|
||||||
|
|
||||||
|
private WebViewClient loadWebViewClient()
|
||||||
|
{
|
||||||
|
WebViewClient client = new WebViewClient()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, String url)
|
||||||
|
{
|
||||||
|
searchbar.setText(url.replaceAll("boogle.store","genesis.onion"));
|
||||||
|
|
||||||
|
if(!url.toString().contains("boogle"))
|
||||||
|
{
|
||||||
|
//admanager.getInstance().showAd();
|
||||||
|
|
||||||
|
boolean init_status=orbot_manager.getInstance().reinitOrbot(application_controller.this);
|
||||||
|
if(!init_status)
|
||||||
|
{
|
||||||
|
session1.close();
|
||||||
|
session1 = new GeckoSession();
|
||||||
|
session1.open(runtime1);
|
||||||
|
session1.setProgressDelegate(new progressDelegate());
|
||||||
|
webLoader.releaseSession();
|
||||||
|
webLoader.setSession(session1);
|
||||||
|
|
||||||
|
session1.loadUri(url);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(traceUrlList.size()==0 || !status.currentURL.equals(traceUrlList.peek()))
|
||||||
|
{
|
||||||
|
traceUrlList.add(status.currentURL);
|
||||||
|
status.currentURL = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadURLAnimate(url);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onPageFinished(WebView view, String url)
|
||||||
|
{
|
||||||
|
super.onPageFinished(view, url);
|
||||||
|
|
||||||
|
webView1.animate().setDuration(250).alpha(1f);
|
||||||
|
webView2.animate().setDuration(250).alpha(1f).withEndAction((() -> {
|
||||||
|
datamodel.getInstance().setIsLoadingURL(false);
|
||||||
|
requestFailure.animate().alpha(0f).setDuration(300).withEndAction((() -> requestFailure.setVisibility(View.INVISIBLE)));;
|
||||||
|
|
||||||
|
}));;
|
||||||
|
|
||||||
|
progressBar.animate().alpha(0f).withEndAction((() -> progressBar.setVisibility(View.INVISIBLE)));;
|
||||||
|
|
||||||
|
if(!status.hasApplicationLoaded)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sleep(2000);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
status.hasApplicationLoaded = true;
|
||||||
|
splashScreen.animate().alpha(0.0f).setDuration(300).setListener(null).withEndAction((() -> splashScreen.setVisibility(View.GONE)));
|
||||||
|
versionChecker();
|
||||||
|
}
|
||||||
|
|
||||||
|
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
imm.hideSoftInputFromWindow(searchbar.getWindowToken(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl)
|
||||||
|
{
|
||||||
|
System.out.println("SUP2");
|
||||||
|
requestFailure.setVisibility(View.VISIBLE);
|
||||||
|
requestFailure.animate().alpha(1.0f);
|
||||||
|
loadErrorPage();
|
||||||
|
super.onReceivedError(view, errorCode, description, failingUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!helperMethod.isNetworkAvailable(this))
|
||||||
|
{
|
||||||
|
status.hasApplicationLoaded = true;
|
||||||
|
splashScreen.animate().setStartDelay(2000).alpha(0.0f).setDuration(300).setListener(null).withEndAction((new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
splashScreen.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
requestFailure.setVisibility(View.VISIBLE);
|
||||||
|
requestFailure.animate().alpha(1.0f);
|
||||||
|
loadErrorPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
class progressDelegate implements GeckoSession.ProgressDelegate
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onPageStart(GeckoSession session, String url)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
URL host = new URL(url);
|
||||||
|
if(!host.getHost().contains("onion"))
|
||||||
|
{
|
||||||
|
session1.stop();
|
||||||
|
session1.close();
|
||||||
|
session1.stop();
|
||||||
|
message_manager.getInstance().baseURLError(application_controller.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (MalformedURLException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
datamodel.getInstance().setIsLoadingURL(true);
|
||||||
|
boolean isBlackPage = url.equals("about:blank");
|
||||||
|
if(!isBlackPage)
|
||||||
|
{
|
||||||
|
traceUrlList.add(status.currentURL);
|
||||||
|
status.currentURL = url;
|
||||||
|
}
|
||||||
|
if(!isBlackPage)
|
||||||
|
{
|
||||||
|
progressBar.setAlpha(0);
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
progressBar.animate().setDuration(300).alpha(1f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onPageStop(GeckoSession session, boolean success)
|
||||||
|
{
|
||||||
|
progressBar.animate().alpha(0f);
|
||||||
|
datamodel.getInstance().setIsLoadingURL(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProgressChange(GeckoSession session, int progress)
|
||||||
|
{
|
||||||
|
if(progress>=50 && webLoader.getVisibility()==View.INVISIBLE)
|
||||||
|
{
|
||||||
|
webLoader.bringToFront();
|
||||||
|
webLoader.animate().setDuration(100).alpha(1);
|
||||||
|
webLoader.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
requestFailure.animate().alpha(0f).setDuration(300).withEndAction((() -> requestFailure.setVisibility(View.INVISIBLE)));;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*-------------------------------------------------------Helper Method-------------------------------------------------------*/
|
||||||
|
|
||||||
|
public void loadErrorPage()
|
||||||
|
{
|
||||||
|
requestFailure.animate().alpha(0.0f).setDuration(300).setListener(null).withEndAction((() -> {
|
||||||
|
requestFailure.setVisibility(View.GONE);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadURLAnimate(String url)
|
||||||
|
{
|
||||||
|
webRequestHandler.getInstance().loadURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*-------------------------------------------------------EVENT LISTENERS-------------------------------------------------------*/
|
||||||
|
|
||||||
|
public void onHomeButtonPressed(View view)
|
||||||
|
{
|
||||||
|
session1.stop();
|
||||||
|
session1.close();
|
||||||
|
session1.stop();
|
||||||
|
progressBar.animate().alpha(0f);
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
progressBar.animate().setDuration(300).alpha(1f);
|
||||||
|
loadURLAnimate("http://boogle.store/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onReloadButtonPressed(View view)
|
||||||
|
{
|
||||||
|
progressBar.animate().alpha(0f);
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
progressBar.animate().setDuration(300).alpha(1f);
|
||||||
|
loadURLAnimate(status.currentURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed()
|
||||||
|
{
|
||||||
|
orbot_manager.getInstance().reinitOrbot(this);
|
||||||
|
if(traceUrlList.size()>0)
|
||||||
|
{
|
||||||
|
searchbar.setText(traceUrlList.peek().toString().replaceAll("boogle.store","genesis.onion"));
|
||||||
|
if(traceUrlList.peek().toString().contains("boogle.store"))
|
||||||
|
{
|
||||||
|
if(!status.currentURL.contains("boogle.store"))
|
||||||
|
{
|
||||||
|
status.currentURL = traceUrlList.pop().toString();
|
||||||
|
progressBar.animate().alpha(0f);
|
||||||
|
webLoader.animate().setDuration(250).alpha(0);
|
||||||
|
webLoader.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
loadURLAnimate(traceUrlList.pop().toString());
|
||||||
|
if(traceUrlList.size()<=0)
|
||||||
|
{
|
||||||
|
status.currentURL = "http://boogle.store/";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
status.currentURL = traceUrlList.peek().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(traceUrlList.size()<=0 || traceUrlList.peek().toString().contains("boogle.store"))
|
||||||
|
{
|
||||||
|
status.currentURL = "http://boogle.store/";
|
||||||
|
webLoader.animate().setDuration(250).alpha(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
webLoader.animate().setDuration(250).alpha(1);
|
||||||
|
status.currentURL = traceUrlList.peek().toString();
|
||||||
|
}
|
||||||
|
traceUrlList.pop();
|
||||||
|
session1.stop();
|
||||||
|
session1.close();
|
||||||
|
session1.stop();
|
||||||
|
session1.goBack();
|
||||||
|
}
|
||||||
|
if(traceUrlList.size()==0)
|
||||||
|
{
|
||||||
|
searchbar.setText("http://genesis.onion/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onEditorClicked(TextView v, int actionId, KeyEvent event)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
session1.stop();
|
||||||
|
session1.close();
|
||||||
|
String url = v.getText().toString();
|
||||||
|
if(!url.startsWith("www.")&& !url.startsWith("http://")&& !url.startsWith("https://")){
|
||||||
|
url = "www."+url;
|
||||||
|
}
|
||||||
|
if(!url.startsWith("http://")&&!url.startsWith("https://")){
|
||||||
|
url = "http://"+url;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
boolean isUrlValid = Patterns.WEB_URL.matcher(url).matches();
|
||||||
|
|
||||||
|
URL host = new URL(url);
|
||||||
|
if(isUrlValid && host.getHost().replace("www.","").contains("."))
|
||||||
|
{
|
||||||
|
if(host.getHost().contains(constants.backendUrlHost)||host.getHost().contains(constants.frontEndUrlHost))
|
||||||
|
{
|
||||||
|
loadURLAnimate(v.getText().toString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if(host.getHost().contains(constants.allowedHost))
|
||||||
|
{
|
||||||
|
if(!orbot_manager.getInstance().reinitOrbot(this.getApplicationContext()))
|
||||||
|
{
|
||||||
|
session1.close();
|
||||||
|
session1 = new GeckoSession();
|
||||||
|
session1.open(runtime1);
|
||||||
|
session1.setProgressDelegate(new application_controller.progressDelegate());
|
||||||
|
webLoader.releaseSession();
|
||||||
|
webLoader.setSession(session1);
|
||||||
|
|
||||||
|
session1.loadUri(url);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
message_manager.getInstance().baseURLError(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
loadURLAnimate("http://boogle.store/search?q="+v.getText().toString().replaceAll(" ","+")+"&p_num=1&s_type=all");
|
||||||
|
}
|
||||||
|
orbot_manager.getInstance().reinitOrbot(this.getApplicationContext());
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
loadURLAnimate("http://boogle.store/search?q="+v.getText().toString().replaceAll(" ","+")+"&p_num=1&s_type=all");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.example.myapplication;
|
||||||
|
|
||||||
|
public class constants
|
||||||
|
{
|
||||||
|
public static String backendUrl = "http://boogle.store";
|
||||||
|
public static String backendUrlHost = "boogle.store";
|
||||||
|
public static String frontEndUrlHost = "genesis.store";
|
||||||
|
public static String allowedHost = ".onion";
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.example.myapplication;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
|
|
||||||
|
public class helperMethod
|
||||||
|
{
|
||||||
|
public static boolean isNetworkAvailable(Context application_context)
|
||||||
|
{
|
||||||
|
ConnectivityManager cm = (ConnectivityManager) application_context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
|
||||||
|
if (networkInfo != null && networkInfo.isConnected())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package com.example.myapplication;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.view.View;
|
||||||
|
import com.yarolegovich.lovelydialog.LovelyInfoDialog;
|
||||||
|
import com.yarolegovich.lovelydialog.LovelyStandardDialog;
|
||||||
|
|
||||||
|
public class message_manager {
|
||||||
|
private static final message_manager ourInstance = new message_manager();
|
||||||
|
|
||||||
|
public static message_manager getInstance() {
|
||||||
|
return ourInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private message_manager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void baseURLError(Context application_context)
|
||||||
|
{
|
||||||
|
new LovelyInfoDialog(application_context)
|
||||||
|
.setTopColorRes(R.color.header)
|
||||||
|
.setIcon(R.drawable.logo)
|
||||||
|
.setTitle("Surface Web URL Not Allowed")
|
||||||
|
.setMessage("This software can only be used to search hidden web such as \"Onion\" and \"I2P\" for searching in Surface Web use \"Google\" or \"Bing\"")
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startingOrbotInfo(Context application_context)
|
||||||
|
{
|
||||||
|
new LovelyInfoDialog(application_context)
|
||||||
|
.setTopColorRes(R.color.header)
|
||||||
|
.setIcon(R.drawable.logo)
|
||||||
|
.setTitle("Orbot is Starting")
|
||||||
|
.setMessage("Looks Like Orbot is Installed but not Running. Please wait while we Start Orbot for you")
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void versionWarning(Context application_context)
|
||||||
|
{
|
||||||
|
new LovelyStandardDialog(application_context)
|
||||||
|
.setTopColorRes(R.color.header)
|
||||||
|
.setIcon(R.drawable.logo)
|
||||||
|
.setTitle("Update Application")
|
||||||
|
.setMessage("A newer version is availabe please install to get better experience")
|
||||||
|
.setPositiveButton(android.R.string.ok, new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v)
|
||||||
|
{
|
||||||
|
String url = "http://boogle.store/android";
|
||||||
|
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||||
|
i.setData(Uri.parse(url));
|
||||||
|
application_context.startActivity(i);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.no, null)
|
||||||
|
.show();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package com.example.myapplication;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
|
import com.yarolegovich.lovelydialog.LovelyStandardDialog;
|
||||||
|
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
||||||
|
|
||||||
|
public class orbot_manager {
|
||||||
|
private static final orbot_manager ourInstance = new orbot_manager();
|
||||||
|
boolean isOrbotRunning = false;
|
||||||
|
|
||||||
|
public static orbot_manager getInstance() {
|
||||||
|
return ourInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private orbot_manager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public BroadcastReceiver orbotStatusReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (TextUtils.equals(intent.getAction(),
|
||||||
|
OrbotHelper.ACTION_STATUS)) {
|
||||||
|
String status = intent.getStringExtra(OrbotHelper.EXTRA_STATUS);
|
||||||
|
if (status.equals(OrbotHelper.STATUS_ON))
|
||||||
|
{
|
||||||
|
isOrbotRunning = true;
|
||||||
|
}
|
||||||
|
else if (status.equals(OrbotHelper.STATUS_OFF))
|
||||||
|
{
|
||||||
|
isOrbotRunning = false;
|
||||||
|
}
|
||||||
|
else if (status.equals(OrbotHelper.STATUS_STARTING))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
else if (status.equals(OrbotHelper.STATUS_STOPPING))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public boolean reinitOrbot(Context application_context)
|
||||||
|
{
|
||||||
|
if(!OrbotHelper.isOrbotInstalled(application_context))
|
||||||
|
{
|
||||||
|
OrbotHelper.getOrbotInstallIntent(application_context);
|
||||||
|
new LovelyStandardDialog(application_context)
|
||||||
|
.setTopColorRes(R.color.header)
|
||||||
|
.setIcon(R.drawable.logo)
|
||||||
|
.setTitle("Orbot Proxy Not Installed")
|
||||||
|
.setMessage("Hidden Web can only be access by Special Proxies. Please Install Orbot Proxy from Playstore")
|
||||||
|
.setPositiveButton(android.R.string.ok, new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
final String appPackageName = "org.torproject.android"; // getPackageName() from Context or Activity object
|
||||||
|
try {
|
||||||
|
application_context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + appPackageName)));
|
||||||
|
} catch (android.content.ActivityNotFoundException anfe) {
|
||||||
|
application_context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + appPackageName)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.no, null)
|
||||||
|
.show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(!isOrbotRunning)
|
||||||
|
{
|
||||||
|
OrbotHelper.get(application_context).init();
|
||||||
|
message_manager.getInstance().startingOrbotInfo(application_context);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.example.myapplication;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
|
public class preference_manager {
|
||||||
|
private static final preference_manager ourInstance = new preference_manager();
|
||||||
|
|
||||||
|
public static preference_manager getInstance() {
|
||||||
|
return ourInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private preference_manager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveString(String valueKey, String value, Context applicationContext) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext);
|
||||||
|
SharedPreferences.Editor edit = prefs.edit();
|
||||||
|
edit.putString(valueKey, value);
|
||||||
|
edit.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(String valueKey, String valueDefault,Context applicationContext) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext);
|
||||||
|
return prefs.getString(valueKey, valueDefault);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,357 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2014-2016 Hans-Christoph Steiner
|
|
||||||
* Copyright 2012-2016 Nathan Freitas
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.example.myapplication.proxy.netcipher;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.Proxy;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.security.KeyManagementException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
|
||||||
|
|
||||||
import info.guardianproject.netcipher.client.TlsOnlySocketFactory;
|
|
||||||
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
|
||||||
|
|
||||||
public class NetCipher {
|
|
||||||
private static final String TAG = "NetCipher";
|
|
||||||
|
|
||||||
private NetCipher() {
|
|
||||||
// this is a utility class with only static methods
|
|
||||||
}
|
|
||||||
|
|
||||||
public final static Proxy ORBOT_HTTP_PROXY = new Proxy(Proxy.Type.HTTP,
|
|
||||||
new InetSocketAddress("127.0.0.1", 8118));
|
|
||||||
|
|
||||||
private static Proxy proxy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the global HTTP proxy for all new {@link HttpURLConnection}s and
|
|
||||||
* {@link HttpsURLConnection}s that are created after this is called.
|
|
||||||
* <p>
|
|
||||||
* {@link #useTor()} will override this setting. Traffic must be directed
|
|
||||||
* to Tor using the proxy settings, and Orbot has its own proxy settings
|
|
||||||
* for connections that need proxies to work. So if "use Tor" is enabled,
|
|
||||||
* as tested by looking for the static instance of Proxy, then no other
|
|
||||||
* proxy settings are allowed to override the current Tor proxy.
|
|
||||||
*
|
|
||||||
* @param host the IP address for the HTTP proxy to use globally
|
|
||||||
* @param port the port number for the HTTP proxy to use globally
|
|
||||||
*/
|
|
||||||
public static void setProxy(String host, int port) {
|
|
||||||
if (!TextUtils.isEmpty(host) && port > 0) {
|
|
||||||
InetSocketAddress isa = new InetSocketAddress(host, port);
|
|
||||||
setProxy(new Proxy(Proxy.Type.HTTP, isa));
|
|
||||||
} else if (NetCipher.proxy != ORBOT_HTTP_PROXY) {
|
|
||||||
setProxy(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the global HTTP proxy for all new {@link HttpURLConnection}s and
|
|
||||||
* {@link HttpsURLConnection}s that are created after this is called.
|
|
||||||
* <p>
|
|
||||||
* {@link #useTor()} will override this setting. Traffic must be directed
|
|
||||||
* to Tor using the proxy settings, and Orbot has its own proxy settings
|
|
||||||
* for connections that need proxies to work. So if "use Tor" is enabled,
|
|
||||||
* as tested by looking for the static instance of Proxy, then no other
|
|
||||||
* proxy settings are allowed to override the current Tor proxy.
|
|
||||||
*
|
|
||||||
* @param proxy the HTTP proxy to use globally
|
|
||||||
*/
|
|
||||||
public static void setProxy(Proxy proxy) {
|
|
||||||
if (proxy != null && NetCipher.proxy == ORBOT_HTTP_PROXY) {
|
|
||||||
Log.w(TAG, "useTor is enabled, ignoring new proxy settings!");
|
|
||||||
} else {
|
|
||||||
NetCipher.proxy = proxy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the currently active global HTTP {@link Proxy}.
|
|
||||||
*
|
|
||||||
* @return the active HTTP {@link Proxy}
|
|
||||||
*/
|
|
||||||
public static Proxy getProxy() {
|
|
||||||
return proxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the global HTTP proxy for all new {@link HttpURLConnection}s and
|
|
||||||
* {@link HttpsURLConnection}s that are created after this is called. This
|
|
||||||
* returns things to the default, proxy-less state.
|
|
||||||
*/
|
|
||||||
public static void clearProxy() {
|
|
||||||
setProxy(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set Orbot as the global HTTP proxy for all new {@link HttpURLConnection}
|
|
||||||
* s and {@link HttpsURLConnection}s that are created after this is called.
|
|
||||||
* This overrides all future calls to {@link #setProxy(Proxy)}, except to
|
|
||||||
* clear the proxy, e.g. {@code #setProxy(null)} or {@link #clearProxy()}.
|
|
||||||
* <p>
|
|
||||||
* Traffic must be directed to Tor using the proxy settings, and Orbot has its
|
|
||||||
* own proxy settings for connections that need proxies to work. So if "use
|
|
||||||
* Tor" is enabled, as tested by looking for the static instance of Proxy,
|
|
||||||
* then no other proxy settings are allowed to override the current Tor proxy.
|
|
||||||
*/
|
|
||||||
public static void useTor() {
|
|
||||||
setProxy(ORBOT_HTTP_PROXY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a {@link TlsOnlySocketFactory} from NetCipher.
|
|
||||||
*
|
|
||||||
* @see HttpsURLConnection#setDefaultSSLSocketFactory(SSLSocketFactory)
|
|
||||||
*/
|
|
||||||
public static TlsOnlySocketFactory getTlsOnlySocketFactory() {
|
|
||||||
return getTlsOnlySocketFactory(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a {@link TlsOnlySocketFactory} from NetCipher, and specify whether
|
|
||||||
* it should use a more compatible, but less strong, suite of ciphers.
|
|
||||||
*
|
|
||||||
* @see HttpsURLConnection#setDefaultSSLSocketFactory(SSLSocketFactory)
|
|
||||||
*/
|
|
||||||
public static TlsOnlySocketFactory getTlsOnlySocketFactory(boolean compatible) {
|
|
||||||
SSLContext sslcontext;
|
|
||||||
try {
|
|
||||||
sslcontext = SSLContext.getInstance("TLSv1");
|
|
||||||
sslcontext.init(null, null, null);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalArgumentException(e);
|
|
||||||
} catch (KeyManagementException e) {
|
|
||||||
throw new IllegalArgumentException(e);
|
|
||||||
}
|
|
||||||
return new TlsOnlySocketFactory(sslcontext.getSocketFactory(), compatible);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a {@link HttpURLConnection} from a {@link URL}, and specify whether
|
|
||||||
* it should use a more compatible, but less strong, suite of ciphers.
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
* @param compatible
|
|
||||||
* @return the {@code url} in an instance of {@link HttpURLConnection}
|
|
||||||
* @throws IOException
|
|
||||||
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
|
||||||
*/
|
|
||||||
public static HttpURLConnection getHttpURLConnection(URL url, boolean compatible)
|
|
||||||
throws IOException {
|
|
||||||
// .onion addresses only work via Tor, so force Tor for all of them
|
|
||||||
Proxy proxy = NetCipher.proxy;
|
|
||||||
if (OrbotHelper.isOnionAddress(url))
|
|
||||||
proxy = ORBOT_HTTP_PROXY;
|
|
||||||
|
|
||||||
HttpURLConnection connection;
|
|
||||||
if (proxy != null) {
|
|
||||||
connection = (HttpURLConnection) url.openConnection(proxy);
|
|
||||||
} else {
|
|
||||||
connection = (HttpURLConnection) url.openConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connection instanceof HttpsURLConnection) {
|
|
||||||
HttpsURLConnection httpsConnection = ((HttpsURLConnection) connection);
|
|
||||||
SSLSocketFactory tlsOnly = getTlsOnlySocketFactory(compatible);
|
|
||||||
httpsConnection.setSSLSocketFactory(tlsOnly);
|
|
||||||
if (Build.VERSION.SDK_INT < 16) {
|
|
||||||
httpsConnection.setHostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a {@link HttpsURLConnection} from a URL {@link String} using the best
|
|
||||||
* TLS configuration available on the device.
|
|
||||||
*
|
|
||||||
* @param urlString
|
|
||||||
* @return the URL in an instance of {@link HttpsURLConnection}
|
|
||||||
* @throws IOException
|
|
||||||
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
|
||||||
* or if an HTTP URL is given that does not support HTTPS
|
|
||||||
*/
|
|
||||||
public static HttpsURLConnection getHttpsURLConnection(String urlString) throws IOException {
|
|
||||||
URL url = new URL(urlString.replaceFirst("^[Hh][Tt][Tt][Pp]:", "https:"));
|
|
||||||
return getHttpsURLConnection(url, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a {@link HttpsURLConnection} from a {@link Uri} using the best TLS
|
|
||||||
* configuration available on the device.
|
|
||||||
*
|
|
||||||
* @param uri
|
|
||||||
* @return the {@code uri} in an instance of {@link HttpsURLConnection}
|
|
||||||
* @throws IOException
|
|
||||||
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
|
||||||
* or if an HTTP URL is given that does not support HTTPS
|
|
||||||
*/
|
|
||||||
public static HttpsURLConnection getHttpsURLConnection(Uri uri) throws IOException {
|
|
||||||
return getHttpsURLConnection(uri.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a {@link HttpsURLConnection} from a {@link URI} using the best TLS
|
|
||||||
* configuration available on the device.
|
|
||||||
*
|
|
||||||
* @param uri
|
|
||||||
* @return the {@code uri} in an instance of {@link HttpsURLConnection}
|
|
||||||
* @throws IOException
|
|
||||||
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
|
||||||
* or if an HTTP URL is given that does not support HTTPS
|
|
||||||
*/
|
|
||||||
public static HttpsURLConnection getHttpsURLConnection(URI uri) throws IOException {
|
|
||||||
if (TextUtils.equals(uri.getScheme(), "https"))
|
|
||||||
return getHttpsURLConnection(uri.toURL(), false);
|
|
||||||
else
|
|
||||||
// otherwise force scheme to https
|
|
||||||
return getHttpsURLConnection(uri.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a {@link HttpsURLConnection} from a {@link URL} using the best TLS
|
|
||||||
* configuration available on the device.
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
* @return the {@code url} in an instance of {@link HttpsURLConnection}
|
|
||||||
* @throws IOException
|
|
||||||
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
|
||||||
* or if an HTTP URL is given that does not support HTTPS
|
|
||||||
*/
|
|
||||||
public static HttpsURLConnection getHttpsURLConnection(URL url) throws IOException {
|
|
||||||
return getHttpsURLConnection(url, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a {@link HttpsURLConnection} from a {@link URL} using a more
|
|
||||||
* compatible, but less strong, suite of ciphers.
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
* @return the {@code url} in an instance of {@link HttpsURLConnection}
|
|
||||||
* @throws IOException
|
|
||||||
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
|
||||||
* or if an HTTP URL is given that does not support HTTPS
|
|
||||||
*/
|
|
||||||
public static HttpsURLConnection getCompatibleHttpsURLConnection(URL url) throws IOException {
|
|
||||||
return getHttpsURLConnection(url, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a {@link HttpsURLConnection} from a {@link URL}, and specify whether
|
|
||||||
* it should use a more compatible, but less strong, suite of ciphers.
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
* @param compatible
|
|
||||||
* @return the {@code url} in an instance of {@link HttpsURLConnection}
|
|
||||||
* @throws IOException
|
|
||||||
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect,
|
|
||||||
* or if an HTTP URL is given that does not support HTTPS
|
|
||||||
*/
|
|
||||||
public static HttpsURLConnection getHttpsURLConnection(URL url, boolean compatible)
|
|
||||||
throws IOException {
|
|
||||||
// use default method, but enforce a HttpsURLConnection
|
|
||||||
HttpURLConnection connection = getHttpURLConnection(url, compatible);
|
|
||||||
if (connection instanceof HttpsURLConnection) {
|
|
||||||
return (HttpsURLConnection) connection;
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("not an HTTPS connection!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a {@link HttpURLConnection} from a {@link URL}. If the connection is
|
|
||||||
* {@code https://}, it will use a more compatible, but less strong, TLS
|
|
||||||
* configuration.
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
* @return the {@code url} in an instance of {@link HttpsURLConnection}
|
|
||||||
* @throws IOException
|
|
||||||
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
|
||||||
*/
|
|
||||||
public static HttpURLConnection getCompatibleHttpURLConnection(URL url) throws IOException {
|
|
||||||
return getHttpURLConnection(url, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a {@link HttpURLConnection} from a URL {@link String}. If it is an
|
|
||||||
* {@code https://} link, then this will use the best TLS configuration
|
|
||||||
* available on the device.
|
|
||||||
*
|
|
||||||
* @param urlString
|
|
||||||
* @return the URL in an instance of {@link HttpURLConnection}
|
|
||||||
* @throws IOException
|
|
||||||
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
|
||||||
*/
|
|
||||||
public static HttpURLConnection getHttpURLConnection(String urlString) throws IOException {
|
|
||||||
return getHttpURLConnection(new URL(urlString));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a {@link HttpURLConnection} from a {@link Uri}. If it is an
|
|
||||||
* {@code https://} link, then this will use the best TLS configuration
|
|
||||||
* available on the device.
|
|
||||||
*
|
|
||||||
* @param uri
|
|
||||||
* @return the {@code uri} in an instance of {@link HttpURLConnection}
|
|
||||||
* @throws IOException
|
|
||||||
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
|
||||||
*/
|
|
||||||
public static HttpURLConnection getHttpURLConnection(Uri uri) throws IOException {
|
|
||||||
return getHttpURLConnection(uri.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a {@link HttpURLConnection} from a {@link URI}. If it is an
|
|
||||||
* {@code https://} link, then this will use the best TLS configuration
|
|
||||||
* available on the device.
|
|
||||||
*
|
|
||||||
* @param uri
|
|
||||||
* @return the {@code uri} in an instance of {@link HttpURLConnection}
|
|
||||||
* @throws IOException
|
|
||||||
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
|
||||||
*/
|
|
||||||
public static HttpURLConnection getHttpURLConnection(URI uri) throws IOException {
|
|
||||||
return getHttpURLConnection(uri.toURL());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a {@link HttpURLConnection} from a {@link URL}. If it is an
|
|
||||||
* {@code https://} link, then this will use the best TLS configuration
|
|
||||||
* available on the device.
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
* @return the {@code url} in an instance of {@link HttpURLConnection}
|
|
||||||
* @throws IOException
|
|
||||||
* @throws IllegalArgumentException if the proxy or TLS setup is incorrect
|
|
||||||
*/
|
|
||||||
public static HttpURLConnection getHttpURLConnection(URL url) throws IOException {
|
|
||||||
return (HttpURLConnection) getHttpURLConnection(url, false);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,754 +0,0 @@
|
||||||
package com.example.myapplication.proxy.netcipher;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.ActivityNotFoundException;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Proxy;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
import android.util.ArrayMap;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.webkit.WebView;
|
|
||||||
|
|
||||||
import org.apache.http.HttpHost;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.Socket;
|
|
||||||
|
|
||||||
public class WebkitProxy {
|
|
||||||
|
|
||||||
private final static String DEFAULT_HOST = "localhost";//"127.0.0.1";
|
|
||||||
private final static int DEFAULT_PORT = 8118;
|
|
||||||
private final static int DEFAULT_SOCKS_PORT = 9050;
|
|
||||||
|
|
||||||
private final static int REQUEST_CODE = 0;
|
|
||||||
|
|
||||||
private final static String TAG = "OrbotHelpher";
|
|
||||||
|
|
||||||
public static boolean setProxy(String appClass, Context ctx, WebView wView, String host, int port) throws Exception {
|
|
||||||
|
|
||||||
setSystemProperties(host, port);
|
|
||||||
|
|
||||||
boolean worked = false;
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < 13) {
|
|
||||||
// worked = setWebkitProxyGingerbread(ctx, host, port);
|
|
||||||
setProxyUpToHC(wView, host, port);
|
|
||||||
} else if (Build.VERSION.SDK_INT < 19) {
|
|
||||||
worked = setWebkitProxyICS(ctx, host, port);
|
|
||||||
} else if (Build.VERSION.SDK_INT < 20) {
|
|
||||||
worked = setKitKatProxy(appClass, ctx, host, port);
|
|
||||||
|
|
||||||
if (!worked) //some kitkat's still use ICS browser component (like Cyanogen 11)
|
|
||||||
worked = setWebkitProxyICS(ctx, host, port);
|
|
||||||
|
|
||||||
} else if (Build.VERSION.SDK_INT >= 21) {
|
|
||||||
worked = setWebkitProxyLollipop(ctx, host, port);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return worked;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void setSystemProperties(String host, int port) {
|
|
||||||
|
|
||||||
System.setProperty("proxyHost", host);
|
|
||||||
System.setProperty("proxyPort", Integer.toString(port));
|
|
||||||
|
|
||||||
System.setProperty("http.proxyHost", host);
|
|
||||||
System.setProperty("http.proxyPort", Integer.toString(port));
|
|
||||||
|
|
||||||
System.setProperty("https.proxyHost", host);
|
|
||||||
System.setProperty("https.proxyPort", Integer.toString(port));
|
|
||||||
|
|
||||||
|
|
||||||
System.setProperty("socks.proxyHost", host);
|
|
||||||
System.setProperty("socks.proxyPort", Integer.toString(DEFAULT_SOCKS_PORT));
|
|
||||||
|
|
||||||
System.setProperty("socksProxyHost", host);
|
|
||||||
System.setProperty("socksProxyPort", Integer.toString(DEFAULT_SOCKS_PORT));
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
ProxySelector pSelect = new ProxySelector();
|
|
||||||
pSelect.addProxy(Proxy.Type.HTTP, host, port);
|
|
||||||
ProxySelector.setDefault(pSelect);
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
System.setProperty("http_proxy", "http://" + host + ":" + port);
|
|
||||||
System.setProperty("proxy-server", "http://" + host + ":" + port);
|
|
||||||
System.setProperty("host-resolver-rules","MAP * 0.0.0.0 , EXCLUDE myproxy");
|
|
||||||
System.getProperty("networkaddress.cache.ttl", "-1");
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void resetSystemProperties() {
|
|
||||||
|
|
||||||
System.setProperty("proxyHost", "");
|
|
||||||
System.setProperty("proxyPort", "");
|
|
||||||
|
|
||||||
System.setProperty("http.proxyHost", "");
|
|
||||||
System.setProperty("http.proxyPort", "");
|
|
||||||
|
|
||||||
System.setProperty("https.proxyHost", "");
|
|
||||||
System.setProperty("https.proxyPort", "");
|
|
||||||
|
|
||||||
|
|
||||||
System.setProperty("socks.proxyHost", "");
|
|
||||||
System.setProperty("socks.proxyPort", Integer.toString(DEFAULT_SOCKS_PORT));
|
|
||||||
|
|
||||||
System.setProperty("socksProxyHost", "");
|
|
||||||
System.setProperty("socksProxyPort", Integer.toString(DEFAULT_SOCKS_PORT));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override WebKit Proxy settings
|
|
||||||
*
|
|
||||||
* @param ctx Android ApplicationContext
|
|
||||||
* @param host
|
|
||||||
* @param port
|
|
||||||
* @return true if Proxy was successfully set
|
|
||||||
*/
|
|
||||||
private static boolean setWebkitProxyGingerbread(Context ctx, String host, int port)
|
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
boolean ret = false;
|
|
||||||
|
|
||||||
Object requestQueueObject = getRequestQueue(ctx);
|
|
||||||
if (requestQueueObject != null) {
|
|
||||||
// Create Proxy config object and set it into request Q
|
|
||||||
HttpHost httpHost = new HttpHost(host, port, "http");
|
|
||||||
setDeclaredField(requestQueueObject, "mProxyHost", httpHost);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set Proxy for Android 3.2 and below.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("all")
|
|
||||||
private static boolean setProxyUpToHC(WebView webview, String host, int port) {
|
|
||||||
Log.d(TAG, "Setting proxy with <= 3.2 API.");
|
|
||||||
|
|
||||||
HttpHost proxyServer = new HttpHost(host, port);
|
|
||||||
// Getting network
|
|
||||||
Class networkClass = null;
|
|
||||||
Object network = null;
|
|
||||||
try {
|
|
||||||
networkClass = Class.forName("android.webkit.Network");
|
|
||||||
if (networkClass == null) {
|
|
||||||
Log.e(TAG, "failed to get class for android.webkit.Network");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Method getInstanceMethod = networkClass.getMethod("getInstance", Context.class);
|
|
||||||
if (getInstanceMethod == null) {
|
|
||||||
Log.e(TAG, "failed to get getInstance method");
|
|
||||||
}
|
|
||||||
network = getInstanceMethod.invoke(networkClass, new Object[]{webview.getContext()});
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.e(TAG, "error getting network: " + ex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (network == null) {
|
|
||||||
Log.e(TAG, "error getting network: network is null");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Object requestQueue = null;
|
|
||||||
try {
|
|
||||||
Field requestQueueField = networkClass
|
|
||||||
.getDeclaredField("mRequestQueue");
|
|
||||||
requestQueue = getFieldValueSafely(requestQueueField, network);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.e(TAG, "error getting field value");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (requestQueue == null) {
|
|
||||||
Log.e(TAG, "Request queue is null");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Field proxyHostField = null;
|
|
||||||
try {
|
|
||||||
Class requestQueueClass = Class.forName("android.net.http.RequestQueue");
|
|
||||||
proxyHostField = requestQueueClass
|
|
||||||
.getDeclaredField("mProxyHost");
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.e(TAG, "error getting proxy host field");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean temp = proxyHostField.isAccessible();
|
|
||||||
try {
|
|
||||||
proxyHostField.setAccessible(true);
|
|
||||||
proxyHostField.set(requestQueue, proxyServer);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.e(TAG, "error setting proxy host");
|
|
||||||
} finally {
|
|
||||||
proxyHostField.setAccessible(temp);
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "Setting proxy with <= 3.2 API successful!");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static Object getFieldValueSafely(Field field, Object classInstance) throws IllegalArgumentException, IllegalAccessException {
|
|
||||||
boolean oldAccessibleValue = field.isAccessible();
|
|
||||||
field.setAccessible(true);
|
|
||||||
Object result = field.get(classInstance);
|
|
||||||
field.setAccessible(oldAccessibleValue);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean setWebkitProxyICS(Context ctx, String host, int port) {
|
|
||||||
|
|
||||||
// PSIPHON: added support for Android 4.x WebView proxy
|
|
||||||
try {
|
|
||||||
Class webViewCoreClass = Class.forName("android.webkit.WebViewCore");
|
|
||||||
|
|
||||||
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
|
||||||
if (webViewCoreClass != null && proxyPropertiesClass != null) {
|
|
||||||
Method m = webViewCoreClass.getDeclaredMethod("sendStaticMessage", Integer.TYPE,
|
|
||||||
Object.class);
|
|
||||||
Constructor c = proxyPropertiesClass.getConstructor(String.class, Integer.TYPE,
|
|
||||||
String.class);
|
|
||||||
|
|
||||||
if (m != null && c != null) {
|
|
||||||
m.setAccessible(true);
|
|
||||||
c.setAccessible(true);
|
|
||||||
Object properties = c.newInstance(host, port, null);
|
|
||||||
|
|
||||||
// android.webkit.WebViewCore.EventHub.PROXY_CHANGED = 193;
|
|
||||||
m.invoke(null, 193, properties);
|
|
||||||
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("ProxySettings",
|
|
||||||
"Exception setting WebKit proxy through android.net.ProxyProperties: "
|
|
||||||
+ e.toString());
|
|
||||||
} catch (Error e) {
|
|
||||||
Log.e("ProxySettings",
|
|
||||||
"Exception setting WebKit proxy through android.webkit.Network: "
|
|
||||||
+ e.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(19)
|
|
||||||
public static boolean resetKitKatProxy(String appClass, Context appContext) {
|
|
||||||
|
|
||||||
return setKitKatProxy(appClass, appContext, null, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(19)
|
|
||||||
private static boolean setKitKatProxy(String appClass, Context appContext, String host, int port) {
|
|
||||||
//Context appContext = webView.getContext().getApplicationContext();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Class applictionCls = Class.forName(appClass);
|
|
||||||
Field loadedApkField = applictionCls.getField("mLoadedApk");
|
|
||||||
loadedApkField.setAccessible(true);
|
|
||||||
Object loadedApk = loadedApkField.get(appContext);
|
|
||||||
Class loadedApkCls = Class.forName("android.app.LoadedApk");
|
|
||||||
Field receiversField = loadedApkCls.getDeclaredField("mReceivers");
|
|
||||||
receiversField.setAccessible(true);
|
|
||||||
ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk);
|
|
||||||
for (Object receiverMap : receivers.values()) {
|
|
||||||
for (Object rec : ((ArrayMap) receiverMap).keySet()) {
|
|
||||||
Class clazz = rec.getClass();
|
|
||||||
if (clazz.getName().contains("ProxyChangeListener")) {
|
|
||||||
Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class, Intent.class);
|
|
||||||
Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
|
|
||||||
|
|
||||||
if (host != null) {
|
|
||||||
/*********** optional, may be need in future *************/
|
|
||||||
final String CLASS_NAME = "android.net.ProxyProperties";
|
|
||||||
Class cls = Class.forName(CLASS_NAME);
|
|
||||||
Constructor constructor = cls.getConstructor(String.class, Integer.TYPE, String.class);
|
|
||||||
constructor.setAccessible(true);
|
|
||||||
Object proxyProperties = constructor.newInstance(host, port, null);
|
|
||||||
intent.putExtra("proxy", (Parcelable) proxyProperties);
|
|
||||||
/*********** optional, may be need in future *************/
|
|
||||||
}
|
|
||||||
|
|
||||||
onReceiveMethod.invoke(rec, appContext, intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
StringWriter sw = new StringWriter();
|
|
||||||
e.printStackTrace(new PrintWriter(sw));
|
|
||||||
String exceptionAsString = sw.toString();
|
|
||||||
Log.v(TAG, e.getMessage());
|
|
||||||
Log.v(TAG, exceptionAsString);
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
StringWriter sw = new StringWriter();
|
|
||||||
e.printStackTrace(new PrintWriter(sw));
|
|
||||||
String exceptionAsString = sw.toString();
|
|
||||||
Log.v(TAG, e.getMessage());
|
|
||||||
Log.v(TAG, exceptionAsString);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
StringWriter sw = new StringWriter();
|
|
||||||
e.printStackTrace(new PrintWriter(sw));
|
|
||||||
String exceptionAsString = sw.toString();
|
|
||||||
Log.v(TAG, e.getMessage());
|
|
||||||
Log.v(TAG, exceptionAsString);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
StringWriter sw = new StringWriter();
|
|
||||||
e.printStackTrace(new PrintWriter(sw));
|
|
||||||
String exceptionAsString = sw.toString();
|
|
||||||
Log.v(TAG, e.getMessage());
|
|
||||||
Log.v(TAG, exceptionAsString);
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
StringWriter sw = new StringWriter();
|
|
||||||
e.printStackTrace(new PrintWriter(sw));
|
|
||||||
String exceptionAsString = sw.toString();
|
|
||||||
Log.v(TAG, e.getMessage());
|
|
||||||
Log.v(TAG, exceptionAsString);
|
|
||||||
} catch (InvocationTargetException e) {
|
|
||||||
StringWriter sw = new StringWriter();
|
|
||||||
e.printStackTrace(new PrintWriter(sw));
|
|
||||||
String exceptionAsString = sw.toString();
|
|
||||||
Log.v(TAG, e.getMessage());
|
|
||||||
Log.v(TAG, exceptionAsString);
|
|
||||||
} catch (InstantiationException e) {
|
|
||||||
StringWriter sw = new StringWriter();
|
|
||||||
e.printStackTrace(new PrintWriter(sw));
|
|
||||||
String exceptionAsString = sw.toString();
|
|
||||||
Log.v(TAG, e.getMessage());
|
|
||||||
Log.v(TAG, exceptionAsString);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(21)
|
|
||||||
public static boolean resetLollipopProxy(String appClass, Context appContext) {
|
|
||||||
|
|
||||||
return setWebkitProxyLollipop(appContext, null, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://stackanswers.com/questions/25272393/android-webview-set-proxy-programmatically-on-android-l
|
|
||||||
@TargetApi(21) // for android.util.ArrayMap methods
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
private static boolean setWebkitProxyLollipop(Context appContext, String host, int port) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
Class applictionClass = Class.forName("android.app.Application");
|
|
||||||
Field mLoadedApkField = applictionClass.getDeclaredField("mLoadedApk");
|
|
||||||
mLoadedApkField.setAccessible(true);
|
|
||||||
Object mloadedApk = mLoadedApkField.get(appContext);
|
|
||||||
Class loadedApkClass = Class.forName("android.app.LoadedApk");
|
|
||||||
Field mReceiversField = loadedApkClass.getDeclaredField("mReceivers");
|
|
||||||
mReceiversField.setAccessible(true);
|
|
||||||
ArrayMap receivers = (ArrayMap) mReceiversField.get(mloadedApk);
|
|
||||||
for (Object receiverMap : receivers.values()) {
|
|
||||||
for (Object receiver : ((ArrayMap) receiverMap).keySet()) {
|
|
||||||
Class clazz = receiver.getClass();
|
|
||||||
if (clazz.getName().contains("ProxyChangeListener")) {
|
|
||||||
Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class, Intent.class);
|
|
||||||
Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
|
|
||||||
Object proxyInfo = null;
|
|
||||||
if (host != null) {
|
|
||||||
final String CLASS_NAME = "android.net.ProxyInfo";
|
|
||||||
Class cls = Class.forName(CLASS_NAME);
|
|
||||||
Method buildDirectProxyMethod = cls.getMethod("buildDirectProxy", String.class, Integer.TYPE);
|
|
||||||
proxyInfo = buildDirectProxyMethod.invoke(cls, host, port);
|
|
||||||
}
|
|
||||||
intent.putExtra("proxy", (Parcelable) proxyInfo);
|
|
||||||
onReceiveMethod.invoke(receiver, appContext, intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
Log.d("ProxySettings", "Exception setting WebKit proxy on Lollipop through ProxyChangeListener: " + e.toString());
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
Log.d("ProxySettings", "Exception setting WebKit proxy on Lollipop through ProxyChangeListener: " + e.toString());
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
Log.d("ProxySettings", "Exception setting WebKit proxy on Lollipop through ProxyChangeListener: " + e.toString());
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
Log.d("ProxySettings", "Exception setting WebKit proxy on Lollipop through ProxyChangeListener: " + e.toString());
|
|
||||||
} catch (InvocationTargetException e) {
|
|
||||||
Log.d("ProxySettings", "Exception setting WebKit proxy on Lollipop through ProxyChangeListener: " + e.toString());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean sendProxyChangedIntent(Context ctx, String host, int port) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
|
||||||
if (proxyPropertiesClass != null) {
|
|
||||||
Constructor c = proxyPropertiesClass.getConstructor(String.class, Integer.TYPE,
|
|
||||||
String.class);
|
|
||||||
|
|
||||||
if (c != null) {
|
|
||||||
c.setAccessible(true);
|
|
||||||
Object properties = c.newInstance(host, port, null);
|
|
||||||
|
|
||||||
Intent intent = new Intent(android.net.Proxy.PROXY_CHANGE_ACTION);
|
|
||||||
intent.putExtra("proxy", (Parcelable) properties);
|
|
||||||
ctx.sendBroadcast(intent);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("ProxySettings",
|
|
||||||
"Exception sending Intent ", e);
|
|
||||||
} catch (Error e) {
|
|
||||||
Log.e("ProxySettings",
|
|
||||||
"Exception sending Intent ", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
private static boolean setKitKatProxy0(Context ctx, String host, int port)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Class cmClass = Class.forName("android.net.ConnectivityManager");
|
|
||||||
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
|
||||||
if (cmClass != null && proxyPropertiesClass != null)
|
|
||||||
{
|
|
||||||
Constructor c = proxyPropertiesClass.getConstructor(String.class, Integer.TYPE,
|
|
||||||
String.class);
|
|
||||||
if (c != null)
|
|
||||||
{
|
|
||||||
c.setAccessible(true);
|
|
||||||
Object proxyProps = c.newInstance(host, port, null);
|
|
||||||
ConnectivityManager cm =
|
|
||||||
(ConnectivityManager)ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
||||||
Method mSetGlobalProxy = cmClass.getDeclaredMethod("setGlobalProxy", proxyPropertiesClass);
|
|
||||||
mSetGlobalProxy.invoke(cm, proxyProps);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.e("ProxySettings",
|
|
||||||
"ConnectivityManager.setGlobalProxy ",e);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
//CommandLine.initFromFile(COMMAND_LINE_FILE);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* private static boolean setKitKatProxy2 (Context ctx, String host, int port)
|
|
||||||
* {
|
|
||||||
* <p>
|
|
||||||
* String commandLinePath = "/data/local/tmp/orweb.conf";
|
|
||||||
* try
|
|
||||||
* {
|
|
||||||
* Class webViewCoreClass = Class.forName("org.chromium.content.common.CommandLine");
|
|
||||||
* <p>
|
|
||||||
* if (webViewCoreClass != null)
|
|
||||||
* {
|
|
||||||
* for (Method method : webViewCoreClass.getDeclaredMethods())
|
|
||||||
* {
|
|
||||||
* Log.d("Orweb","Proxy methods: " + method.getName());
|
|
||||||
* }
|
|
||||||
* <p>
|
|
||||||
* Method m = webViewCoreClass.getDeclaredMethod("initFromFile",
|
|
||||||
* String.class);
|
|
||||||
* <p>
|
|
||||||
* if (m != null)
|
|
||||||
* {
|
|
||||||
* m.setAccessible(true);
|
|
||||||
* m.invoke(null, commandLinePath);
|
|
||||||
* return true;
|
|
||||||
* }
|
|
||||||
* else
|
|
||||||
* return false;
|
|
||||||
* }
|
|
||||||
* } catch (Exception e)
|
|
||||||
* {
|
|
||||||
* Log.e("ProxySettings",
|
|
||||||
* "Exception setting WebKit proxy through android.net.ProxyProperties: "
|
|
||||||
* + e.toString());
|
|
||||||
* } catch (Error e)
|
|
||||||
* {
|
|
||||||
* Log.e("ProxySettings",
|
|
||||||
* "Exception setting WebKit proxy through android.webkit.Network: "
|
|
||||||
* + e.toString());
|
|
||||||
* }
|
|
||||||
* <p>
|
|
||||||
* return false;
|
|
||||||
* }
|
|
||||||
* <p>
|
|
||||||
* /**
|
|
||||||
* private static boolean setKitKatProxy (Context ctx, String host, int port)
|
|
||||||
* {
|
|
||||||
* <p>
|
|
||||||
* try
|
|
||||||
* {
|
|
||||||
* Class webViewCoreClass = Class.forName("android.net.Proxy");
|
|
||||||
* <p>
|
|
||||||
* Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
|
||||||
* if (webViewCoreClass != null && proxyPropertiesClass != null)
|
|
||||||
* {
|
|
||||||
* for (Method method : webViewCoreClass.getDeclaredMethods())
|
|
||||||
* {
|
|
||||||
* Log.d("Orweb","Proxy methods: " + method.getName());
|
|
||||||
* }
|
|
||||||
* <p>
|
|
||||||
* Method m = webViewCoreClass.getDeclaredMethod("setHttpProxySystemProperty",
|
|
||||||
* proxyPropertiesClass);
|
|
||||||
* Constructor c = proxyPropertiesClass.getConstructor(String.class, Integer.TYPE,
|
|
||||||
* String.class);
|
|
||||||
* <p>
|
|
||||||
* if (m != null && c != null)
|
|
||||||
* {
|
|
||||||
* m.setAccessible(true);
|
|
||||||
* c.setAccessible(true);
|
|
||||||
* Object properties = c.newInstance(host, port, null);
|
|
||||||
* <p>
|
|
||||||
* m.invoke(null, properties);
|
|
||||||
* return true;
|
|
||||||
* }
|
|
||||||
* else
|
|
||||||
* return false;
|
|
||||||
* }
|
|
||||||
* } catch (Exception e)
|
|
||||||
* {
|
|
||||||
* Log.e("ProxySettings",
|
|
||||||
* "Exception setting WebKit proxy through android.net.ProxyProperties: "
|
|
||||||
* + e.toString());
|
|
||||||
* } catch (Error e)
|
|
||||||
* {
|
|
||||||
* Log.e("ProxySettings",
|
|
||||||
* "Exception setting WebKit proxy through android.webkit.Network: "
|
|
||||||
* + e.toString());
|
|
||||||
* }
|
|
||||||
* <p>
|
|
||||||
* return false;
|
|
||||||
* }
|
|
||||||
* <p>
|
|
||||||
* private static boolean resetProxyForKitKat ()
|
|
||||||
* {
|
|
||||||
* <p>
|
|
||||||
* try
|
|
||||||
* {
|
|
||||||
* Class webViewCoreClass = Class.forName("android.net.Proxy");
|
|
||||||
* <p>
|
|
||||||
* Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
|
||||||
* if (webViewCoreClass != null && proxyPropertiesClass != null)
|
|
||||||
* {
|
|
||||||
* for (Method method : webViewCoreClass.getDeclaredMethods())
|
|
||||||
* {
|
|
||||||
* Log.d("Orweb","Proxy methods: " + method.getName());
|
|
||||||
* }
|
|
||||||
* <p>
|
|
||||||
* Method m = webViewCoreClass.getDeclaredMethod("setHttpProxySystemProperty",
|
|
||||||
* proxyPropertiesClass);
|
|
||||||
* <p>
|
|
||||||
* if (m != null)
|
|
||||||
* {
|
|
||||||
* m.setAccessible(true);
|
|
||||||
* <p>
|
|
||||||
* m.invoke(null, null);
|
|
||||||
* return true;
|
|
||||||
* }
|
|
||||||
* else
|
|
||||||
* return false;
|
|
||||||
* }
|
|
||||||
* } catch (Exception e)
|
|
||||||
* {
|
|
||||||
* Log.e("ProxySettings",
|
|
||||||
* "Exception setting WebKit proxy through android.net.ProxyProperties: "
|
|
||||||
* + e.toString());
|
|
||||||
* } catch (Error e)
|
|
||||||
* {
|
|
||||||
* Log.e("ProxySettings",
|
|
||||||
* "Exception setting WebKit proxy through android.webkit.Network: "
|
|
||||||
* + e.toString());
|
|
||||||
* }
|
|
||||||
* <p>
|
|
||||||
* return false;
|
|
||||||
* }
|
|
||||||
**/
|
|
||||||
|
|
||||||
public static void resetProxy(String appClass, Context ctx) throws Exception {
|
|
||||||
|
|
||||||
resetSystemProperties();
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < 14) {
|
|
||||||
resetProxyForGingerBread(ctx);
|
|
||||||
} else if (Build.VERSION.SDK_INT < 19) {
|
|
||||||
resetProxyForICS();
|
|
||||||
} else if (Build.VERSION.SDK_INT < 20) {
|
|
||||||
resetKitKatProxy(appClass, ctx);
|
|
||||||
} else if (Build.VERSION.SDK_INT >= 21) {
|
|
||||||
resetLollipopProxy(appClass, ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void resetProxyForICS() throws Exception {
|
|
||||||
try {
|
|
||||||
Class webViewCoreClass = Class.forName("android.webkit.WebViewCore");
|
|
||||||
Class proxyPropertiesClass = Class.forName("android.net.ProxyProperties");
|
|
||||||
if (webViewCoreClass != null && proxyPropertiesClass != null) {
|
|
||||||
Method m = webViewCoreClass.getDeclaredMethod("sendStaticMessage", Integer.TYPE,
|
|
||||||
Object.class);
|
|
||||||
|
|
||||||
if (m != null) {
|
|
||||||
m.setAccessible(true);
|
|
||||||
|
|
||||||
// android.webkit.WebViewCore.EventHub.PROXY_CHANGED = 193;
|
|
||||||
m.invoke(null, 193, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("ProxySettings",
|
|
||||||
"Exception setting WebKit proxy through android.net.ProxyProperties: "
|
|
||||||
+ e.toString());
|
|
||||||
throw e;
|
|
||||||
} catch (Error e) {
|
|
||||||
Log.e("ProxySettings",
|
|
||||||
"Exception setting WebKit proxy through android.webkit.Network: "
|
|
||||||
+ e.toString());
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void resetProxyForGingerBread(Context ctx) throws Exception {
|
|
||||||
Object requestQueueObject = getRequestQueue(ctx);
|
|
||||||
if (requestQueueObject != null) {
|
|
||||||
setDeclaredField(requestQueueObject, "mProxyHost", null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Object getRequestQueue(Context ctx) throws Exception {
|
|
||||||
Object ret = null;
|
|
||||||
Class networkClass = Class.forName("android.webkit.Network");
|
|
||||||
if (networkClass != null) {
|
|
||||||
Object networkObj = invokeMethod(networkClass, "getInstance", new Object[]{
|
|
||||||
ctx
|
|
||||||
}, Context.class);
|
|
||||||
if (networkObj != null) {
|
|
||||||
ret = getDeclaredField(networkObj, "mRequestQueue");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object getDeclaredField(Object obj, String name)
|
|
||||||
throws SecurityException, NoSuchFieldException,
|
|
||||||
IllegalArgumentException, IllegalAccessException {
|
|
||||||
Field f = obj.getClass().getDeclaredField(name);
|
|
||||||
f.setAccessible(true);
|
|
||||||
Object out = f.get(obj);
|
|
||||||
// System.out.println(obj.getClass().getName() + "." + name + " = "+
|
|
||||||
// out);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void setDeclaredField(Object obj, String name, Object value)
|
|
||||||
throws SecurityException, NoSuchFieldException,
|
|
||||||
IllegalArgumentException, IllegalAccessException {
|
|
||||||
Field f = obj.getClass().getDeclaredField(name);
|
|
||||||
f.setAccessible(true);
|
|
||||||
f.set(obj, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object invokeMethod(Object object, String methodName, Object[] params,
|
|
||||||
Class... types) throws Exception {
|
|
||||||
Object out = null;
|
|
||||||
Class c = object instanceof Class ? (Class) object : object.getClass();
|
|
||||||
if (types != null) {
|
|
||||||
Method method = c.getMethod(methodName, types);
|
|
||||||
out = method.invoke(object, params);
|
|
||||||
} else {
|
|
||||||
Method method = c.getMethod(methodName);
|
|
||||||
out = method.invoke(object);
|
|
||||||
}
|
|
||||||
// System.out.println(object.getClass().getName() + "." + methodName +
|
|
||||||
// "() = "+ out);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Socket getSocket(Context context, String proxyHost, int proxyPort)
|
|
||||||
throws IOException {
|
|
||||||
Socket sock = new Socket();
|
|
||||||
|
|
||||||
sock.connect(new InetSocketAddress(proxyHost, proxyPort), 10000);
|
|
||||||
|
|
||||||
return sock;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Socket getSocket(Context context) throws IOException {
|
|
||||||
return getSocket(context, DEFAULT_HOST, DEFAULT_SOCKS_PORT);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AlertDialog initOrbot(Activity activity,
|
|
||||||
CharSequence stringTitle,
|
|
||||||
CharSequence stringMessage,
|
|
||||||
CharSequence stringButtonYes,
|
|
||||||
CharSequence stringButtonNo,
|
|
||||||
CharSequence stringDesiredBarcodeFormats) {
|
|
||||||
Intent intentScan = new Intent("org.torproject.android.START_TOR");
|
|
||||||
intentScan.addCategory(Intent.CATEGORY_DEFAULT);
|
|
||||||
|
|
||||||
try {
|
|
||||||
activity.startActivityForResult(intentScan, REQUEST_CODE);
|
|
||||||
return null;
|
|
||||||
} catch (ActivityNotFoundException e) {
|
|
||||||
return showDownloadDialog(activity, stringTitle, stringMessage, stringButtonYes,
|
|
||||||
stringButtonNo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AlertDialog showDownloadDialog(final Activity activity,
|
|
||||||
CharSequence stringTitle,
|
|
||||||
CharSequence stringMessage,
|
|
||||||
CharSequence stringButtonYes,
|
|
||||||
CharSequence stringButtonNo) {
|
|
||||||
AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity);
|
|
||||||
downloadDialog.setTitle(stringTitle);
|
|
||||||
downloadDialog.setMessage(stringMessage);
|
|
||||||
downloadDialog.setPositiveButton(stringButtonYes, new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
|
||||||
Uri uri = Uri.parse("market://search?q=pname:org.torproject.android");
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
|
||||||
activity.startActivity(intent);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
downloadDialog.setNegativeButton(stringButtonNo, new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return downloadDialog.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,157 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2016 CommonsWare, LLC
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package info.guardianproject.netcipher.client;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.KeyManagementException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
import javax.net.ssl.TrustManager;
|
|
||||||
|
|
||||||
public interface StrongBuilder<T extends StrongBuilder, C> {
|
|
||||||
/**
|
|
||||||
* Callback to get a connection handed to you for use,
|
|
||||||
* already set up for NetCipher.
|
|
||||||
*
|
|
||||||
* @param <C> the type of connection created by this builder
|
|
||||||
*/
|
|
||||||
interface Callback<C> {
|
|
||||||
/**
|
|
||||||
* Called when the NetCipher-enhanced connection is ready
|
|
||||||
* for use.
|
|
||||||
*
|
|
||||||
* @param connection the connection
|
|
||||||
*/
|
|
||||||
void onConnected(C connection);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called if we tried to connect through to Orbot but failed
|
|
||||||
* for some reason
|
|
||||||
*
|
|
||||||
* @param e the reason
|
|
||||||
*/
|
|
||||||
void onConnectionException(Exception e);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called if our attempt to get a status from Orbot failed
|
|
||||||
* after a defined period of time. See statusTimeout() on
|
|
||||||
* OrbotInitializer.
|
|
||||||
*/
|
|
||||||
void onTimeout();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called if you requested validation that we are connecting
|
|
||||||
* through Tor, and while we were able to connect to Orbot, that
|
|
||||||
* validation failed.
|
|
||||||
*/
|
|
||||||
void onInvalid();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call this to configure the Tor proxy from the results
|
|
||||||
* returned by Orbot, using the best available proxy
|
|
||||||
* (SOCKS if possible, else HTTP)
|
|
||||||
*
|
|
||||||
* @return the builder
|
|
||||||
*/
|
|
||||||
T withBestProxy();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if this builder supports HTTP proxies, false
|
|
||||||
* otherwise
|
|
||||||
*/
|
|
||||||
boolean supportsHttpProxy();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call this to configure the Tor proxy from the results
|
|
||||||
* returned by Orbot, using the HTTP proxy.
|
|
||||||
*
|
|
||||||
* @return the builder
|
|
||||||
*/
|
|
||||||
T withHttpProxy();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if this builder supports SOCKS proxies, false
|
|
||||||
* otherwise
|
|
||||||
*/
|
|
||||||
boolean supportsSocksProxy();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call this to configure the Tor proxy from the results
|
|
||||||
* returned by Orbot, using the SOCKS proxy.
|
|
||||||
*
|
|
||||||
* @return the builder
|
|
||||||
*/
|
|
||||||
T withSocksProxy();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies your own custom TrustManagers, such as for
|
|
||||||
* replacing the stock keystore support with a custom
|
|
||||||
* keystore.
|
|
||||||
*
|
|
||||||
* @param trustManagers the TrustManagers to use
|
|
||||||
* @return the builder
|
|
||||||
*/
|
|
||||||
T withTrustManagers(TrustManager[] trustManagers)
|
|
||||||
throws NoSuchAlgorithmException, KeyManagementException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call this if you want a weaker set of supported ciphers,
|
|
||||||
* because you are running into compatibility problems with
|
|
||||||
* some server due to a cipher mismatch. The better solution
|
|
||||||
* is to fix the server.
|
|
||||||
*
|
|
||||||
* @return the builder
|
|
||||||
*/
|
|
||||||
T withWeakCiphers();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call this if you want the builder to confirm that we are
|
|
||||||
* communicating over Tor, by reaching out to a Tor test
|
|
||||||
* server and confirming our connection status. By default,
|
|
||||||
* this is skipped. Adding this check adds security, but it
|
|
||||||
* has the chance of false negatives (e.g., we cannot reach
|
|
||||||
* that Tor server for some reason).
|
|
||||||
*
|
|
||||||
* @return the builder
|
|
||||||
*/
|
|
||||||
T withTorValidation();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a connection, applying the configuration already
|
|
||||||
* specified in the builder.
|
|
||||||
*
|
|
||||||
* @param status status Intent from OrbotInitializer
|
|
||||||
* @return the connection
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
C build(Intent status) throws Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asynchronous version of build(), one that uses OrbotInitializer
|
|
||||||
* internally to get the status and checks the validity of the Tor
|
|
||||||
* connection (if requested). Note that your callback methods may
|
|
||||||
* be invoked on any thread; do not assume that they will be called
|
|
||||||
* on any particular thread.
|
|
||||||
*
|
|
||||||
* @param callback Callback to get a connection handed to you
|
|
||||||
* for use, already set up for NetCipher
|
|
||||||
*/
|
|
||||||
void build(Callback<C> callback);
|
|
||||||
}
|
|
|
@ -1,166 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2016 CommonsWare, LLC
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package info.guardianproject.netcipher.client;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.Proxy;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds an HttpUrlConnection that connects via Tor through
|
|
||||||
* Orbot.
|
|
||||||
*/
|
|
||||||
public class StrongConnectionBuilder
|
|
||||||
extends StrongBuilderBase<StrongConnectionBuilder, HttpURLConnection> {
|
|
||||||
private URL url;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a StrongConnectionBuilder using the strongest set
|
|
||||||
* of options for security. Use this if the strongest set of
|
|
||||||
* options is what you want; otherwise, create a
|
|
||||||
* builder via the constructor and configure it as you see fit.
|
|
||||||
*
|
|
||||||
* @param context any Context will do
|
|
||||||
* @return a configured StrongConnectionBuilder
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
static public StrongConnectionBuilder forMaxSecurity(Context context)
|
|
||||||
throws Exception {
|
|
||||||
return (new StrongConnectionBuilder(context)
|
|
||||||
.withBestProxy());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a builder instance.
|
|
||||||
*
|
|
||||||
* @param context any Context will do; builder will hold onto
|
|
||||||
* Application context
|
|
||||||
*/
|
|
||||||
public StrongConnectionBuilder(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy constructor.
|
|
||||||
*
|
|
||||||
* @param original builder to clone
|
|
||||||
*/
|
|
||||||
public StrongConnectionBuilder(StrongConnectionBuilder original) {
|
|
||||||
super(original);
|
|
||||||
this.url = original.url;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
public boolean supportsSocksProxy() {
|
|
||||||
return(false);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the URL to build a connection for.
|
|
||||||
*
|
|
||||||
* @param url the URL
|
|
||||||
* @return the builder
|
|
||||||
* @throws MalformedURLException
|
|
||||||
*/
|
|
||||||
public StrongConnectionBuilder connectTo(String url)
|
|
||||||
throws MalformedURLException {
|
|
||||||
connectTo(new URL(url));
|
|
||||||
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the URL to build a connection for.
|
|
||||||
*
|
|
||||||
* @param url the URL
|
|
||||||
* @return the builder
|
|
||||||
*/
|
|
||||||
public StrongConnectionBuilder connectTo(URL url) {
|
|
||||||
this.url = url;
|
|
||||||
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public HttpURLConnection build(Intent status) throws IOException {
|
|
||||||
return (buildForUrl(status, url));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String get(Intent status, HttpURLConnection connection,
|
|
||||||
String url) throws Exception {
|
|
||||||
HttpURLConnection realConnection = buildForUrl(status, new URL(url));
|
|
||||||
|
|
||||||
return (slurp(realConnection.getInputStream()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpURLConnection buildForUrl(Intent status, URL urlToUse)
|
|
||||||
throws IOException {
|
|
||||||
URLConnection result;
|
|
||||||
Proxy proxy = buildProxy(status);
|
|
||||||
|
|
||||||
if (proxy == null) {
|
|
||||||
result = urlToUse.openConnection();
|
|
||||||
} else {
|
|
||||||
result = urlToUse.openConnection(proxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result instanceof HttpsURLConnection && sslContext != null) {
|
|
||||||
SSLSocketFactory tlsOnly = buildSocketFactory();
|
|
||||||
HttpsURLConnection https = (HttpsURLConnection) result;
|
|
||||||
|
|
||||||
https.setSSLSocketFactory(tlsOnly);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ((HttpURLConnection) result);
|
|
||||||
}
|
|
||||||
|
|
||||||
// based on http://stackoverflow.com/a/309718/115145
|
|
||||||
|
|
||||||
public static String slurp(final InputStream is)
|
|
||||||
throws IOException {
|
|
||||||
final char[] buffer = new char[128];
|
|
||||||
final StringBuilder out = new StringBuilder();
|
|
||||||
final Reader in = new InputStreamReader(is, "UTF-8");
|
|
||||||
|
|
||||||
for (; ; ) {
|
|
||||||
int rsz = in.read(buffer, 0, buffer.length);
|
|
||||||
if (rsz < 0)
|
|
||||||
break;
|
|
||||||
out.append(buffer, 0, rsz);
|
|
||||||
}
|
|
||||||
|
|
||||||
in.close();
|
|
||||||
|
|
||||||
return out.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2012-2016 Nathan Freitas
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package info.guardianproject.netcipher.client;
|
|
||||||
|
|
||||||
public class StrongConstants {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ordered to prefer the stronger cipher suites as noted
|
|
||||||
* http://op-co.de/blog/posts/android_ssl_downgrade/
|
|
||||||
*/
|
|
||||||
public static final String ENABLED_CIPHERS[] = {
|
|
||||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
|
|
||||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
|
|
||||||
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
|
|
||||||
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
|
|
||||||
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
|
|
||||||
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
|
|
||||||
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
|
|
||||||
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
|
|
||||||
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA",
|
|
||||||
"TLS_RSA_WITH_AES_256_CBC_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
|
|
||||||
"SSL_RSA_WITH_RC4_128_SHA", "SSL_RSA_WITH_RC4_128_MD5"};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ordered to prefer the stronger/newer TLS versions as noted
|
|
||||||
* http://op-co.de/blog/posts/android_ssl_downgrade/
|
|
||||||
*/
|
|
||||||
public static final String ENABLED_PROTOCOLS[] = {"TLSv1.2", "TLSv1.1",
|
|
||||||
"TLSv1"};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,546 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 Bhavit Singh Sengar
|
|
||||||
* Copyright 2015-2016 Hans-Christoph Steiner
|
|
||||||
* Copyright 2015-2016 Nathan Freitas
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*
|
|
||||||
* From https://stackoverflow.com/a/29946540
|
|
||||||
*/
|
|
||||||
|
|
||||||
package info.guardianproject.netcipher.client;
|
|
||||||
|
|
||||||
import android.net.SSLCertificateSocketFactory;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import javax.net.ssl.HandshakeCompletedListener;
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
|
||||||
import javax.net.ssl.SSLSession;
|
|
||||||
import javax.net.ssl.SSLSocket;
|
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* While making a secure connection, Android's {@link HttpsURLConnection} falls
|
|
||||||
* back to SSLv3 from TLSv1. This is a bug in android versions < 4.4. It can be
|
|
||||||
* fixed by removing the SSLv3 protocol from Enabled Protocols list. Use this as
|
|
||||||
* the {@link SSLSocketFactory} for
|
|
||||||
* {@link HttpsURLConnection#setDefaultSSLSocketFactory(SSLSocketFactory)}
|
|
||||||
*
|
|
||||||
* @author Bhavit S. Sengar
|
|
||||||
* @author Hans-Christoph Steiner
|
|
||||||
*/
|
|
||||||
public class TlsOnlySocketFactory extends SSLSocketFactory {
|
|
||||||
private static final int HANDSHAKE_TIMEOUT = 0;
|
|
||||||
private static final String TAG = "TlsOnlySocketFactory";
|
|
||||||
private final SSLSocketFactory delegate;
|
|
||||||
private final boolean compatible;
|
|
||||||
|
|
||||||
public TlsOnlySocketFactory() {
|
|
||||||
this.delegate = SSLCertificateSocketFactory.getDefault(HANDSHAKE_TIMEOUT, null);
|
|
||||||
this.compatible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TlsOnlySocketFactory(SSLSocketFactory delegate) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
this.compatible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make {@link SSLSocket}s that are compatible with outdated servers.
|
|
||||||
*
|
|
||||||
* @param delegate
|
|
||||||
* @param compatible
|
|
||||||
*/
|
|
||||||
public TlsOnlySocketFactory(SSLSocketFactory delegate, boolean compatible) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
this.compatible = compatible;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getDefaultCipherSuites() {
|
|
||||||
return delegate.getDefaultCipherSuites();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getSupportedCipherSuites() {
|
|
||||||
return delegate.getSupportedCipherSuites();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see <a href="https://timtaubert.de/blog/2014/11/the-sad-state-of-server-side-tls-session-resumption-implementations/">The sad state of server-side TLS Session Resumption implementations</a>
|
|
||||||
*/
|
|
||||||
private Socket makeSocketSafe(Socket socket, String host) {
|
|
||||||
if (socket instanceof SSLSocket) {
|
|
||||||
TlsOnlySSLSocket tempSocket =
|
|
||||||
new TlsOnlySSLSocket((SSLSocket) socket, compatible);
|
|
||||||
|
|
||||||
if (delegate instanceof SSLCertificateSocketFactory &&
|
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
|
||||||
SSLCertificateSocketFactory factory = (SSLCertificateSocketFactory) delegate;
|
|
||||||
factory.setHostname(socket, host);
|
|
||||||
factory.setUseSessionTickets(socket, false);
|
|
||||||
} else {
|
|
||||||
tempSocket.setHostname(host);
|
|
||||||
}
|
|
||||||
|
|
||||||
socket = tempSocket;
|
|
||||||
}
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose)
|
|
||||||
throws IOException {
|
|
||||||
return makeSocketSafe(delegate.createSocket(s, host, port, autoClose), host);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Socket createSocket(String host, int port) throws IOException {
|
|
||||||
return makeSocketSafe(delegate.createSocket(host, port), host);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
|
|
||||||
throws IOException {
|
|
||||||
return makeSocketSafe(delegate.createSocket(host, port, localHost, localPort), host);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
|
||||||
return makeSocketSafe(delegate.createSocket(host, port), host.getHostName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
|
|
||||||
int localPort) throws IOException {
|
|
||||||
return makeSocketSafe(delegate.createSocket(address, port, localAddress, localPort),
|
|
||||||
address.getHostName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TlsOnlySSLSocket extends DelegateSSLSocket {
|
|
||||||
|
|
||||||
final boolean compatible;
|
|
||||||
|
|
||||||
private TlsOnlySSLSocket(SSLSocket delegate, boolean compatible) {
|
|
||||||
super(delegate);
|
|
||||||
this.compatible = compatible;
|
|
||||||
|
|
||||||
// badly configured servers can't handle a good config
|
|
||||||
if (compatible) {
|
|
||||||
ArrayList<String> protocols = new ArrayList<String>(Arrays.asList(delegate
|
|
||||||
.getEnabledProtocols()));
|
|
||||||
protocols.remove("SSLv2");
|
|
||||||
protocols.remove("SSLv3");
|
|
||||||
super.setEnabledProtocols(protocols.toArray(new String[protocols.size()]));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Exclude extremely weak EXPORT ciphers. NULL ciphers should
|
|
||||||
* never even have been an option in TLS.
|
|
||||||
*/
|
|
||||||
ArrayList<String> enabled = new ArrayList<String>(10);
|
|
||||||
Pattern exclude = Pattern.compile(".*(EXPORT|NULL).*");
|
|
||||||
for (String cipher : delegate.getEnabledCipherSuites()) {
|
|
||||||
if (!exclude.matcher(cipher).matches()) {
|
|
||||||
enabled.add(cipher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.setEnabledCipherSuites(enabled.toArray(new String[enabled.size()]));
|
|
||||||
return;
|
|
||||||
} // else
|
|
||||||
|
|
||||||
// 16-19 support v1.1 and v1.2 but only by default starting in 20+
|
|
||||||
// https://developer.android.com/reference/javax/net/ssl/SSLSocket.html
|
|
||||||
ArrayList<String> protocols = new ArrayList<String>(Arrays.asList(delegate
|
|
||||||
.getSupportedProtocols()));
|
|
||||||
protocols.remove("SSLv2");
|
|
||||||
protocols.remove("SSLv3");
|
|
||||||
super.setEnabledProtocols(protocols.toArray(new String[protocols.size()]));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Exclude weak ciphers, like EXPORT, MD5, DES, and DH. NULL ciphers
|
|
||||||
* should never even have been an option in TLS.
|
|
||||||
*/
|
|
||||||
ArrayList<String> enabledCiphers = new ArrayList<String>(10);
|
|
||||||
Pattern exclude = Pattern.compile(".*(_DES|DH_|DSS|EXPORT|MD5|NULL|RC4|TLS_FALLBACK_SCSV).*");
|
|
||||||
for (String cipher : delegate.getSupportedCipherSuites()) {
|
|
||||||
if (!exclude.matcher(cipher).matches()) {
|
|
||||||
enabledCiphers.add(cipher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.setEnabledCipherSuites(enabledCiphers.toArray(new String[enabledCiphers.size()]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This works around a bug in Android < 19 where SSLv3 is forced
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void setEnabledProtocols(String[] protocols) {
|
|
||||||
if (protocols != null && protocols.length == 1 && "SSLv3".equals(protocols[0])) {
|
|
||||||
List<String> systemProtocols;
|
|
||||||
if (this.compatible) {
|
|
||||||
systemProtocols = Arrays.asList(delegate.getEnabledProtocols());
|
|
||||||
} else {
|
|
||||||
systemProtocols = Arrays.asList(delegate.getSupportedProtocols());
|
|
||||||
}
|
|
||||||
List<String> enabledProtocols = new ArrayList<String>(systemProtocols);
|
|
||||||
if (enabledProtocols.size() > 1) {
|
|
||||||
enabledProtocols.remove("SSLv2");
|
|
||||||
enabledProtocols.remove("SSLv3");
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "SSL stuck with protocol available for "
|
|
||||||
+ String.valueOf(enabledProtocols));
|
|
||||||
}
|
|
||||||
protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]);
|
|
||||||
}
|
|
||||||
super.setEnabledProtocols(protocols);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DelegateSSLSocket extends SSLSocket {
|
|
||||||
|
|
||||||
protected final SSLSocket delegate;
|
|
||||||
|
|
||||||
DelegateSSLSocket(SSLSocket delegate) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getSupportedCipherSuites() {
|
|
||||||
return delegate.getSupportedCipherSuites();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getEnabledCipherSuites() {
|
|
||||||
return delegate.getEnabledCipherSuites();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setEnabledCipherSuites(String[] suites) {
|
|
||||||
delegate.setEnabledCipherSuites(suites);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getSupportedProtocols() {
|
|
||||||
return delegate.getSupportedProtocols();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getEnabledProtocols() {
|
|
||||||
return delegate.getEnabledProtocols();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setEnabledProtocols(String[] protocols) {
|
|
||||||
delegate.setEnabledProtocols(protocols);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SSLSession getSession() {
|
|
||||||
return delegate.getSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
|
|
||||||
delegate.addHandshakeCompletedListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
|
|
||||||
delegate.removeHandshakeCompletedListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startHandshake() throws IOException {
|
|
||||||
delegate.startHandshake();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setUseClientMode(boolean mode) {
|
|
||||||
delegate.setUseClientMode(mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean getUseClientMode() {
|
|
||||||
return delegate.getUseClientMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setNeedClientAuth(boolean need) {
|
|
||||||
delegate.setNeedClientAuth(need);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setWantClientAuth(boolean want) {
|
|
||||||
delegate.setWantClientAuth(want);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean getNeedClientAuth() {
|
|
||||||
return delegate.getNeedClientAuth();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean getWantClientAuth() {
|
|
||||||
return delegate.getWantClientAuth();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setEnableSessionCreation(boolean flag) {
|
|
||||||
delegate.setEnableSessionCreation(flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean getEnableSessionCreation() {
|
|
||||||
return delegate.getEnableSessionCreation();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void bind(SocketAddress localAddr) throws IOException {
|
|
||||||
delegate.bind(localAddr);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void close() throws IOException {
|
|
||||||
delegate.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void connect(SocketAddress remoteAddr) throws IOException {
|
|
||||||
delegate.connect(remoteAddr);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void connect(SocketAddress remoteAddr, int timeout) throws IOException {
|
|
||||||
delegate.connect(remoteAddr, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SocketChannel getChannel() {
|
|
||||||
return delegate.getChannel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InetAddress getInetAddress() {
|
|
||||||
return delegate.getInetAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InputStream getInputStream() throws IOException {
|
|
||||||
return delegate.getInputStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean getKeepAlive() throws SocketException {
|
|
||||||
return delegate.getKeepAlive();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InetAddress getLocalAddress() {
|
|
||||||
return delegate.getLocalAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLocalPort() {
|
|
||||||
return delegate.getLocalPort();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SocketAddress getLocalSocketAddress() {
|
|
||||||
return delegate.getLocalSocketAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean getOOBInline() throws SocketException {
|
|
||||||
return delegate.getOOBInline();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OutputStream getOutputStream() throws IOException {
|
|
||||||
return delegate.getOutputStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPort() {
|
|
||||||
return delegate.getPort();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized int getReceiveBufferSize() throws SocketException {
|
|
||||||
return delegate.getReceiveBufferSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SocketAddress getRemoteSocketAddress() {
|
|
||||||
return delegate.getRemoteSocketAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean getReuseAddress() throws SocketException {
|
|
||||||
return delegate.getReuseAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized int getSendBufferSize() throws SocketException {
|
|
||||||
return delegate.getSendBufferSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSoLinger() throws SocketException {
|
|
||||||
return delegate.getSoLinger();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized int getSoTimeout() throws SocketException {
|
|
||||||
return delegate.getSoTimeout();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean getTcpNoDelay() throws SocketException {
|
|
||||||
return delegate.getTcpNoDelay();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTrafficClass() throws SocketException {
|
|
||||||
return delegate.getTrafficClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isBound() {
|
|
||||||
return delegate.isBound();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isClosed() {
|
|
||||||
return delegate.isClosed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConnected() {
|
|
||||||
return delegate.isConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isInputShutdown() {
|
|
||||||
return delegate.isInputShutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOutputShutdown() {
|
|
||||||
return delegate.isOutputShutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendUrgentData(int value) throws IOException {
|
|
||||||
delegate.sendUrgentData(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setKeepAlive(boolean keepAlive) throws SocketException {
|
|
||||||
delegate.setKeepAlive(keepAlive);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOOBInline(boolean oobinline) throws SocketException {
|
|
||||||
delegate.setOOBInline(oobinline);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
|
|
||||||
delegate.setPerformancePreferences(connectionTime,
|
|
||||||
latency, bandwidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setReceiveBufferSize(int size) throws SocketException {
|
|
||||||
delegate.setReceiveBufferSize(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setReuseAddress(boolean reuse) throws SocketException {
|
|
||||||
delegate.setReuseAddress(reuse);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setSendBufferSize(int size) throws SocketException {
|
|
||||||
delegate.setSendBufferSize(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSoLinger(boolean on, int timeout) throws SocketException {
|
|
||||||
delegate.setSoLinger(on, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setSoTimeout(int timeout) throws SocketException {
|
|
||||||
delegate.setSoTimeout(timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTcpNoDelay(boolean on) throws SocketException {
|
|
||||||
delegate.setTcpNoDelay(on);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTrafficClass(int value) throws SocketException {
|
|
||||||
delegate.setTrafficClass(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdownInput() throws IOException {
|
|
||||||
delegate.shutdownInput();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdownOutput() throws IOException {
|
|
||||||
delegate.shutdownOutput();
|
|
||||||
}
|
|
||||||
|
|
||||||
// inspired by https://github.com/k9mail/k-9/commit/54f9fd36a77423a55f63fbf9b1bcea055a239768
|
|
||||||
|
|
||||||
public DelegateSSLSocket setHostname(String host) {
|
|
||||||
try {
|
|
||||||
delegate
|
|
||||||
.getClass()
|
|
||||||
.getMethod("setHostname", String.class)
|
|
||||||
.invoke(delegate, host);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalStateException("Could not enable SNI", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return delegate.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
return delegate.equals(o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,701 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2014-2016 Hans-Christoph Steiner
|
|
||||||
* Copyright 2012-2016 Nathan Freitas
|
|
||||||
* Portions Copyright (c) 2016 CommonsWare, LLC
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package info.guardianproject.netcipher.proxy;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.WeakHashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class to simplify setting up a proxy connection
|
|
||||||
* to Orbot.
|
|
||||||
* <p>
|
|
||||||
* If you are using classes in the info.guardianproject.netcipher.client
|
|
||||||
* package, call OrbotHelper.get(this).init(); from onCreate()
|
|
||||||
* of a custom Application subclass, or from some other guaranteed
|
|
||||||
* entry point to your app. At that point, the
|
|
||||||
* info.guardianproject.netcipher.client classes will be ready
|
|
||||||
* for use.
|
|
||||||
*/
|
|
||||||
public class OrbotHelper implements ProxyHelper {
|
|
||||||
|
|
||||||
private final static int REQUEST_CODE_STATUS = 100;
|
|
||||||
|
|
||||||
public final static String ORBOT_PACKAGE_NAME = "org.torproject.android";
|
|
||||||
public final static String ORBOT_MARKET_URI = "market://details?id=" + ORBOT_PACKAGE_NAME;
|
|
||||||
public final static String ORBOT_FDROID_URI = "https://f-droid.org/repository/browse/?fdid="
|
|
||||||
+ ORBOT_PACKAGE_NAME;
|
|
||||||
public final static String ORBOT_PLAY_URI = "https://play.google.com/store/apps/details?id="
|
|
||||||
+ ORBOT_PACKAGE_NAME;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A request to Orbot to transparently start Tor services
|
|
||||||
*/
|
|
||||||
public final static String ACTION_START = "org.torproject.android.intent.action.START";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link Intent} send by Orbot 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 final static String ACTION_STATUS = "org.torproject.android.intent.action.STATUS";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@code String} that contains a status constant: {@link #STATUS_ON},
|
|
||||||
* {@link #STATUS_OFF}, {@link #STATUS_STARTING}, or
|
|
||||||
* {@link #STATUS_STOPPING}
|
|
||||||
*/
|
|
||||||
public final static String EXTRA_STATUS = "org.torproject.android.intent.extra.STATUS";
|
|
||||||
/**
|
|
||||||
* A {@link String} {@code packageName} for Orbot to direct its status reply
|
|
||||||
* to, used in {@link #ACTION_START} {@link Intent}s sent to Orbot
|
|
||||||
*/
|
|
||||||
public final static String EXTRA_PACKAGE_NAME = "org.torproject.android.intent.extra.PACKAGE_NAME";
|
|
||||||
|
|
||||||
public final static String EXTRA_PROXY_PORT_HTTP = "org.torproject.android.intent.extra.HTTP_PROXY_PORT";
|
|
||||||
public final static String EXTRA_PROXY_PORT_SOCKS = "org.torproject.android.intent.extra.SOCKS_PROXY_PORT";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All tor-related services and daemons are stopped
|
|
||||||
*/
|
|
||||||
public final static String STATUS_OFF = "OFF";
|
|
||||||
/**
|
|
||||||
* All tor-related services and daemons have completed starting
|
|
||||||
*/
|
|
||||||
public final static String STATUS_ON = "ON";
|
|
||||||
public final static String STATUS_STARTING = "STARTING";
|
|
||||||
public final static String STATUS_STOPPING = "STOPPING";
|
|
||||||
/**
|
|
||||||
* The user has disabled the ability for background starts triggered by
|
|
||||||
* apps. Fallback to the old Intent that brings up Orbot.
|
|
||||||
*/
|
|
||||||
public final static String STATUS_STARTS_DISABLED = "STARTS_DISABLED";
|
|
||||||
|
|
||||||
public final static String ACTION_START_TOR = "org.torproject.android.START_TOR";
|
|
||||||
public final static String ACTION_REQUEST_HS = "org.torproject.android.REQUEST_HS_PORT";
|
|
||||||
public final static int START_TOR_RESULT = 0x9234;
|
|
||||||
public final static int HS_REQUEST_CODE = 9999;
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
private OrbotHelper() {
|
|
||||||
// only static utility methods, do not instantiate
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test whether a {@link URL} is a Tor Hidden Service host name, also known
|
|
||||||
* as an ".onion address".
|
|
||||||
*
|
|
||||||
* @return whether the host name is a Tor .onion address
|
|
||||||
*/
|
|
||||||
public static boolean isOnionAddress(URL url) {
|
|
||||||
return url.getHost().endsWith(".onion");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test whether a URL {@link String} is a Tor Hidden Service host name, also known
|
|
||||||
* as an ".onion address".
|
|
||||||
*
|
|
||||||
* @return whether the host name is a Tor .onion address
|
|
||||||
*/
|
|
||||||
public static boolean isOnionAddress(String urlString) {
|
|
||||||
try {
|
|
||||||
return isOnionAddress(new URL(urlString));
|
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test whether a {@link Uri} is a Tor Hidden Service host name, also known
|
|
||||||
* as an ".onion address".
|
|
||||||
*
|
|
||||||
* @return whether the host name is a Tor .onion address
|
|
||||||
*/
|
|
||||||
public static boolean isOnionAddress(Uri uri) {
|
|
||||||
return uri.getHost().endsWith(".onion");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the tor process is running. This method is very
|
|
||||||
* brittle, and is therefore deprecated in favor of using the
|
|
||||||
* {@link #ACTION_STATUS} {@code Intent} along with the
|
|
||||||
* {@link #requestStartTor(Context)} method.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public static boolean isOrbotRunning(Context context) {
|
|
||||||
int procId = TorServiceUtils.findProcessId(context);
|
|
||||||
|
|
||||||
return (procId != -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isOrbotInstalled(Context context) {
|
|
||||||
return isAppInstalled(context, ORBOT_PACKAGE_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isAppInstalled(Context context, String uri) {
|
|
||||||
try {
|
|
||||||
PackageManager pm = context.getPackageManager();
|
|
||||||
pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
|
|
||||||
return true;
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void requestHiddenServiceOnPort(Activity activity, int port) {
|
|
||||||
Intent intent = new Intent(ACTION_REQUEST_HS);
|
|
||||||
intent.setPackage(ORBOT_PACKAGE_NAME);
|
|
||||||
intent.putExtra("hs_port", port);
|
|
||||||
|
|
||||||
activity.startActivityForResult(intent, HS_REQUEST_CODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* First, checks whether Orbot is installed. If Orbot is installed, then a
|
|
||||||
* broadcast {@link Intent} is sent to request Orbot to start
|
|
||||||
* transparently in the background. When Orbot receives this {@code
|
|
||||||
* Intent}, it will immediately reply to the app that called this method
|
|
||||||
* with an {@link #ACTION_STATUS} {@code Intent} that is broadcast to the
|
|
||||||
* {@code packageName} of the provided {@link Context} (i.e. {@link
|
|
||||||
* Context#getPackageName()}.
|
|
||||||
* <p>
|
|
||||||
* That reply {@link #ACTION_STATUS} {@code Intent} could say that the user
|
|
||||||
* has disabled background starts with the status
|
|
||||||
* {@link #STATUS_STARTS_DISABLED}. That means that Orbot ignored this
|
|
||||||
* request. To directly prompt the user to start Tor, use
|
|
||||||
* {@link #requestShowOrbotStart(Activity)}, which will bring up
|
|
||||||
* Orbot itself for the user to manually start Tor. Orbot always broadcasts
|
|
||||||
* it's status, so your app will receive those no matter how Tor gets
|
|
||||||
* started.
|
|
||||||
*
|
|
||||||
* @param context the app {@link Context} will receive the reply
|
|
||||||
* @return whether the start request was sent to Orbot
|
|
||||||
* @see #requestShowOrbotStart(Activity activity)
|
|
||||||
*/
|
|
||||||
public static boolean requestStartTor(Context context) {
|
|
||||||
if (OrbotHelper.isOrbotInstalled(context)) {
|
|
||||||
Log.i("OrbotHelper", "requestStartTor " + context.getPackageName());
|
|
||||||
Intent intent = getOrbotStartIntent(context);
|
|
||||||
context.sendBroadcast(intent);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an {@link Intent} for starting Orbot. Orbot will reply with the
|
|
||||||
* current status to the {@code packageName} of the app in the provided
|
|
||||||
* {@link Context} (i.e. {@link Context#getPackageName()}.
|
|
||||||
*/
|
|
||||||
public static Intent getOrbotStartIntent(Context context) {
|
|
||||||
Intent intent = new Intent(ACTION_START);
|
|
||||||
intent.setPackage(ORBOT_PACKAGE_NAME);
|
|
||||||
intent.putExtra(EXTRA_PACKAGE_NAME, context.getPackageName());
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a barebones {@link Intent} for starting Orbot. This is deprecated
|
|
||||||
* in favor of {@link #getOrbotStartIntent(Context)}.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public static Intent getOrbotStartIntent() {
|
|
||||||
Intent intent = new Intent(ACTION_START);
|
|
||||||
intent.setPackage(ORBOT_PACKAGE_NAME);
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* First, checks whether Orbot is installed, then checks whether Orbot is
|
|
||||||
* running. If Orbot is installed and not running, then an {@link Intent} is
|
|
||||||
* sent to request the user to start Orbot, which will show the main Orbot screen.
|
|
||||||
* The result will be returned in
|
|
||||||
* {@link Activity#onActivityResult(int requestCode, int resultCode, Intent data)}
|
|
||||||
* with a {@code requestCode} of {@code START_TOR_RESULT}
|
|
||||||
* <p>
|
|
||||||
* Orbot will also always broadcast the status of starting Tor via the
|
|
||||||
* {@link #ACTION_STATUS} Intent, no matter how it is started.
|
|
||||||
*
|
|
||||||
* @param activity the {@code Activity} that gets the result of the
|
|
||||||
* {@link #START_TOR_RESULT} request
|
|
||||||
* @return whether the start request was sent to Orbot
|
|
||||||
* @see #requestStartTor(Context context)
|
|
||||||
*/
|
|
||||||
public static boolean requestShowOrbotStart(Activity activity) {
|
|
||||||
if (OrbotHelper.isOrbotInstalled(activity)) {
|
|
||||||
if (!OrbotHelper.isOrbotRunning(activity)) {
|
|
||||||
Intent intent = getShowOrbotStartIntent();
|
|
||||||
activity.startActivityForResult(intent, START_TOR_RESULT);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Intent getShowOrbotStartIntent() {
|
|
||||||
Intent intent = new Intent(ACTION_START_TOR);
|
|
||||||
intent.setPackage(ORBOT_PACKAGE_NAME);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Intent getOrbotInstallIntent(Context context) {
|
|
||||||
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
||||||
intent.setData(Uri.parse(ORBOT_MARKET_URI));
|
|
||||||
|
|
||||||
PackageManager pm = context.getPackageManager();
|
|
||||||
List<ResolveInfo> resInfos = pm.queryIntentActivities(intent, 0);
|
|
||||||
|
|
||||||
String foundPackageName = null;
|
|
||||||
for (ResolveInfo r : resInfos) {
|
|
||||||
Log.i("OrbotHelper", "market: " + r.activityInfo.packageName);
|
|
||||||
if (TextUtils.equals(r.activityInfo.packageName, FDROID_PACKAGE_NAME)
|
|
||||||
|| TextUtils.equals(r.activityInfo.packageName, PLAY_PACKAGE_NAME)) {
|
|
||||||
foundPackageName = r.activityInfo.packageName;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foundPackageName == null) {
|
|
||||||
intent.setData(Uri.parse(ORBOT_FDROID_URI));
|
|
||||||
} else {
|
|
||||||
intent.setPackage(foundPackageName);
|
|
||||||
}
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isInstalled(Context context) {
|
|
||||||
return isOrbotInstalled(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void requestStatus(Context context) {
|
|
||||||
isOrbotRunning(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean requestStart(Context context) {
|
|
||||||
return requestStartTor(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Intent getInstallIntent(Context context) {
|
|
||||||
return getOrbotInstallIntent(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Intent getStartIntent(Context context) {
|
|
||||||
return getOrbotStartIntent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "Orbot";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* MLM additions */
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final Handler handler;
|
|
||||||
private boolean isInstalled = false;
|
|
||||||
private Intent lastStatusIntent = null;
|
|
||||||
private Set<StatusCallback> statusCallbacks =
|
|
||||||
newSetFromMap(new WeakHashMap<StatusCallback, Boolean>());
|
|
||||||
private Set<InstallCallback> installCallbacks =
|
|
||||||
newSetFromMap(new WeakHashMap<InstallCallback, Boolean>());
|
|
||||||
private long statusTimeoutMs = 30000L;
|
|
||||||
private long installTimeoutMs = 60000L;
|
|
||||||
private boolean validateOrbot = true;
|
|
||||||
|
|
||||||
abstract public static class SimpleStatusCallback
|
|
||||||
implements StatusCallback {
|
|
||||||
@Override
|
|
||||||
public void onEnabled(Intent statusIntent) {
|
|
||||||
// no-op; extend and override if needed
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStarting() {
|
|
||||||
// no-op; extend and override if needed
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStopping() {
|
|
||||||
// no-op; extend and override if needed
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisabled() {
|
|
||||||
// no-op; extend and override if needed
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNotYetInstalled() {
|
|
||||||
// no-op; extend and override if needed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback interface used for reporting the results of an
|
|
||||||
* attempt to install Orbot
|
|
||||||
*/
|
|
||||||
public interface InstallCallback {
|
|
||||||
void onInstalled();
|
|
||||||
|
|
||||||
void onInstallTimeout();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static volatile OrbotHelper INSTANCE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the singleton, initializing if if needed
|
|
||||||
*
|
|
||||||
* @param context any Context will do, as we will hold onto
|
|
||||||
* the Application
|
|
||||||
* @return the singleton
|
|
||||||
*/
|
|
||||||
synchronized public static OrbotHelper get(Context context) {
|
|
||||||
if (INSTANCE == null) {
|
|
||||||
INSTANCE = new OrbotHelper(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (INSTANCE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Standard constructor
|
|
||||||
*
|
|
||||||
* @param context any Context will do; OrbotInitializer will hold
|
|
||||||
* onto the Application context
|
|
||||||
*/
|
|
||||||
private OrbotHelper(Context context) {
|
|
||||||
this.context = context.getApplicationContext();
|
|
||||||
this.handler = new Handler(Looper.getMainLooper());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a StatusCallback to be called when we find out that
|
|
||||||
* Orbot is ready. If Orbot is ready for use, your callback
|
|
||||||
* will be called with onEnabled() immediately, before this
|
|
||||||
* method returns.
|
|
||||||
*
|
|
||||||
* @param cb a callback
|
|
||||||
* @return the singleton, for chaining
|
|
||||||
*/
|
|
||||||
public OrbotHelper addStatusCallback(StatusCallback cb) {
|
|
||||||
statusCallbacks.add(cb);
|
|
||||||
|
|
||||||
if (lastStatusIntent != null) {
|
|
||||||
String status =
|
|
||||||
lastStatusIntent.getStringExtra(OrbotHelper.EXTRA_STATUS);
|
|
||||||
|
|
||||||
if (status.equals(OrbotHelper.STATUS_ON)) {
|
|
||||||
cb.onEnabled(lastStatusIntent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes an existing registered StatusCallback.
|
|
||||||
*
|
|
||||||
* @param cb the callback to remove
|
|
||||||
* @return the singleton, for chaining
|
|
||||||
*/
|
|
||||||
public OrbotHelper removeStatusCallback(StatusCallback cb) {
|
|
||||||
statusCallbacks.remove(cb);
|
|
||||||
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an InstallCallback to be called when we find out that
|
|
||||||
* Orbot is installed
|
|
||||||
*
|
|
||||||
* @param cb a callback
|
|
||||||
* @return the singleton, for chaining
|
|
||||||
*/
|
|
||||||
public OrbotHelper addInstallCallback(InstallCallback cb) {
|
|
||||||
installCallbacks.add(cb);
|
|
||||||
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes an existing registered InstallCallback.
|
|
||||||
*
|
|
||||||
* @param cb the callback to remove
|
|
||||||
* @return the singleton, for chaining
|
|
||||||
*/
|
|
||||||
public OrbotHelper removeInstallCallback(InstallCallback cb) {
|
|
||||||
installCallbacks.remove(cb);
|
|
||||||
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets how long of a delay, in milliseconds, after trying
|
|
||||||
* to get a status from Orbot before we give up.
|
|
||||||
* Defaults to 30000ms = 30 seconds = 0.000347222 days
|
|
||||||
*
|
|
||||||
* @param timeoutMs delay period in milliseconds
|
|
||||||
* @return the singleton, for chaining
|
|
||||||
*/
|
|
||||||
public OrbotHelper statusTimeout(long timeoutMs) {
|
|
||||||
statusTimeoutMs = timeoutMs;
|
|
||||||
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets how long of a delay, in milliseconds, after trying
|
|
||||||
* to install Orbot do we assume that it's not happening.
|
|
||||||
* Defaults to 60000ms = 60 seconds = 1 minute = 1.90259e-6 years
|
|
||||||
*
|
|
||||||
* @param timeoutMs delay period in milliseconds
|
|
||||||
* @return the singleton, for chaining
|
|
||||||
*/
|
|
||||||
public OrbotHelper installTimeout(long timeoutMs) {
|
|
||||||
installTimeoutMs = timeoutMs;
|
|
||||||
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* By default, NetCipher ensures that the Orbot on the
|
|
||||||
* device is one of the official builds. Call this method
|
|
||||||
* to skip that validation. Mostly, this is for developers
|
|
||||||
* who have their own custom Orbot builds (e.g., for
|
|
||||||
* dedicated hardware).
|
|
||||||
*
|
|
||||||
* @return the singleton, for chaining
|
|
||||||
*/
|
|
||||||
public OrbotHelper skipOrbotValidation() {
|
|
||||||
validateOrbot = false;
|
|
||||||
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if Orbot is installed (the last time we checked),
|
|
||||||
* false otherwise
|
|
||||||
*/
|
|
||||||
public boolean isInstalled() {
|
|
||||||
return (isInstalled);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the connection to Orbot, revalidating that it is installed
|
|
||||||
* and requesting fresh status broadcasts. This is best run in your app's
|
|
||||||
* {@link android.app.Application} subclass, in its
|
|
||||||
* {@link android.app.Application#onCreate()} method.
|
|
||||||
*
|
|
||||||
* @return true if initialization is proceeding, false if Orbot is not installed,
|
|
||||||
* or version of Orbot with a unofficial signing key is present.
|
|
||||||
*/
|
|
||||||
public boolean init() {
|
|
||||||
Intent orbot = OrbotHelper.getOrbotStartIntent(context);
|
|
||||||
|
|
||||||
if (validateOrbot) {
|
|
||||||
ArrayList<String> hashes = new ArrayList<String>();
|
|
||||||
|
|
||||||
// Tor Project signing key
|
|
||||||
hashes.add("A4:54:B8:7A:18:47:A8:9E:D7:F5:E7:0F:BA:6B:BA:96:F3:EF:29:C2:6E:09:81:20:4F:E3:47:BF:23:1D:FD:5B");
|
|
||||||
// f-droid.org signing key
|
|
||||||
hashes.add("A7:02:07:92:4F:61:FF:09:37:1D:54:84:14:5C:4B:EE:77:2C:55:C1:9E:EE:23:2F:57:70:E1:82:71:F7:CB:AE");
|
|
||||||
|
|
||||||
orbot =
|
|
||||||
SignatureUtils.validateBroadcastIntent(context, orbot,
|
|
||||||
hashes, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (orbot != null) {
|
|
||||||
isInstalled = true;
|
|
||||||
handler.postDelayed(onStatusTimeout, statusTimeoutMs);
|
|
||||||
context.registerReceiver(orbotStatusReceiver,
|
|
||||||
new IntentFilter(OrbotHelper.ACTION_STATUS));
|
|
||||||
context.sendBroadcast(orbot);
|
|
||||||
} else {
|
|
||||||
isInstalled = false;
|
|
||||||
|
|
||||||
for (StatusCallback cb : statusCallbacks) {
|
|
||||||
cb.onNotYetInstalled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (isInstalled);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given that init() returned false, calling installOrbot()
|
|
||||||
* will trigger an attempt to install Orbot from an available
|
|
||||||
* distribution channel (e.g., the Play Store). Only call this
|
|
||||||
* if the user is expecting it, such as in response to tapping
|
|
||||||
* a dialog button or an action bar item.
|
|
||||||
* <p>
|
|
||||||
* Note that installation may take a long time, even if
|
|
||||||
* the user is proceeding with the installation, due to network
|
|
||||||
* speeds, waiting for user input, and so on. Either specify
|
|
||||||
* a long timeout, or consider the timeout to be merely advisory
|
|
||||||
* and use some other user input to cause you to try
|
|
||||||
* init() again after, presumably, Orbot has been installed
|
|
||||||
* and configured by the user.
|
|
||||||
* <p>
|
|
||||||
* If the user does install Orbot, we will attempt init()
|
|
||||||
* again automatically. Hence, you will probably need user input
|
|
||||||
* to tell you when the user has gotten Orbot up and going.
|
|
||||||
*
|
|
||||||
* @param host the Activity that is triggering this work
|
|
||||||
*/
|
|
||||||
public void installOrbot(Activity host) {
|
|
||||||
handler.postDelayed(onInstallTimeout, installTimeoutMs);
|
|
||||||
|
|
||||||
IntentFilter filter =
|
|
||||||
new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
|
|
||||||
|
|
||||||
filter.addDataScheme("package");
|
|
||||||
|
|
||||||
context.registerReceiver(orbotInstallReceiver, filter);
|
|
||||||
host.startActivity(OrbotHelper.getOrbotInstallIntent(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
private BroadcastReceiver orbotStatusReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
if (TextUtils.equals(intent.getAction(),
|
|
||||||
OrbotHelper.ACTION_STATUS)) {
|
|
||||||
String status = intent.getStringExtra(OrbotHelper.EXTRA_STATUS);
|
|
||||||
|
|
||||||
if (status.equals(OrbotHelper.STATUS_ON)) {
|
|
||||||
lastStatusIntent = intent;
|
|
||||||
handler.removeCallbacks(onStatusTimeout);
|
|
||||||
|
|
||||||
for (StatusCallback cb : statusCallbacks) {
|
|
||||||
cb.onEnabled(intent);
|
|
||||||
}
|
|
||||||
} else if (status.equals(OrbotHelper.STATUS_OFF)) {
|
|
||||||
for (StatusCallback cb : statusCallbacks) {
|
|
||||||
cb.onDisabled();
|
|
||||||
}
|
|
||||||
} else if (status.equals(OrbotHelper.STATUS_STARTING)) {
|
|
||||||
for (StatusCallback cb : statusCallbacks) {
|
|
||||||
cb.onStarting();
|
|
||||||
}
|
|
||||||
} else if (status.equals(OrbotHelper.STATUS_STOPPING)) {
|
|
||||||
for (StatusCallback cb : statusCallbacks) {
|
|
||||||
cb.onStopping();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private Runnable onStatusTimeout = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
context.unregisterReceiver(orbotStatusReceiver);
|
|
||||||
|
|
||||||
for (StatusCallback cb : statusCallbacks) {
|
|
||||||
cb.onStatusTimeout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private BroadcastReceiver orbotInstallReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
if (TextUtils.equals(intent.getAction(),
|
|
||||||
Intent.ACTION_PACKAGE_ADDED)) {
|
|
||||||
String pkgName = intent.getData().getEncodedSchemeSpecificPart();
|
|
||||||
|
|
||||||
if (OrbotHelper.ORBOT_PACKAGE_NAME.equals(pkgName)) {
|
|
||||||
isInstalled = true;
|
|
||||||
handler.removeCallbacks(onInstallTimeout);
|
|
||||||
context.unregisterReceiver(orbotInstallReceiver);
|
|
||||||
|
|
||||||
for (InstallCallback cb : installCallbacks) {
|
|
||||||
cb.onInstalled();
|
|
||||||
}
|
|
||||||
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private Runnable onInstallTimeout = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
context.unregisterReceiver(orbotInstallReceiver);
|
|
||||||
|
|
||||||
for (InstallCallback cb : installCallbacks) {
|
|
||||||
cb.onInstallTimeout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
||||||
* (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
static <E> Set<E> newSetFromMap(Map<E, Boolean> map) {
|
|
||||||
if (map.isEmpty()) {
|
|
||||||
return new SetFromMap<E>(map);
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("map not empty");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2012-2016 Nathan Freitas
|
|
||||||
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package info.guardianproject.netcipher.proxy;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
public interface ProxyHelper {
|
|
||||||
|
|
||||||
public boolean isInstalled(Context context);
|
|
||||||
|
|
||||||
public void requestStatus(Context context);
|
|
||||||
|
|
||||||
public boolean requestStart(Context context);
|
|
||||||
|
|
||||||
public Intent getInstallIntent(Context context);
|
|
||||||
|
|
||||||
public Intent getStartIntent(Context context);
|
|
||||||
|
|
||||||
public String getName();
|
|
||||||
|
|
||||||
public final static String FDROID_PACKAGE_NAME = "org.fdroid.fdroid";
|
|
||||||
public final static String PLAY_PACKAGE_NAME = "com.android.vending";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A request to Orbot to transparently start Tor services
|
|
||||||
*/
|
|
||||||
public final static String ACTION_START = "android.intent.action.PROXY_START";
|
|
||||||
/**
|
|
||||||
* {@link Intent} send by Orbot with {@code ON/OFF/STARTING/STOPPING} status
|
|
||||||
*/
|
|
||||||
public final static String ACTION_STATUS = "android.intent.action.PROXY_STATUS";
|
|
||||||
/**
|
|
||||||
* {@code String} that contains a status constant: {@link #STATUS_ON},
|
|
||||||
* {@link #STATUS_OFF}, {@link #STATUS_STARTING}, or
|
|
||||||
* {@link #STATUS_STOPPING}
|
|
||||||
*/
|
|
||||||
public final static String EXTRA_STATUS = "android.intent.extra.PROXY_STATUS";
|
|
||||||
|
|
||||||
public final static String EXTRA_PROXY_PORT_HTTP = "android.intent.extra.PROXY_PORT_HTTP";
|
|
||||||
public final static String EXTRA_PROXY_PORT_SOCKS = "android.intent.extra.PROXY_PORT_SOCKS";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link String} {@code packageName} for Orbot to direct its status reply
|
|
||||||
* to, used in {@link #ACTION_START} {@link Intent}s sent to Orbot
|
|
||||||
*/
|
|
||||||
public final static String EXTRA_PACKAGE_NAME = "android.intent.extra.PROXY_PACKAGE_NAME";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All tor-related services and daemons are stopped
|
|
||||||
*/
|
|
||||||
public final static String STATUS_OFF = "OFF";
|
|
||||||
/**
|
|
||||||
* All tor-related services and daemons have completed starting
|
|
||||||
*/
|
|
||||||
public final static String STATUS_ON = "ON";
|
|
||||||
public final static String STATUS_STARTING = "STARTING";
|
|
||||||
public final static String STATUS_STOPPING = "STOPPING";
|
|
||||||
/**
|
|
||||||
* The user has disabled the ability for background starts triggered by
|
|
||||||
* apps. Fallback to the old Intent that brings up Orbot.
|
|
||||||
*/
|
|
||||||
public final static String STATUS_STARTS_DISABLED = "STARTS_DISABLED";
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2012-2016 Nathan Freitas
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package info.guardianproject.netcipher.proxy;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.Proxy;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ProxySelector extends java.net.ProxySelector {
|
|
||||||
|
|
||||||
private ArrayList<Proxy> listProxies;
|
|
||||||
|
|
||||||
public ProxySelector() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
listProxies = new ArrayList<Proxy>();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addProxy(Proxy.Type type, String host, int port) {
|
|
||||||
Proxy proxy = new Proxy(type, new InetSocketAddress(host, port));
|
|
||||||
listProxies.add(proxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void connectFailed(URI uri, SocketAddress address,
|
|
||||||
IOException failure) {
|
|
||||||
Log.w("ProxySelector", "could not connect to " + address.toString() + ": " + failure.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Proxy> select(URI uri) {
|
|
||||||
|
|
||||||
return listProxies;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,168 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2012-2016 Nathan Freitas
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package info.guardianproject.netcipher.proxy;
|
|
||||||
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import java.net.ConnectException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class PsiphonHelper implements ProxyHelper {
|
|
||||||
|
|
||||||
public final static String PACKAGE_NAME = "com.psiphon3";
|
|
||||||
public final static String COMPONENT_NAME = "com.psiphon3.StatusActivity";
|
|
||||||
|
|
||||||
|
|
||||||
public final static String MARKET_URI = "market://details?id=" + PACKAGE_NAME;
|
|
||||||
public final static String FDROID_URI = "https://f-droid.org/repository/browse/?fdid="
|
|
||||||
+ PACKAGE_NAME;
|
|
||||||
public final static String ORBOT_PLAY_URI = "https://play.google.com/store/apps/details?id="
|
|
||||||
+ PACKAGE_NAME;
|
|
||||||
|
|
||||||
public final static int DEFAULT_SOCKS_PORT = 1080;
|
|
||||||
public final static int DEFAULT_HTTP_PORT = 8080;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isInstalled(Context context) {
|
|
||||||
return isAppInstalled(context, PACKAGE_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static boolean isAppInstalled(Context context, String uri) {
|
|
||||||
try {
|
|
||||||
PackageManager pm = context.getPackageManager();
|
|
||||||
pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
|
|
||||||
return true;
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void requestStatus(final Context context) {
|
|
||||||
|
|
||||||
Thread thread = new Thread() {
|
|
||||||
public void run() {
|
|
||||||
//can connect to default HTTP proxy port?
|
|
||||||
boolean isSocksOpen = false;
|
|
||||||
boolean isHttpOpen = false;
|
|
||||||
|
|
||||||
int socksPort = DEFAULT_SOCKS_PORT;
|
|
||||||
int httpPort = DEFAULT_HTTP_PORT;
|
|
||||||
|
|
||||||
for (int i = 0; i < 10 && (!isSocksOpen); i++)
|
|
||||||
isSocksOpen = isPortOpen("127.0.0.1", socksPort++, 100);
|
|
||||||
|
|
||||||
for (int i = 0; i < 10 && (!isHttpOpen); i++)
|
|
||||||
isHttpOpen = isPortOpen("127.0.0.1", httpPort++, 100);
|
|
||||||
|
|
||||||
//any other check?
|
|
||||||
|
|
||||||
Intent intent = new Intent(ProxyHelper.ACTION_STATUS);
|
|
||||||
intent.putExtra(EXTRA_PACKAGE_NAME, PACKAGE_NAME);
|
|
||||||
|
|
||||||
if (isSocksOpen && isHttpOpen) {
|
|
||||||
intent.putExtra(EXTRA_STATUS, STATUS_ON);
|
|
||||||
|
|
||||||
intent.putExtra(EXTRA_PROXY_PORT_HTTP, httpPort - 1);
|
|
||||||
intent.putExtra(EXTRA_PROXY_PORT_SOCKS, socksPort - 1);
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
|
||||||
intent.putExtra(EXTRA_STATUS, STATUS_OFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.sendBroadcast(intent);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
thread.start();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean requestStart(Context context) {
|
|
||||||
|
|
||||||
Intent intent = getStartIntent(context);
|
|
||||||
// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
context.startActivity(intent);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Intent getInstallIntent(Context context) {
|
|
||||||
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
||||||
intent.setData(Uri.parse(MARKET_URI));
|
|
||||||
|
|
||||||
PackageManager pm = context.getPackageManager();
|
|
||||||
List<ResolveInfo> resInfos = pm.queryIntentActivities(intent, 0);
|
|
||||||
|
|
||||||
String foundPackageName = null;
|
|
||||||
for (ResolveInfo r : resInfos) {
|
|
||||||
if (TextUtils.equals(r.activityInfo.packageName, FDROID_PACKAGE_NAME)
|
|
||||||
|| TextUtils.equals(r.activityInfo.packageName, PLAY_PACKAGE_NAME)) {
|
|
||||||
foundPackageName = r.activityInfo.packageName;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foundPackageName == null) {
|
|
||||||
intent.setData(Uri.parse(FDROID_URI));
|
|
||||||
} else {
|
|
||||||
intent.setPackage(foundPackageName);
|
|
||||||
}
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Intent getStartIntent(Context context) {
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.setComponent(new ComponentName(PACKAGE_NAME, COMPONENT_NAME));
|
|
||||||
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isPortOpen(final String ip, final int port, final int timeout) {
|
|
||||||
try {
|
|
||||||
Socket socket = new Socket();
|
|
||||||
socket.connect(new InetSocketAddress(ip, port), timeout);
|
|
||||||
socket.close();
|
|
||||||
return true;
|
|
||||||
} catch (ConnectException ce) {
|
|
||||||
ce.printStackTrace();
|
|
||||||
return false;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return PACKAGE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,117 +0,0 @@
|
||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
||||||
* (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package info.guardianproject.netcipher.proxy;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.AbstractSet;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
class SetFromMap<E> extends AbstractSet<E>
|
|
||||||
implements Serializable {
|
|
||||||
private static final long serialVersionUID = 2454657854757543876L;
|
|
||||||
// Must be named as is, to pass serialization compatibility test.
|
|
||||||
private final Map<E, Boolean> m;
|
|
||||||
private transient Set<E> backingSet;
|
|
||||||
|
|
||||||
SetFromMap(final Map<E, Boolean> map) {
|
|
||||||
m = map;
|
|
||||||
backingSet = map.keySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object object) {
|
|
||||||
return backingSet.equals(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return backingSet.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean add(E object) {
|
|
||||||
return m.put(object, Boolean.TRUE) == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clear() {
|
|
||||||
m.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return backingSet.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean contains(Object object) {
|
|
||||||
return backingSet.contains(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsAll(Collection<?> collection) {
|
|
||||||
return backingSet.containsAll(collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return m.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean remove(Object object) {
|
|
||||||
return m.remove(object) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean retainAll(Collection<?> collection) {
|
|
||||||
return backingSet.retainAll(collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object[] toArray() {
|
|
||||||
return backingSet.toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> T[] toArray(T[] contents) {
|
|
||||||
return backingSet.toArray(contents);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterator<E> iterator() {
|
|
||||||
return backingSet.iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
return m.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private void readObject(ObjectInputStream stream)
|
|
||||||
throws IOException, ClassNotFoundException {
|
|
||||||
stream.defaultReadObject();
|
|
||||||
backingSet = m.keySet();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,468 +0,0 @@
|
||||||
/***
|
|
||||||
* Copyright (c) 2014 CommonsWare, LLC
|
|
||||||
* <p>
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
* not use this file except in compliance with the License. You may obtain
|
|
||||||
* a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package info.guardianproject.netcipher.proxy;
|
|
||||||
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.content.pm.Signature;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class SignatureUtils {
|
|
||||||
public static String getOwnSignatureHash(Context context)
|
|
||||||
throws
|
|
||||||
NameNotFoundException,
|
|
||||||
NoSuchAlgorithmException {
|
|
||||||
return (getSignatureHash(context, context.getPackageName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getSignatureHash(Context context, String packageName)
|
|
||||||
throws
|
|
||||||
NameNotFoundException,
|
|
||||||
NoSuchAlgorithmException {
|
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
|
||||||
Signature sig =
|
|
||||||
context.getPackageManager()
|
|
||||||
.getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures[0];
|
|
||||||
|
|
||||||
return (toHexStringWithColons(md.digest(sig.toByteArray())));
|
|
||||||
}
|
|
||||||
|
|
||||||
// based on https://stackoverflow.com/a/2197650/115145
|
|
||||||
|
|
||||||
public static String toHexStringWithColons(byte[] bytes) {
|
|
||||||
char[] hexArray =
|
|
||||||
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
|
|
||||||
'C', 'D', 'E', 'F'};
|
|
||||||
char[] hexChars = new char[(bytes.length * 3) - 1];
|
|
||||||
int v;
|
|
||||||
|
|
||||||
for (int j = 0; j < bytes.length; j++) {
|
|
||||||
v = bytes[j] & 0xFF;
|
|
||||||
hexChars[j * 3] = hexArray[v / 16];
|
|
||||||
hexChars[j * 3 + 1] = hexArray[v % 16];
|
|
||||||
|
|
||||||
if (j < bytes.length - 1) {
|
|
||||||
hexChars[j * 3 + 2] = ':';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new String(hexChars);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Confirms that the broadcast receiver for a given Intent
|
|
||||||
* has the desired signature hash.
|
|
||||||
* <p>
|
|
||||||
* If you know the package name of the receiver, call
|
|
||||||
* setPackage() on the Intent before passing into this method.
|
|
||||||
* That will validate whether the package is installed and whether
|
|
||||||
* it has the proper signature hash. You can distinguish between
|
|
||||||
* these cases by passing true for the failIfHack parameter.
|
|
||||||
* <p>
|
|
||||||
* In general, there are three possible outcomes of calling
|
|
||||||
* this method:
|
|
||||||
* <p>
|
|
||||||
* 1. You get a SecurityException, because failIfHack is true,
|
|
||||||
* and we found some receiver whose app does not match the
|
|
||||||
* desired hash. The user may have installed a repackaged
|
|
||||||
* version of this app that is signed by the wrong key.
|
|
||||||
* <p>
|
|
||||||
* 2. You get null. If failIfHack is true, this means that no
|
|
||||||
* receiver was found that matches the Intent. If failIfHack
|
|
||||||
* is false, this means that no receiver was found that matches
|
|
||||||
* the Intent and has a valid matching signature.
|
|
||||||
* <p>
|
|
||||||
* 3. You get an Intent. This means we found a matching receiver
|
|
||||||
* that has a matching signature. The Intent will be a copy of
|
|
||||||
* the passed-in Intent, with the component name set to the
|
|
||||||
* matching receiver, so the "broadcast" will only go to this
|
|
||||||
* one component.
|
|
||||||
*
|
|
||||||
* @param context any Context will do; the value is not retained
|
|
||||||
* @param toValidate the Intent that you intend to broadcast
|
|
||||||
* @param sigHash the signature hash of the app that you expect
|
|
||||||
* to handle this broadcast
|
|
||||||
* @param failIfHack true if you want a SecurityException if
|
|
||||||
* a matching receiver is found but it has
|
|
||||||
* the wrong signature hash, false otherwise
|
|
||||||
* @return null if there is no matching receiver with the correct
|
|
||||||
* hash, or a copy of the toValidate parameter with the full component
|
|
||||||
* name of the target receiver added to the Intent
|
|
||||||
*/
|
|
||||||
public static Intent validateBroadcastIntent(Context context,
|
|
||||||
Intent toValidate,
|
|
||||||
String sigHash,
|
|
||||||
boolean failIfHack) {
|
|
||||||
ArrayList<String> sigHashes = new ArrayList<String>();
|
|
||||||
|
|
||||||
sigHashes.add(sigHash);
|
|
||||||
|
|
||||||
return (validateBroadcastIntent(context, toValidate, sigHashes,
|
|
||||||
failIfHack));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Confirms that the broadcast receiver for a given Intent
|
|
||||||
* has a desired signature hash.
|
|
||||||
* <p>
|
|
||||||
* If you know the package name of the receiver, call
|
|
||||||
* setPackage() on the Intent before passing into this method.
|
|
||||||
* That will validate whether the package is installed and whether
|
|
||||||
* it has a proper signature hash. You can distinguish between
|
|
||||||
* these cases by passing true for the failIfHack parameter.
|
|
||||||
* <p>
|
|
||||||
* In general, there are three possible outcomes of calling
|
|
||||||
* this method:
|
|
||||||
* <p>
|
|
||||||
* 1. You get a SecurityException, because failIfHack is true,
|
|
||||||
* and we found some receiver whose app does not match the
|
|
||||||
* desired hash. The user may have installed a repackaged
|
|
||||||
* version of this app that is signed by the wrong key.
|
|
||||||
* <p>
|
|
||||||
* 2. You get null. If failIfHack is true, this means that no
|
|
||||||
* receiver was found that matches the Intent. If failIfHack
|
|
||||||
* is false, this means that no receiver was found that matches
|
|
||||||
* the Intent and has a valid matching signature.
|
|
||||||
* <p>
|
|
||||||
* 3. You get an Intent. This means we found a matching receiver
|
|
||||||
* that has a matching signature. The Intent will be a copy of
|
|
||||||
* the passed-in Intent, with the component name set to the
|
|
||||||
* matching receiver, so the "broadcast" will only go to this
|
|
||||||
* one component.
|
|
||||||
*
|
|
||||||
* @param context any Context will do; the value is not retained
|
|
||||||
* @param toValidate the Intent that you intend to broadcast
|
|
||||||
* @param sigHashes the possible signature hashes of the app
|
|
||||||
* that you expect to handle this broadcast
|
|
||||||
* @param failIfHack true if you want a SecurityException if
|
|
||||||
* a matching receiver is found but it has
|
|
||||||
* the wrong signature hash, false otherwise
|
|
||||||
* @return null if there is no matching receiver with the correct
|
|
||||||
* hash, or a copy of the toValidate parameter with the full component
|
|
||||||
* name of the target receiver added to the Intent
|
|
||||||
*/
|
|
||||||
public static Intent validateBroadcastIntent(Context context,
|
|
||||||
Intent toValidate,
|
|
||||||
List<String> sigHashes,
|
|
||||||
boolean failIfHack) {
|
|
||||||
PackageManager pm = context.getPackageManager();
|
|
||||||
Intent result = null;
|
|
||||||
List<ResolveInfo> receivers =
|
|
||||||
pm.queryBroadcastReceivers(toValidate, 0);
|
|
||||||
|
|
||||||
if (receivers != null) {
|
|
||||||
for (ResolveInfo info : receivers) {
|
|
||||||
try {
|
|
||||||
if (sigHashes.contains(getSignatureHash(context,
|
|
||||||
info.activityInfo.packageName))) {
|
|
||||||
ComponentName cn =
|
|
||||||
new ComponentName(info.activityInfo.packageName,
|
|
||||||
info.activityInfo.name);
|
|
||||||
|
|
||||||
result = new Intent(toValidate).setComponent(cn);
|
|
||||||
break;
|
|
||||||
} else if (failIfHack) {
|
|
||||||
throw new SecurityException(
|
|
||||||
"Package has signature hash mismatch: " +
|
|
||||||
info.activityInfo.packageName);
|
|
||||||
}
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
Log.w("SignatureUtils",
|
|
||||||
"Exception when computing signature hash", e);
|
|
||||||
} catch (NameNotFoundException e) {
|
|
||||||
Log.w("SignatureUtils",
|
|
||||||
"Exception when computing signature hash", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Confirms that the activity for a given Intent has the
|
|
||||||
* desired signature hash.
|
|
||||||
* <p>
|
|
||||||
* If you know the package name of the activity, call
|
|
||||||
* setPackage() on the Intent before passing into this method.
|
|
||||||
* That will validate whether the package is installed and whether
|
|
||||||
* it has the proper signature hash. You can distinguish between
|
|
||||||
* these cases by passing true for the failIfHack parameter.
|
|
||||||
* <p>
|
|
||||||
* In general, there are three possible outcomes of calling
|
|
||||||
* this method:
|
|
||||||
* <p>
|
|
||||||
* 1. You get a SecurityException, because failIfHack is true,
|
|
||||||
* and we found some activity whose app does not match the
|
|
||||||
* desired hash. The user may have installed a repackaged
|
|
||||||
* version of this app that is signed by the wrong key.
|
|
||||||
* <p>
|
|
||||||
* 2. You get null. If failIfHack is true, this means that no
|
|
||||||
* activity was found that matches the Intent. If failIfHack
|
|
||||||
* is false, this means that no activity was found that matches
|
|
||||||
* the Intent and has a valid matching signature.
|
|
||||||
* <p>
|
|
||||||
* 3. You get an Intent. This means we found a matching activity
|
|
||||||
* that has a matching signature. The Intent will be a copy of
|
|
||||||
* the passed-in Intent, with the component name set to the
|
|
||||||
* matching activity, so a call to startActivity() for this
|
|
||||||
* Intent is guaranteed to go to this specific activity.
|
|
||||||
*
|
|
||||||
* @param context any Context will do; the value is not retained
|
|
||||||
* @param toValidate the Intent that you intend to use with
|
|
||||||
* startActivity()
|
|
||||||
* @param sigHash the signature hash of the app that you expect
|
|
||||||
* to handle this activity
|
|
||||||
* @param failIfHack true if you want a SecurityException if
|
|
||||||
* a matching activity is found but it has
|
|
||||||
* the wrong signature hash, false otherwise
|
|
||||||
* @return null if there is no matching activity with the correct
|
|
||||||
* hash, or a copy of the toValidate parameter with the full component
|
|
||||||
* name of the target activity added to the Intent
|
|
||||||
*/
|
|
||||||
public static Intent validateActivityIntent(Context context,
|
|
||||||
Intent toValidate,
|
|
||||||
String sigHash,
|
|
||||||
boolean failIfHack) {
|
|
||||||
ArrayList<String> sigHashes = new ArrayList<String>();
|
|
||||||
|
|
||||||
sigHashes.add(sigHash);
|
|
||||||
|
|
||||||
return (validateActivityIntent(context, toValidate, sigHashes,
|
|
||||||
failIfHack));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Confirms that the activity for a given Intent has the
|
|
||||||
* desired signature hash.
|
|
||||||
* <p>
|
|
||||||
* If you know the package name of the activity, call
|
|
||||||
* setPackage() on the Intent before passing into this method.
|
|
||||||
* That will validate whether the package is installed and whether
|
|
||||||
* it has the proper signature hash. You can distinguish between
|
|
||||||
* these cases by passing true for the failIfHack parameter.
|
|
||||||
* <p>
|
|
||||||
* In general, there are three possible outcomes of calling
|
|
||||||
* this method:
|
|
||||||
* <p>
|
|
||||||
* 1. You get a SecurityException, because failIfHack is true,
|
|
||||||
* and we found some activity whose app does not match the
|
|
||||||
* desired hash. The user may have installed a repackaged
|
|
||||||
* version of this app that is signed by the wrong key.
|
|
||||||
* <p>
|
|
||||||
* 2. You get null. If failIfHack is true, this means that no
|
|
||||||
* activity was found that matches the Intent. If failIfHack
|
|
||||||
* is false, this means that no activity was found that matches
|
|
||||||
* the Intent and has a valid matching signature.
|
|
||||||
* <p>
|
|
||||||
* 3. You get an Intent. This means we found a matching activity
|
|
||||||
* that has a matching signature. The Intent will be a copy of
|
|
||||||
* the passed-in Intent, with the component name set to the
|
|
||||||
* matching activity, so a call to startActivity() for this
|
|
||||||
* Intent is guaranteed to go to this specific activity.
|
|
||||||
*
|
|
||||||
* @param context any Context will do; the value is not retained
|
|
||||||
* @param toValidate the Intent that you intend to use with
|
|
||||||
* startActivity()
|
|
||||||
* @param sigHashes the signature hashes of the app that you expect
|
|
||||||
* to handle this activity
|
|
||||||
* @param failIfHack true if you want a SecurityException if
|
|
||||||
* a matching activity is found but it has
|
|
||||||
* the wrong signature hash, false otherwise
|
|
||||||
* @return null if there is no matching activity with the correct
|
|
||||||
* hash, or a copy of the toValidate parameter with the full component
|
|
||||||
* name of the target activity added to the Intent
|
|
||||||
*/
|
|
||||||
public static Intent validateActivityIntent(Context context,
|
|
||||||
Intent toValidate,
|
|
||||||
List<String> sigHashes,
|
|
||||||
boolean failIfHack) {
|
|
||||||
PackageManager pm = context.getPackageManager();
|
|
||||||
Intent result = null;
|
|
||||||
List<ResolveInfo> activities =
|
|
||||||
pm.queryIntentActivities(toValidate, 0);
|
|
||||||
|
|
||||||
if (activities != null) {
|
|
||||||
for (ResolveInfo info : activities) {
|
|
||||||
try {
|
|
||||||
if (sigHashes.contains(getSignatureHash(context,
|
|
||||||
info.activityInfo.packageName))) {
|
|
||||||
ComponentName cn =
|
|
||||||
new ComponentName(info.activityInfo.packageName,
|
|
||||||
info.activityInfo.name);
|
|
||||||
|
|
||||||
result = new Intent(toValidate).setComponent(cn);
|
|
||||||
break;
|
|
||||||
} else if (failIfHack) {
|
|
||||||
throw new SecurityException(
|
|
||||||
"Package has signature hash mismatch: " +
|
|
||||||
info.activityInfo.packageName);
|
|
||||||
}
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
Log.w("SignatureUtils",
|
|
||||||
"Exception when computing signature hash", e);
|
|
||||||
} catch (NameNotFoundException e) {
|
|
||||||
Log.w("SignatureUtils",
|
|
||||||
"Exception when computing signature hash", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Confirms that the service for a given Intent has the
|
|
||||||
* desired signature hash.
|
|
||||||
* <p>
|
|
||||||
* If you know the package name of the service, call
|
|
||||||
* setPackage() on the Intent before passing into this method.
|
|
||||||
* That will validate whether the package is installed and whether
|
|
||||||
* it has the proper signature hash. You can distinguish between
|
|
||||||
* these cases by passing true for the failIfHack parameter.
|
|
||||||
* <p>
|
|
||||||
* In general, there are three possible outcomes of calling
|
|
||||||
* this method:
|
|
||||||
* <p>
|
|
||||||
* 1. You get a SecurityException, because failIfHack is true,
|
|
||||||
* and we found some service whose app does not match the
|
|
||||||
* desired hash. The user may have installed a repackaged
|
|
||||||
* version of this app that is signed by the wrong key.
|
|
||||||
* <p>
|
|
||||||
* 2. You get null. If failIfHack is true, this means that no
|
|
||||||
* service was found that matches the Intent. If failIfHack
|
|
||||||
* is false, this means that no service was found that matches
|
|
||||||
* the Intent and has a valid matching signature.
|
|
||||||
* <p>
|
|
||||||
* 3. You get an Intent. This means we found a matching service
|
|
||||||
* that has a matching signature. The Intent will be a copy of
|
|
||||||
* the passed-in Intent, with the component name set to the
|
|
||||||
* matching service, so a call to startService() or
|
|
||||||
* bindService() for this Intent is guaranteed to go to this
|
|
||||||
* specific service.
|
|
||||||
*
|
|
||||||
* @param context any Context will do; the value is not retained
|
|
||||||
* @param toValidate the Intent that you intend to use with
|
|
||||||
* startService() or bindService()
|
|
||||||
* @param sigHash the signature hash of the app that you expect
|
|
||||||
* to handle this service
|
|
||||||
* @param failIfHack true if you want a SecurityException if
|
|
||||||
* a matching service is found but it has
|
|
||||||
* the wrong signature hash, false otherwise
|
|
||||||
* @return null if there is no matching service with the correct
|
|
||||||
* hash, or a copy of the toValidate parameter with the full component
|
|
||||||
* name of the target service added to the Intent
|
|
||||||
*/
|
|
||||||
public static Intent validateServiceIntent(Context context,
|
|
||||||
Intent toValidate,
|
|
||||||
String sigHash,
|
|
||||||
boolean failIfHack) {
|
|
||||||
ArrayList<String> sigHashes = new ArrayList<String>();
|
|
||||||
|
|
||||||
sigHashes.add(sigHash);
|
|
||||||
|
|
||||||
return (validateServiceIntent(context, toValidate, sigHashes,
|
|
||||||
failIfHack));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Confirms that the service for a given Intent has the
|
|
||||||
* desired signature hash.
|
|
||||||
* <p>
|
|
||||||
* If you know the package name of the service, call
|
|
||||||
* setPackage() on the Intent before passing into this method.
|
|
||||||
* That will validate whether the package is installed and whether
|
|
||||||
* it has the proper signature hash. You can distinguish between
|
|
||||||
* these cases by passing true for the failIfHack parameter.
|
|
||||||
* <p>
|
|
||||||
* In general, there are three possible outcomes of calling
|
|
||||||
* this method:
|
|
||||||
* <p>
|
|
||||||
* 1. You get a SecurityException, because failIfHack is true,
|
|
||||||
* and we found some service whose app does not match the
|
|
||||||
* desired hash. The user may have installed a repackaged
|
|
||||||
* version of this app that is signed by the wrong key.
|
|
||||||
* <p>
|
|
||||||
* 2. You get null. If failIfHack is true, this means that no
|
|
||||||
* service was found that matches the Intent. If failIfHack
|
|
||||||
* is false, this means that no service was found that matches
|
|
||||||
* the Intent and has a valid matching signature.
|
|
||||||
* <p>
|
|
||||||
* 3. You get an Intent. This means we found a matching service
|
|
||||||
* that has a matching signature. The Intent will be a copy of
|
|
||||||
* the passed-in Intent, with the component name set to the
|
|
||||||
* matching service, so a call to startService() or
|
|
||||||
* bindService() for this Intent is guaranteed to go to this
|
|
||||||
* specific service.
|
|
||||||
*
|
|
||||||
* @param context any Context will do; the value is not retained
|
|
||||||
* @param toValidate the Intent that you intend to use with
|
|
||||||
* startService() or bindService()
|
|
||||||
* @param sigHashes the signature hash of the app that you expect
|
|
||||||
* to handle this service
|
|
||||||
* @param failIfHack true if you want a SecurityException if
|
|
||||||
* a matching service is found but it has
|
|
||||||
* the wrong signature hash, false otherwise
|
|
||||||
* @return null if there is no matching service with the correct
|
|
||||||
* hash, or a copy of the toValidate parameter with the full component
|
|
||||||
* name of the target service added to the Intent
|
|
||||||
*/
|
|
||||||
public static Intent validateServiceIntent(Context context,
|
|
||||||
Intent toValidate,
|
|
||||||
List<String> sigHashes,
|
|
||||||
boolean failIfHack) {
|
|
||||||
PackageManager pm = context.getPackageManager();
|
|
||||||
Intent result = null;
|
|
||||||
List<ResolveInfo> services =
|
|
||||||
pm.queryIntentServices(toValidate, 0);
|
|
||||||
|
|
||||||
if (services != null) {
|
|
||||||
for (ResolveInfo info : services) {
|
|
||||||
try {
|
|
||||||
if (sigHashes.contains(getSignatureHash(context,
|
|
||||||
info.serviceInfo.packageName))) {
|
|
||||||
ComponentName cn =
|
|
||||||
new ComponentName(info.serviceInfo.packageName,
|
|
||||||
info.serviceInfo.name);
|
|
||||||
|
|
||||||
result = new Intent(toValidate).setComponent(cn);
|
|
||||||
break;
|
|
||||||
} else if (failIfHack) {
|
|
||||||
throw new SecurityException(
|
|
||||||
"Package has signature hash mismatch: " +
|
|
||||||
info.activityInfo.packageName);
|
|
||||||
}
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
Log.w("SignatureUtils",
|
|
||||||
"Exception when computing signature hash", e);
|
|
||||||
} catch (NameNotFoundException e) {
|
|
||||||
Log.w("SignatureUtils",
|
|
||||||
"Exception when computing signature hash", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (result);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2016 CommonsWare, LLC
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package info.guardianproject.netcipher.proxy;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback interface used for reporting Orbot status
|
|
||||||
*/
|
|
||||||
public interface StatusCallback {
|
|
||||||
/**
|
|
||||||
* Called when Orbot is operational
|
|
||||||
*
|
|
||||||
* @param statusIntent an Intent containing information about
|
|
||||||
* Orbot, including proxy ports
|
|
||||||
*/
|
|
||||||
void onEnabled(Intent statusIntent);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when Orbot reports that it is starting up
|
|
||||||
*/
|
|
||||||
void onStarting();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when Orbot reports that it is shutting down
|
|
||||||
*/
|
|
||||||
void onStopping();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when Orbot reports that it is no longer running
|
|
||||||
*/
|
|
||||||
void onDisabled();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called if our attempt to get a status from Orbot failed
|
|
||||||
* after a defined period of time. See statusTimeout() on
|
|
||||||
* OrbotInitializer.
|
|
||||||
*/
|
|
||||||
void onStatusTimeout();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called if Orbot is not yet installed. Usually, you handle
|
|
||||||
* this by checking the return value from init() on OrbotInitializer
|
|
||||||
* or calling isInstalled() on OrbotInitializer. However, if
|
|
||||||
* you have need for it, if a callback is registered before
|
|
||||||
* an init() call determines that Orbot is not installed, your
|
|
||||||
* callback will be called with onNotYetInstalled().
|
|
||||||
*/
|
|
||||||
void onNotYetInstalled();
|
|
||||||
}
|
|
|
@ -1,233 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2009-2016 Nathan Freitas
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package info.guardianproject.netcipher.proxy;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.StringTokenizer;
|
|
||||||
|
|
||||||
public class TorServiceUtils {
|
|
||||||
|
|
||||||
private final static String TAG = "TorUtils";
|
|
||||||
// various console cmds
|
|
||||||
public final static String SHELL_CMD_CHMOD = "chmod";
|
|
||||||
public final static String SHELL_CMD_KILL = "kill -9";
|
|
||||||
public final static String SHELL_CMD_RM = "rm";
|
|
||||||
public final static String SHELL_CMD_PS = "ps";
|
|
||||||
public final static String SHELL_CMD_PIDOF = "pidof";
|
|
||||||
|
|
||||||
public final static String CHMOD_EXE_VALUE = "700";
|
|
||||||
|
|
||||||
public static boolean isRootPossible() {
|
|
||||||
|
|
||||||
StringBuilder log = new StringBuilder();
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Check if Superuser.apk exists
|
|
||||||
File fileSU = new File("/system/app/Superuser.apk");
|
|
||||||
if (fileSU.exists())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
fileSU = new File("/system/app/superuser.apk");
|
|
||||||
if (fileSU.exists())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
fileSU = new File("/system/bin/su");
|
|
||||||
if (fileSU.exists()) {
|
|
||||||
String[] cmd = {
|
|
||||||
"su"
|
|
||||||
};
|
|
||||||
int exitCode = TorServiceUtils.doShellCommand(cmd, log, false, true);
|
|
||||||
if (exitCode != 0)
|
|
||||||
return false;
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for 'su' binary
|
|
||||||
String[] cmd = {
|
|
||||||
"which su"
|
|
||||||
};
|
|
||||||
int exitCode = TorServiceUtils.doShellCommand(cmd, log, false, true);
|
|
||||||
|
|
||||||
if (exitCode == 0) {
|
|
||||||
Log.d(TAG, "root exists, but not sure about permissions");
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
// this means that there is no root to be had (normally) so we won't
|
|
||||||
// log anything
|
|
||||||
Log.e(TAG, "Error checking for root access", e);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Error checking for root access", e);
|
|
||||||
// this means that there is no root to be had (normally)
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.e(TAG, "Could not acquire root permissions");
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int findProcessId(Context context) {
|
|
||||||
String dataPath = context.getFilesDir().getParentFile().getParentFile().getAbsolutePath();
|
|
||||||
String command = dataPath + "/" + OrbotHelper.ORBOT_PACKAGE_NAME + "/app_bin/tor";
|
|
||||||
int procId = -1;
|
|
||||||
|
|
||||||
try {
|
|
||||||
procId = findProcessIdWithPidOf(command);
|
|
||||||
|
|
||||||
if (procId == -1)
|
|
||||||
procId = findProcessIdWithPS(command);
|
|
||||||
} catch (Exception e) {
|
|
||||||
try {
|
|
||||||
procId = findProcessIdWithPS(command);
|
|
||||||
} catch (Exception e2) {
|
|
||||||
Log.e(TAG, "Unable to get proc id for command: " + URLEncoder.encode(command), e2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return procId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// use 'pidof' command
|
|
||||||
public static int findProcessIdWithPidOf(String command) throws Exception {
|
|
||||||
|
|
||||||
int procId = -1;
|
|
||||||
|
|
||||||
Runtime r = Runtime.getRuntime();
|
|
||||||
|
|
||||||
Process procPs = null;
|
|
||||||
|
|
||||||
String baseName = new File(command).getName();
|
|
||||||
// fix contributed my mikos on 2010.12.10
|
|
||||||
procPs = r.exec(new String[]{
|
|
||||||
SHELL_CMD_PIDOF, baseName
|
|
||||||
});
|
|
||||||
// procPs = r.exec(SHELL_CMD_PIDOF);
|
|
||||||
|
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream()));
|
|
||||||
String line = null;
|
|
||||||
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
// this line should just be the process id
|
|
||||||
procId = Integer.parseInt(line.trim());
|
|
||||||
break;
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
Log.e("TorServiceUtils", "unable to parse process pid: " + line, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return procId;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// use 'ps' command
|
|
||||||
public static int findProcessIdWithPS(String command) throws Exception {
|
|
||||||
|
|
||||||
int procId = -1;
|
|
||||||
|
|
||||||
Runtime r = Runtime.getRuntime();
|
|
||||||
|
|
||||||
Process procPs = null;
|
|
||||||
|
|
||||||
procPs = r.exec(SHELL_CMD_PS);
|
|
||||||
|
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream()));
|
|
||||||
String line = null;
|
|
||||||
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
if (line.indexOf(' ' + command) != -1) {
|
|
||||||
|
|
||||||
StringTokenizer st = new StringTokenizer(line, " ");
|
|
||||||
st.nextToken(); // proc owner
|
|
||||||
|
|
||||||
procId = Integer.parseInt(st.nextToken().trim());
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return procId;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int doShellCommand(String[] cmds, StringBuilder log, boolean runAsRoot,
|
|
||||||
boolean waitFor) throws Exception {
|
|
||||||
|
|
||||||
Process proc = null;
|
|
||||||
int exitCode = -1;
|
|
||||||
|
|
||||||
if (runAsRoot)
|
|
||||||
proc = Runtime.getRuntime().exec("su");
|
|
||||||
else
|
|
||||||
proc = Runtime.getRuntime().exec("sh");
|
|
||||||
|
|
||||||
OutputStreamWriter out = new OutputStreamWriter(proc.getOutputStream());
|
|
||||||
|
|
||||||
for (int i = 0; i < cmds.length; i++) {
|
|
||||||
// TorService.logMessage("executing shell cmd: " + cmds[i] +
|
|
||||||
// "; runAsRoot=" + runAsRoot + ";waitFor=" + waitFor);
|
|
||||||
|
|
||||||
out.write(cmds[i]);
|
|
||||||
out.write("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
out.flush();
|
|
||||||
out.write("exit\n");
|
|
||||||
out.flush();
|
|
||||||
|
|
||||||
if (waitFor) {
|
|
||||||
|
|
||||||
final char buf[] = new char[10];
|
|
||||||
|
|
||||||
// Consume the "stdout"
|
|
||||||
InputStreamReader reader = new InputStreamReader(proc.getInputStream());
|
|
||||||
int read = 0;
|
|
||||||
while ((read = reader.read(buf)) != -1) {
|
|
||||||
if (log != null)
|
|
||||||
log.append(buf, 0, read);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume the "stderr"
|
|
||||||
reader = new InputStreamReader(proc.getErrorStream());
|
|
||||||
read = 0;
|
|
||||||
while ((read = reader.read(buf)) != -1) {
|
|
||||||
if (log != null)
|
|
||||||
log.append(buf, 0, read);
|
|
||||||
}
|
|
||||||
|
|
||||||
exitCode = proc.waitFor();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return exitCode;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.example.myapplication;
|
||||||
|
|
||||||
|
public class status
|
||||||
|
{
|
||||||
|
public static boolean hasApplicationLoaded = false;
|
||||||
|
public static String currentURL = "http://boogle.store/";
|
||||||
|
}
|
|
@ -1,55 +1,50 @@
|
||||||
package com.example.myapplication;
|
package com.example.myapplication;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.support.constraint.ConstraintLayout;
|
import android.support.constraint.ConstraintLayout;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.Proxy;
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import cz.msebera.android.httpclient.HttpHost;
|
|
||||||
import cz.msebera.android.httpclient.HttpResponse;
|
import cz.msebera.android.httpclient.HttpResponse;
|
||||||
import cz.msebera.android.httpclient.client.HttpClient;
|
import cz.msebera.android.httpclient.client.HttpClient;
|
||||||
import cz.msebera.android.httpclient.client.methods.HttpGet;
|
import cz.msebera.android.httpclient.client.methods.HttpGet;
|
||||||
import cz.msebera.android.httpclient.conn.params.ConnRoutePNames;
|
|
||||||
import cz.msebera.android.httpclient.impl.client.DefaultHttpClient;
|
import cz.msebera.android.httpclient.impl.client.DefaultHttpClient;
|
||||||
import info.guardianproject.netcipher.NetCipher;
|
import info.guardianproject.netcipher.NetCipher;
|
||||||
import info.guardianproject.netcipher.client.StrongBuilder;
|
import info.guardianproject.netcipher.client.StrongBuilder;
|
||||||
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
||||||
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
|
||||||
|
|
||||||
public class webRequestHandler implements StrongBuilder.Callback<HttpClient>
|
public class webRequestHandler implements StrongBuilder.Callback<HttpClient>
|
||||||
{
|
{
|
||||||
private static final webRequestHandler ourInstance = new webRequestHandler();
|
private static final webRequestHandler ourInstance = new webRequestHandler();
|
||||||
private WebView[] view = new WebView[2];;
|
private WebView[] view = new WebView[2];
|
||||||
|
private ProgressBar progressBar;
|
||||||
|
private EditText searchbar;
|
||||||
|
private ConstraintLayout requestFailure;
|
||||||
|
|
||||||
|
public boolean isReloadedUrl = false;
|
||||||
private int viewIndex = 1;
|
private int viewIndex = 1;
|
||||||
private int currentViewIndex = 0;
|
private int currentViewIndex = 0;
|
||||||
private String html = "";
|
private String html = "";
|
||||||
private String baseURL = "";
|
private String baseURL = "";
|
||||||
private Boolean isLoading = false;
|
|
||||||
private ProgressBar progressBar;
|
|
||||||
private EditText searchbar;
|
|
||||||
private Thread clientThread = null;
|
private Thread clientThread = null;
|
||||||
private ConstraintLayout requestFailure;
|
|
||||||
HttpGet request = null;
|
HttpGet request = null;
|
||||||
|
private Handler updateUIHandler = null;
|
||||||
|
|
||||||
// test the local device proxy provided by Orbot/Tor
|
private final static int MESSAGE_UPDATE_TEXT_CHILD_THREAD =1;
|
||||||
private final static String PROXY_HOST = "127.0.0.1";
|
private final static int INTERNET_ERROR =2;
|
||||||
private final static int PROXY_HTTP_PORT = 8118; // default for Orbot/Tor
|
|
||||||
private final static int PROXY_SOCKS_PORT = 9050; // default for Orbot/Tor
|
|
||||||
private Proxy.Type mProxyType = null;
|
|
||||||
|
|
||||||
public static webRequestHandler getInstance() {
|
public static webRequestHandler getInstance() {
|
||||||
return ourInstance;
|
return ourInstance;
|
||||||
|
@ -70,11 +65,40 @@ public class webRequestHandler implements StrongBuilder.Callback<HttpClient>
|
||||||
createUpdateUiHandler();
|
createUpdateUiHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isReloadedUrl = false;
|
|
||||||
public void loadURL(final String url)
|
public void loadURL(final String url)
|
||||||
{
|
{
|
||||||
Log.d("ME HERE 1 : ","SUPER WOW");
|
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
preInitialization(url);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
clientThread = new Thread(() -> {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(url.contains("boogle.store"))
|
||||||
|
{
|
||||||
|
nonProxyConnection(url);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
proxyConnection(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
onError();
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
clientThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void preInitialization(String url)
|
||||||
{
|
{
|
||||||
if(!datamodel.getInstance().getIsLoadingURL())
|
if(!datamodel.getInstance().getIsLoadingURL())
|
||||||
{
|
{
|
||||||
|
@ -84,7 +108,9 @@ public class webRequestHandler implements StrongBuilder.Callback<HttpClient>
|
||||||
{
|
{
|
||||||
request.abort();
|
request.abort();
|
||||||
isReloadedUrl = true;
|
isReloadedUrl = true;
|
||||||
|
if(clientThread!=null)
|
||||||
clientThread.stop();
|
clientThread.stop();
|
||||||
|
clientThread = null;
|
||||||
searchbar.setText(url.replace("http://boogle.store","http://genesis.onion"));
|
searchbar.setText(url.replace("http://boogle.store","http://genesis.onion"));
|
||||||
}
|
}
|
||||||
progressBar.animate().alpha(0f);
|
progressBar.animate().alpha(0f);
|
||||||
|
@ -92,18 +118,8 @@ public class webRequestHandler implements StrongBuilder.Callback<HttpClient>
|
||||||
progressBar.animate().setDuration(300).alpha(1f);
|
progressBar.animate().setDuration(300).alpha(1f);
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.d("ERROR : ","SUPER WOW");
|
|
||||||
}
|
|
||||||
clientThread = new Thread(new Runnable() {
|
|
||||||
|
|
||||||
@Override
|
public void nonProxyConnection(String url) throws IOException {
|
||||||
public void run() {
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if(url.contains("boogle.store"))
|
|
||||||
{
|
|
||||||
HttpClient client = new DefaultHttpClient();
|
HttpClient client = new DefaultHttpClient();
|
||||||
request = new HttpGet(url);
|
request = new HttpGet(url);
|
||||||
baseURL = url;
|
baseURL = url;
|
||||||
|
@ -124,27 +140,6 @@ public class webRequestHandler implements StrongBuilder.Callback<HttpClient>
|
||||||
message.what = MESSAGE_UPDATE_TEXT_CHILD_THREAD;
|
message.what = MESSAGE_UPDATE_TEXT_CHILD_THREAD;
|
||||||
updateUIHandler.sendMessage(message);
|
updateUIHandler.sendMessage(message);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
proxyConnection(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
if(!isReloadedUrl)
|
|
||||||
{
|
|
||||||
Message message = new Message();
|
|
||||||
message.what = INTERNET_ERROR;
|
|
||||||
updateUIHandler.sendMessage(message);
|
|
||||||
Log.d("ERROR : ","SUPER WOW");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
isReloadedUrl = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
clientThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void proxyConnection(String url) throws Exception {
|
public void proxyConnection(String url) throws Exception {
|
||||||
NetCipher.useTor();
|
NetCipher.useTor();
|
||||||
|
@ -152,16 +147,6 @@ public class webRequestHandler implements StrongBuilder.Callback<HttpClient>
|
||||||
connection.setRequestProperty("User-Agent","Mozilla/5.0 ( compatible ) ");
|
connection.setRequestProperty("User-Agent","Mozilla/5.0 ( compatible ) ");
|
||||||
connection.setRequestProperty("Accept","*/*");
|
connection.setRequestProperty("Accept","*/*");
|
||||||
connection.connect();
|
connection.connect();
|
||||||
int status = connection.getResponseCode();
|
|
||||||
|
|
||||||
int responseCode = connection.getResponseCode(); //can call this instead of con.connect()
|
|
||||||
InputStream in;
|
|
||||||
if (responseCode >= 400 && responseCode <= 499) {
|
|
||||||
throw new Exception("Bad authentication status: " + responseCode); //provide a more meaningful exception message
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
in = connection.getInputStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader((connection.getInputStream())));
|
BufferedReader br = new BufferedReader(new InputStreamReader((connection.getInputStream())));
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
@ -176,15 +161,18 @@ public class webRequestHandler implements StrongBuilder.Callback<HttpClient>
|
||||||
updateUIHandler.sendMessage(message);
|
updateUIHandler.sendMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Handler updateUIHandler = null;
|
public void onError()
|
||||||
private final static int MESSAGE_UPDATE_TEXT_CHILD_THREAD =1;
|
|
||||||
private final static int INTERNET_ERROR =2;
|
|
||||||
|
|
||||||
public WebView getView()
|
|
||||||
{
|
{
|
||||||
return view[currentViewIndex];
|
if(!isReloadedUrl)
|
||||||
|
{
|
||||||
|
Message message = new Message();
|
||||||
|
message.what = INTERNET_ERROR;
|
||||||
|
updateUIHandler.sendMessage(message);
|
||||||
|
}
|
||||||
|
isReloadedUrl = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("HandlerLeak")
|
||||||
private void createUpdateUiHandler()
|
private void createUpdateUiHandler()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -194,7 +182,6 @@ public class webRequestHandler implements StrongBuilder.Callback<HttpClient>
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message msg) {
|
public void handleMessage(Message msg) {
|
||||||
Log.i("APPLYING : ","SUCCESS : APPLYING");
|
|
||||||
if(msg.what == MESSAGE_UPDATE_TEXT_CHILD_THREAD)
|
if(msg.what == MESSAGE_UPDATE_TEXT_CHILD_THREAD)
|
||||||
{
|
{
|
||||||
view[viewIndex].animate().setDuration(0).alpha(0f);
|
view[viewIndex].animate().setDuration(0).alpha(0f);
|
||||||
|
@ -211,11 +198,7 @@ public class webRequestHandler implements StrongBuilder.Callback<HttpClient>
|
||||||
viewIndex = 1;
|
viewIndex = 1;
|
||||||
currentViewIndex=0;
|
currentViewIndex=0;
|
||||||
}
|
}
|
||||||
view[currentViewIndex].animate().setDuration(0).alpha(0f).withEndAction((new Runnable() {
|
view[currentViewIndex].animate().setDuration(0).alpha(0f).withEndAction((() -> {
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
else if (msg.what == INTERNET_ERROR)
|
else if (msg.what == INTERNET_ERROR)
|
||||||
|
@ -223,12 +206,8 @@ public class webRequestHandler implements StrongBuilder.Callback<HttpClient>
|
||||||
datamodel.getInstance().setIsLoadingURL(false);
|
datamodel.getInstance().setIsLoadingURL(false);
|
||||||
progressBar.animate().alpha(0f);
|
progressBar.animate().alpha(0f);
|
||||||
requestFailure.setVisibility(View.VISIBLE);
|
requestFailure.setVisibility(View.VISIBLE);
|
||||||
requestFailure.animate().alpha(1f).setDuration(300).withEndAction((new Runnable() {
|
requestFailure.animate().alpha(1f).setDuration(300).withEndAction((() -> {
|
||||||
@Override
|
}));
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}));;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -253,4 +232,37 @@ public class webRequestHandler implements StrongBuilder.Callback<HttpClient>
|
||||||
public void onInvalid() {
|
public void onInvalid() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void getVersion(Context applicationContext)
|
||||||
|
{
|
||||||
|
new Thread()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String webPage = "http://boogle.store/version";
|
||||||
|
URL url = new URL(webPage);
|
||||||
|
URLConnection urlConnection = null;
|
||||||
|
urlConnection = url.openConnection();
|
||||||
|
InputStream is = urlConnection.getInputStream();
|
||||||
|
InputStreamReader isr = new InputStreamReader(is);
|
||||||
|
|
||||||
|
int numCharsRead;
|
||||||
|
char[] charArray = new char[1024];
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
while ((numCharsRead = isr.read(charArray)) > 0) {
|
||||||
|
sb.append(charArray, 0, numCharsRead);
|
||||||
|
}
|
||||||
|
String result = sb.toString();
|
||||||
|
preference_manager.getInstance().saveString("version",result,applicationContext);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,151 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<android.support.constraint.ConstraintLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:context=".MainActivity">
|
|
||||||
<WebView
|
|
||||||
android:id="@+id/pageLoader1"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:layout_editor_absoluteY="8dp" tools:layout_editor_absoluteX="8dp">
|
|
||||||
</WebView>
|
|
||||||
|
|
||||||
<android.support.constraint.ConstraintLayout
|
|
||||||
android:id="@+id/requestFailure"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
android:background="#ffffff"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
>
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="#f8f8f8"
|
|
||||||
android:id="@+id/errorBack" app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"/>
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:textAlignment="viewStart"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inputType="textPersonName"
|
|
||||||
android:text=" Opps! Some Thing Went Wrong"
|
|
||||||
android:textColor="#4d4d4d"
|
|
||||||
android:ems="10"
|
|
||||||
android:id="@+id/editText"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="104dp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"/>
|
|
||||||
<Button
|
|
||||||
android:text="Reload"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:id="@+id/reloadButton"
|
|
||||||
android:background="@drawable/shape"
|
|
||||||
android:textColor="#000000"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
android:radius="1dp"
|
|
||||||
android:bottomRightRadius="10dp"
|
|
||||||
android:bottomLeftRadius="10dp"
|
|
||||||
android:topLeftRadius="10dp"
|
|
||||||
android:topRightRadius="10dp"
|
|
||||||
android:layout_marginBottom="36dp" app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintHorizontal_bias="0.498" app:layout_constraintEnd_toEndOf="parent"/>
|
|
||||||
<TextView
|
|
||||||
android:text="These might be the problems you are facing\n\n\u2022 Webpage or Website might be down\n\u2022 Your Internet connection might be poor\n\u2022 You might be using a proxy\n\u2022 Website might be blocked by firewall"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:id="@+id/textView"
|
|
||||||
android:textColor="#4d4d4d"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="170dp"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="10dp"
|
|
||||||
android:layout_marginEnd="10dp" app:layout_constraintEnd_toEndOf="parent"/>
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="53dp"
|
|
||||||
android:layout_height="53dp" app:srcCompat="@mipmap/ic_launcher_round_v1"
|
|
||||||
android:id="@+id/imageView"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" android:layout_marginTop="4dp"
|
|
||||||
android:layout_marginStart="4dp"/>
|
|
||||||
<TextView
|
|
||||||
android:text="Genesis Search Engine"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textColor="#4d4d4d"
|
|
||||||
android:id="@+id/textView2"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="16dp"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="68dp"/>
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="205dp"
|
|
||||||
android:layout_height="0dp" app:srcCompat="@drawable/interneticon"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/textView"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/reloadButton" android:layout_marginBottom="35dp"
|
|
||||||
android:layout_marginTop="35dp"/>
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
|
||||||
<ProgressBar
|
|
||||||
style="?android:attr/progressBarStyleHorizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="4dp"
|
|
||||||
android:id="@+id/progressBar"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:max="100"
|
|
||||||
android:progress="0" app:layout_constraintTop_toTopOf="parent"
|
|
||||||
/>
|
|
||||||
<android.support.constraint.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" android:id="@+id/splashScreen"
|
|
||||||
android:background="@drawable/backgradient"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintVertical_bias="1.0">
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
style="?android:attr/progressBarStyle"
|
|
||||||
android:layout_width="95dp"
|
|
||||||
android:layout_height="95dp"
|
|
||||||
android:indeterminateTint="#ffffff"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" app:layout_constraintHorizontal_bias="0.5"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginBottom="80dp"/>
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="100dp"
|
|
||||||
android:layout_height="70dp" app:srcCompat="@drawable/logolarge"
|
|
||||||
android:id="@+id/imageView_loading" app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
android:layout_marginBottom="80dp"/>
|
|
||||||
<TextView
|
|
||||||
android:text="Genesis Search Engine"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textColor="#ffffff"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:id="@+id/textView3"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginBottom="16dp"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="35dp"
|
|
||||||
android:layout_marginEnd="231dp" app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintHorizontal_bias="0.0"/>
|
|
||||||
<TextView
|
|
||||||
android:text="Loading Please Wait"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textColor="#ffffff"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:id="@+id/notification" app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
android:layout_marginTop="50dp" android:layout_marginStart="76dp" android:layout_marginEnd="76dp"/>
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
|
||||||
|
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
|
|
@ -5,7 +5,7 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".applicationController">
|
tools:context=".application_controller">
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
@ -63,6 +63,7 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"/>
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
<Button
|
<Button
|
||||||
android:text="Reload"
|
android:text="Reload"
|
||||||
|
android:onClick="onReloadButtonPressed"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/reloadButton"
|
android:id="@+id/reloadButton"
|
||||||
|
@ -119,6 +120,7 @@
|
||||||
android:topLeftRadius="3dp"
|
android:topLeftRadius="3dp"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:src="@drawable/homeicon"
|
android:src="@drawable/homeicon"
|
||||||
|
android:onClick="onHomeButtonPressed"
|
||||||
android:background="@drawable/pressedcolor"
|
android:background="@drawable/pressedcolor"
|
||||||
android:topRightRadius="3dp"/>
|
android:topRightRadius="3dp"/>
|
||||||
<EditText
|
<EditText
|
|
@ -1,46 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<android.support.constraint.ConstraintLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
<android.support.constraint.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" android:id="@+id/splashScreen"
|
|
||||||
android:background="@drawable/backgradient"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintVertical_bias="1.0">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="100dp"
|
|
||||||
android:layout_height="70dp" app:srcCompat="@drawable/logolarge"
|
|
||||||
android:id="@+id/imageView_loading" app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
android:layout_marginBottom="80dp"/>
|
|
||||||
<TextView
|
|
||||||
android:text="Genesis Search Engine"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textColor="#ffffff"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:id="@+id/textView3"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginBottom="16dp"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="35dp"
|
|
||||||
android:layout_marginEnd="231dp" app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintHorizontal_bias="0.0"/>
|
|
||||||
<TextView
|
|
||||||
android:text="Loading Please Wait"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textColor="#ffffff"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:id="@+id/notification" app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
android:layout_marginTop="50dp" android:layout_marginStart="76dp" android:layout_marginEnd="76dp"/>
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 25 KiB |
|
@ -1,7 +1,6 @@
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.3.20'
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
@ -9,7 +8,6 @@ buildscript {
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.3.1'
|
classpath 'com.android.tools.build:gradle:3.3.1'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|