Merge branch 'hidden_services' of https://github.com/arrase/orbot into arrase-hidden_services

This commit is contained in:
Nathan Freitas 2017-01-13 22:19:02 -05:00
commit 26b9199378
53 changed files with 3513 additions and 474 deletions

View File

@ -30,5 +30,5 @@ dependencies {
compile project(':orbotservice') compile project(':orbotservice')
compile 'com.android.support:support-v4:23.4.0' compile 'com.android.support:support-v4:23.4.0'
compile 'com.android.support:appcompat-v7:23.4.0' compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:design:23.4.0'
} }

View File

@ -1,137 +1,178 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.torproject.android" package="org.torproject.android"
android:versionName="15.2.0-RC-8-multi" android:installLocation="auto"
android:versionCode="15208000" android:versionCode="15208000"
android:installLocation="auto" android:versionName="15.2.0-RC-8-multi">
>
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="23"/> <uses-sdk
<!-- android:minSdkVersion="16"
android:targetSdkVersion="23" />
<!--
<permission android:name="org.torproject.android.MANAGE_TOR" <permission android:name="org.torproject.android.MANAGE_TOR"
android:label="@string/permission_manage_tor_label" android:label="@string/permission_manage_tor_label"
android:description="@string/permission_manage_tor_description" android:description="@string/permission_manage_tor_description"
android:protectionLevel="signature"/> android:protectionLevel="signature"/>
<uses-permission android:name="org.torproject.android.MANAGE_TOR"/> <uses-permission android:name="org.torproject.android.MANAGE_TOR"/>
--> -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.ACCESS_SUPERUSER" />
<application android:name="org.torproject.android.OrbotApp" android:icon="@drawable/ic_launcher" <uses-permission android:name="android.permission.INTERNET" />
android:label="@string/app_name" <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
android:description="@string/app_description" <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
android:configChanges="locale|orientation|screenSize" <uses-permission android:name="android.permission.ACCESS_SUPERUSER" />
android:theme="@style/DefaultTheme" <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
android:allowBackup="false" <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
android:allowClearUserData="true" <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
android:largeHeap="false"
android:hardwareAccelerated="false"
> <android:uses-permission android:name="android.permission.READ_PHONE_STATE" />
<activity android:name=".OrbotMainActivity" <application
android:name=".OrbotApp"
android:allowBackup="false"
android:allowClearUserData="true"
android:configChanges="locale|orientation|screenSize"
android:description="@string/app_description"
android:hardwareAccelerated="false"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:largeHeap="false"
android:theme="@style/DefaultTheme">
<activity
android:name=".OrbotMainActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:launchMode="singleTop" android:launchMode="singleTop">
>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="bridge" /> <data android:scheme="bridge" />
</intent-filter> </intent-filter>
<intent-filter>
<intent-filter>
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<action android:name="org.torproject.android.REQUEST_HS_PORT" /> <action android:name="org.torproject.android.REQUEST_HS_PORT" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<action android:name="org.torproject.android.START_TOR" /> <action android:name="org.torproject.android.START_TOR" />
</intent-filter> </intent-filter>
</activity> </activity>
<!-- <!-- This is for ensuring the background service still runs when/if the app is swiped away -->
This is for ensuring the background service still runs when/if the app is swiped away <activity
--> android:name=".service.util.DummyActivity"
<activity android:allowTaskReparenting="true"
android:name=".service.util.DummyActivity" android:alwaysRetainTaskState="false"
android:theme="@android:style/Theme.Translucent" android:clearTaskOnLaunch="true"
android:enabled="true" android:enabled="true"
android:allowTaskReparenting="true" android:excludeFromRecents="true"
android:noHistory="true" android:finishOnTaskLaunch="true"
android:excludeFromRecents="true" android:noHistory="true"
android:alwaysRetainTaskState="false" android:stateNotNeeded="true"
android:stateNotNeeded="true" android:theme="@android:style/Theme.Translucent" />
android:clearTaskOnLaunch="true" <activity
android:finishOnTaskLaunch="true" android:name=".vpn.VPNEnableActivity"
android:exported="false"
/> android:label="@string/app_name" />
<activity
<activity android:name=".ui.PromoAppsActivity"
android:name=".vpn.VPNEnableActivity" android:label="@string/app_name" android:exported="false" android:exported="false" />
/> <activity
android:name=".settings.SettingsPreferences"
android:label="@string/app_name" />
<activity android:name="org.torproject.android.ui.PromoAppsActivity" android:exported="false"/> <activity
android:name=".ui.AppManager"
android:label="@string/app_name"
<activity android:name=".settings.SettingsPreferences" android:label="@string/app_name"/> android:theme="@style/Theme.AppCompat" />
<activity android:name=".ui.AppManager" android:label="@string/app_name"
android:theme="@style/Theme.AppCompat"
/>
<service <service
android:name=".service.TorService" android:name=".service.TorService"
android:enabled="true" android:enabled="true"
android:permission="android.permission.BIND_VPN_SERVICE" android:permission="android.permission.BIND_VPN_SERVICE"
android:stopWithTask="false" > android:stopWithTask="false"></service>
<service
android:name=".service.vpn.TorVpnService"
android:enabled="true"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService" />
</intent-filter>
</service> </service>
<service
android:name=".service.vpn.TorVpnService"
android:enabled="true"
android:permission="android.permission.BIND_VPN_SERVICE" >
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
</service>
<receiver <receiver
android:name=".service.StartTorReceiver" android:name=".service.StartTorReceiver"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="org.torproject.android.intent.action.START" /> <action android:name="org.torproject.android.intent.action.START" />
</intent-filter> </intent-filter>
</receiver>
<receiver
android:name=".OnBootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_MOUNTED" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
</receiver> </receiver>
<receiver android:name=".OnBootReceiver" <activity
android:enabled="true" android:exported="true" android:name=".ui.hiddenservices.HiddenServicesActivity"
android:label="@string/title_activity_hidden_services"
> android:theme="@style/DefaultTheme">
<intent-filter> <meta-data
<action android:name="android.intent.action.BOOT_COMPLETED" /> android:name="android.support.PARENT_ACTIVITY"
<category android:name="android.intent.category.HOME" /> android:value=".OrbotMainActivity" />
</intent-filter> </activity>
<intent-filter>
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_MOUNTED"/>
<category android:name="android.intent.category.HOME" />
</intent-filter>
</receiver>
</application> <provider
</manifest> android:name=".ui.hiddenservices.providers.HSContentProvider"
android:authorities="org.torproject.android.ui.hiddenservices.providers"
android:exported="false" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="org.torproject.android.ui.hiddenservices.storage"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/hidden_services_paths" />
</provider>
<activity
android:name=".ui.hiddenservices.ClientCookiesActivity"
android:label="@string/client_cookies"
android:theme="@style/DefaultTheme">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".OrbotMainActivity" />
</activity>
<provider
android:name=".ui.hiddenservices.providers.CookieContentProvider"
android:authorities="org.torproject.android.ui.hiddenservices.providers.cookie"
android:exported="false" />
</application>
</manifest>

View File

@ -4,6 +4,7 @@
package org.torproject.android; package org.torproject.android;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
@ -26,6 +27,11 @@ import org.torproject.android.ui.AppManager;
import org.torproject.android.ui.ImageProgressView; import org.torproject.android.ui.ImageProgressView;
import org.torproject.android.ui.PromoAppsActivity; import org.torproject.android.ui.PromoAppsActivity;
import org.torproject.android.ui.Rotate3dAnimation; import org.torproject.android.ui.Rotate3dAnimation;
import org.torproject.android.ui.hiddenservices.ClientCookiesActivity;
import org.torproject.android.ui.hiddenservices.HiddenServicesActivity;
import org.torproject.android.ui.hiddenservices.backup.BackupUtils;
import org.torproject.android.ui.hiddenservices.permissions.PermissionManager;
import org.torproject.android.ui.hiddenservices.providers.HSContentProvider;
import org.torproject.android.vpn.VPNEnableActivity; import org.torproject.android.vpn.VPNEnableActivity;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@ -34,6 +40,8 @@ import android.app.ActivityManager.RunningServiceInfo;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
@ -43,6 +51,7 @@ import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
@ -80,6 +89,8 @@ import android.widget.Toast;
import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult; import com.google.zxing.integration.android.IntentResult;
import static android.support.v4.content.FileProvider.getUriForFile;
public class OrbotMainActivity extends AppCompatActivity public class OrbotMainActivity extends AppCompatActivity
implements OrbotConstants, OnLongClickListener, OnTouchListener { implements OrbotConstants, OnLongClickListener, OnTouchListener {
@ -114,8 +125,6 @@ public class OrbotMainActivity extends AppCompatActivity
private final static int REQUEST_SETTINGS = 0x9874; private final static int REQUEST_SETTINGS = 0x9874;
private final static int REQUEST_VPN_APPS_SELECT = 8889; private final static int REQUEST_VPN_APPS_SELECT = 8889;
private final static boolean mIsLollipop = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
// message types for mStatusUpdateHandler // message types for mStatusUpdateHandler
private final static int STATUS_UPDATE = 1; private final static int STATUS_UPDATE = 1;
private static final int MESSAGE_TRAFFIC_COUNT = 2; private static final int MESSAGE_TRAFFIC_COUNT = 2;
@ -140,13 +149,37 @@ public class OrbotMainActivity extends AppCompatActivity
return super.onCreateView(parent, name, context, attrs); return super.onCreateView(parent, name, context, attrs);
return null; return null;
} }
/** Called when the activity is first created. */ private void migratePreferences() {
String hsPortString = mPrefs.getString("pref_hs_ports", "");
if (hsPortString.length() > 0) {
StringTokenizer st = new StringTokenizer(hsPortString, ",");
ContentResolver cr = getContentResolver();
while (st.hasMoreTokens()) {
int hsPort = Integer.parseInt(st.nextToken().split(" ")[0]);
ContentValues fields = new ContentValues();
fields.put("name", hsPort);
fields.put("port", hsPort);
fields.put("onion_port", hsPort);
cr.insert(HSContentProvider.CONTENT_URI, fields);
}
Editor pEdit = mPrefs.edit();
pEdit.remove("pref_hs_ports");
pEdit.commit();
}
}
/**
* Called when the activity is first created.
*/
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mPrefs = TorServiceUtils.getSharedPrefs(getApplicationContext()); mPrefs = TorServiceUtils.getSharedPrefs(getApplicationContext());
migratePreferences(); // Migrate old preferences
/* Create the widgets before registering for broadcasts to guarantee /* Create the widgets before registering for broadcasts to guarantee
* that the widgets exist when the status updates try to update them */ * that the widgets exist when the status updates try to update them */
doLayout(); doLayout();
@ -478,6 +511,10 @@ public class OrbotMainActivity extends AppCompatActivity
} }
} }
} else if (item.getItemId() == R.id.menu_hidden_services) {
startActivity(new Intent(this, HiddenServicesActivity.class));
} else if (item.getItemId() == R.id.menu_client_cookies) {
startActivity(new Intent(this, ClientCookiesActivity.class));
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
@ -548,192 +585,249 @@ public class OrbotMainActivity extends AppCompatActivity
Prefs.putUseVpn(enable); Prefs.putUseVpn(enable);
if (enable) { if (enable) {
if (mIsLollipop) //let the user choose the apps if (PermissionManager.isLollipopOrHigher()) //let the user choose the apps
startActivityForResult(new Intent(OrbotMainActivity.this, AppManager.class), REQUEST_VPN_APPS_SELECT); startActivityForResult(new Intent(OrbotMainActivity.this, AppManager.class), REQUEST_VPN_APPS_SELECT);
else else
startActivity(new Intent(OrbotMainActivity.this, VPNEnableActivity.class)); startActivity(new Intent(OrbotMainActivity.this, VPNEnableActivity.class));
} else } else
stopVpnService(); stopVpnService();
} }
private void enableHiddenServicePort (int hsPort) throws RemoteException, InterruptedException
{
Editor pEdit = mPrefs.edit();
String hsPortString = mPrefs.getString("pref_hs_ports", "");
String onionHostname = mPrefs.getString("pref_hs_hostname","");
if (hsPortString.indexOf(hsPort+"")==-1)
{
if (hsPortString.length() > 0 && hsPortString.indexOf(hsPort+"")==-1)
hsPortString += ',' + hsPort;
else
hsPortString = hsPort + "";
pEdit.putString("pref_hs_ports", hsPortString);
pEdit.putBoolean("pref_hs_enable", true);
pEdit.commit();
}
if (onionHostname == null || onionHostname.length() == 0) private void enableHiddenServicePort(
{ String hsName, final int hsPort, int hsRemotePort,
requestTorRereadConfig(); final String backupToPackage, final Uri hsKeyPath,
final Boolean authCookie
) throws RemoteException, InterruptedException {
new Thread () { String onionHostname = null;
public void run () if (hsName == null)
{ hsName = "hs" + hsPort;
String onionHostname = mPrefs.getString("pref_hs_hostname","");
while (onionHostname.length() == 0) if (hsRemotePort == -1)
{ hsRemotePort = hsPort;
//we need to stop and start Tor
try {
Thread.sleep(3000); //wait three seconds
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
onionHostname = mPrefs.getString("pref_hs_hostname","");
}
Intent nResult = new Intent();
nResult.putExtra("hs_host", onionHostname);
setResult(RESULT_OK, nResult);
finish();
}
}.start();
}
else
{
Intent nResult = new Intent();
nResult.putExtra("hs_host", onionHostname);
setResult(RESULT_OK, nResult);
finish();
}
}
private synchronized void handleIntents ()
{
if (getIntent() == null)
return;
// Get intent, action and MIME type
Intent intent = getIntent();
String action = intent.getAction();
Log.d(TAG, "handleIntents " + action);
//String type = intent.getType(); ContentValues fields = new ContentValues();
fields.put(HSContentProvider.HiddenService.NAME, hsName);
if (action == null) fields.put(HSContentProvider.HiddenService.PORT, hsPort);
return; fields.put(HSContentProvider.HiddenService.ONION_PORT, hsRemotePort);
fields.put(HSContentProvider.HiddenService.AUTH_COOKIE, authCookie);
if (action.equals(INTENT_ACTION_REQUEST_HIDDEN_SERVICE))
{
final int hiddenServicePortRequest = getIntent().getIntExtra("hs_port", -1);
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { ContentResolver cr = getContentResolver();
public void onClick(DialogInterface dialog, int which) {
switch (which){
case DialogInterface.BUTTON_POSITIVE:
try {
enableHiddenServicePort (hiddenServicePortRequest);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Cursor row = cr.query(
break; HSContentProvider.CONTENT_URI,
HSContentProvider.PROJECTION,
HSContentProvider.HiddenService.ONION_PORT + "=" + hsPort,
null,
null
);
case DialogInterface.BUTTON_NEGATIVE: if (row == null || row.getCount() < 1) {
//No button clicked cr.insert(HSContentProvider.CONTENT_URI, fields);
finish(); } else {
break; onionHostname = row.getString(row.getColumnIndex(HSContentProvider.HiddenService.DOMAIN));
} row.close();
} }
};
String requestMsg = getString(R.string.hidden_service_request, hiddenServicePortRequest); if (onionHostname == null || onionHostname.length() < 1) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(requestMsg).setPositiveButton("Allow", dialogClickListener)
.setNegativeButton("Deny", dialogClickListener).show();
return; //don't null the setIntent() as we need it later
}
else if (action.equals(INTENT_ACTION_REQUEST_START_TOR))
{
autoStartFromIntent = true;
startTor();
//never allow backgrounds start from this type of intent start if (hsKeyPath != null) {
//app devs who want background starts, can use the service intents BackupUtils hsutils = new BackupUtils(getApplicationContext());
/** hsutils.restoreKeyBackup(hsPort, hsKeyPath);
if (Prefs.allowBackgroundStarts()) }
{
Intent resultIntent;
if (lastStatusIntent == null) {
resultIntent = new Intent(intent);
} else {
resultIntent = lastStatusIntent;
}
resultIntent.putExtra(TorServiceConstants.EXTRA_STATUS, torStatus);
setResult(RESULT_OK, resultIntent);
finish();
}*/
}
else if (action.equals(Intent.ACTION_VIEW))
{
String urlString = intent.getDataString();
if (urlString != null)
{
if (urlString.toLowerCase().startsWith("bridge://"))
{ if (torStatus.equals(TorServiceConstants.STATUS_OFF)) {
String newBridgeValue = urlString.substring(9); //remove the bridge protocol piece startTor();
newBridgeValue = URLDecoder.decode(newBridgeValue); //decode the value here } else {
stopTor();
Toast.makeText(
this, R.string.start_tor_again_for_finish_the_process, Toast.LENGTH_LONG
).show();
}
showAlert(getString(R.string.bridges_updated),getString(R.string.restart_orbot_to_use_this_bridge_) + newBridgeValue,false); new Thread() {
setNewBridges(newBridgeValue);
}
}
}
updateStatus(null);
setIntent(null);
}
private void setNewBridges (String newBridgeValue)
{
Prefs.setBridgesList(newBridgeValue); //set the string to a preference public void run() {
Prefs.putBridgesEnabled(true); String hostname = null;
Intent nResult = new Intent();
setResult(RESULT_OK);
while (hostname == null) {
mBtnBridges.setChecked(true); try {
Thread.sleep(3000); //wait three seconds
enableBridges(true); } catch (Exception e) {
} // TODO Auto-generated catch block
e.printStackTrace();
}
Cursor onion = getContentResolver().query(
HSContentProvider.CONTENT_URI,
HSContentProvider.PROJECTION,
HSContentProvider.HiddenService.ONION_PORT + "=" + hsPort,
null,
null
);
if (onion != null && onion.getCount() > 0) {
onion.moveToNext();
hostname = onion.getString(onion.getColumnIndex(HSContentProvider.HiddenService.DOMAIN));
if(hostname == null || hostname.length() < 1)
continue;
nResult.putExtra("hs_host", hostname);
if (authCookie) {
nResult.putExtra(
"hs_auth_cookie",
onion.getString(onion.getColumnIndex(HSContentProvider.HiddenService.AUTH_COOKIE_VALUE))
);
}
if (backupToPackage != null && backupToPackage.length() > 0) {
String servicePath = getFilesDir() + "/" + TorServiceConstants.HIDDEN_SERVICES_DIR + "/hs" + hsPort;
File hidden_service_key = new File(servicePath, "private_key");
Context context = getApplicationContext();
Uri contentUri = getUriForFile(
context,
"org.torproject.android.ui.hiddenservices.storage",
hidden_service_key
);
context.grantUriPermission(backupToPackage, contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
nResult.setData(contentUri);
nResult.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
onion.close();
setResult(RESULT_OK, nResult);
finish();
}
}
}
}.start();
} else {
Intent nResult = new Intent();
nResult.putExtra("hs_host", onionHostname);
setResult(RESULT_OK, nResult);
finish();
}
}
private synchronized void handleIntents() {
if (getIntent() == null)
return;
// Get intent, action and MIME type
Intent intent = getIntent();
String action = intent.getAction();
Log.d(TAG, "handleIntents " + action);
//String type = intent.getType();
if (action == null)
return;
switch (action) {
case INTENT_ACTION_REQUEST_HIDDEN_SERVICE:
final int hiddenServicePort = intent.getIntExtra("hs_port", -1);
final int hiddenServiceRemotePort = intent.getIntExtra("hs_onion_port", -1);
final String hiddenServiceName = intent.getStringExtra("hs_name");
final String backupToPackage = intent.getStringExtra("hs_backup_to_package");
final Boolean authCookie = intent.getBooleanExtra("hs_auth_cookie", false);
final Uri mKeyUri = intent.getData();
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
try {
enableHiddenServicePort(
hiddenServiceName, hiddenServicePort,
hiddenServiceRemotePort, backupToPackage,
mKeyUri, authCookie
);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
}
}
};
String requestMsg = getString(R.string.hidden_service_request, hiddenServicePort);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(requestMsg).setPositiveButton("Allow", dialogClickListener)
.setNegativeButton("Deny", dialogClickListener).show();
return; //don't null the setIntent() as we need it later
case INTENT_ACTION_REQUEST_START_TOR:
autoStartFromIntent = true;
startTor();
//never allow backgrounds start from this type of intent start
//app devs who want background starts, can use the service intents
/**
if (Prefs.allowBackgroundStarts())
{
Intent resultIntent;
if (lastStatusIntent == null) {
resultIntent = new Intent(intent);
} else {
resultIntent = lastStatusIntent;
}
resultIntent.putExtra(TorServiceConstants.EXTRA_STATUS, torStatus);
setResult(RESULT_OK, resultIntent);
finish();
}*/
break;
case Intent.ACTION_VIEW:
String urlString = intent.getDataString();
if (urlString != null) {
if (urlString.toLowerCase().startsWith("bridge://"))
{
String newBridgeValue = urlString.substring(9); //remove the bridge protocol piece
newBridgeValue = URLDecoder.decode(newBridgeValue); //decode the value here
showAlert(getString(R.string.bridges_updated), getString(R.string.restart_orbot_to_use_this_bridge_) + newBridgeValue, false);
setNewBridges(newBridgeValue);
}
}
break;
}
updateStatus(null);
setIntent(null);
}
private void setNewBridges(String newBridgeValue) {
Prefs.setBridgesList(newBridgeValue); //set the string to a preference
Prefs.putBridgesEnabled(true);
setResult(RESULT_OK);
mBtnBridges.setChecked(true);
enableBridges(true);
}
/* /*
* Launch the system activity for Uri viewing with the provided url * Launch the system activity for Uri viewing with the provided url
@ -1193,11 +1287,7 @@ public class OrbotMainActivity extends AppCompatActivity
if (autoStartFromIntent) if (autoStartFromIntent)
{ {
autoStartFromIntent = false; autoStartFromIntent = false;
Intent resultIntent = lastStatusIntent; Intent resultIntent = lastStatusIntent;
if (resultIntent == null)
resultIntent = new Intent(TorServiceConstants.ACTION_START);
resultIntent.putExtra(TorServiceConstants.EXTRA_STATUS, torStatus); resultIntent.putExtra(TorServiceConstants.EXTRA_STATUS, torStatus);
setResult(RESULT_OK, resultIntent); setResult(RESULT_OK, resultIntent);
finish(); finish();

View File

@ -37,9 +37,6 @@ public class SettingsPreferences
private Preference prefTransProxyFlush = null; private Preference prefTransProxyFlush = null;
private Preference prefTransProxyApps = null; private Preference prefTransProxyApps = null;
private CheckBoxPreference prefHiddenServices = null;
private EditTextPreference prefHiddenServicesPorts;
private EditTextPreference prefHiddenServicesHostname;
private CheckBoxPreference prefRequestRoot = null; private CheckBoxPreference prefRequestRoot = null;
private ListPreference prefLocale = null; private ListPreference prefLocale = null;
@ -104,22 +101,14 @@ public class SettingsPreferences
prefTransProxyApps.setOnPreferenceClickListener(this); prefTransProxyApps.setOnPreferenceClickListener(this);
prefCBTransProxy.setOnPreferenceClickListener(this); prefCBTransProxy.setOnPreferenceClickListener(this);
prefcBTransProxyAll.setOnPreferenceClickListener(this); prefcBTransProxyAll.setOnPreferenceClickListener(this);
prefHiddenServices = (CheckBoxPreference) findPreference("pref_hs_enable");
prefHiddenServices.setOnPreferenceClickListener(this);
prefHiddenServicesHostname = (EditTextPreference) findPreference("pref_hs_hostname");
prefCBTransProxy.setEnabled(prefRequestRoot.isChecked()); prefCBTransProxy.setEnabled(prefRequestRoot.isChecked());
prefcBTransProxyAll.setEnabled(prefCBTransProxy.isChecked()); prefcBTransProxyAll.setEnabled(prefCBTransProxy.isChecked());
prefcbTransTethering.setEnabled(prefCBTransProxy.isChecked()); prefcbTransTethering.setEnabled(prefCBTransProxy.isChecked());
if (prefCBTransProxy.isChecked()) if (prefCBTransProxy.isChecked())
prefTransProxyApps.setEnabled((!prefcBTransProxyAll.isChecked())); prefTransProxyApps.setEnabled((!prefcBTransProxyAll.isChecked()));
prefHiddenServicesPorts = (EditTextPreference) findPreference("pref_hs_ports");
prefHiddenServicesHostname.setEnabled(prefHiddenServices.isChecked());
prefHiddenServicesPorts.setEnabled(prefHiddenServices.isChecked());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{ {
prefTransProxyApps.setEnabled(true); prefTransProxyApps.setEnabled(true);
@ -153,11 +142,6 @@ public class SettingsPreferences
{ {
startActivity(new Intent(this, AppManager.class)); startActivity(new Intent(this, AppManager.class));
}
else if (preference == prefHiddenServices)
{
prefHiddenServicesPorts.setEnabled(prefHiddenServices.isChecked());
prefHiddenServicesHostname.setEnabled(prefHiddenServices.isChecked());
} }
else else
{ {

View File

@ -0,0 +1,199 @@
package org.torproject.android.ui.hiddenservices;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
import org.json.JSONException;
import org.json.JSONObject;
import org.torproject.android.R;
import org.torproject.android.ui.hiddenservices.adapters.ClientCookiesAdapter;
import org.torproject.android.ui.hiddenservices.dialogs.AddCookieDialog;
import org.torproject.android.ui.hiddenservices.dialogs.CookieActionsDialog;
import org.torproject.android.ui.hiddenservices.dialogs.SelectCookieBackupDialog;
import org.torproject.android.ui.hiddenservices.permissions.PermissionManager;
import org.torproject.android.ui.hiddenservices.providers.CookieContentProvider;
public class ClientCookiesActivity extends AppCompatActivity {
public final int WRITE_EXTERNAL_STORAGE_FROM_COOKIE_ACTIONBAR = 3;
private ContentResolver mResolver;
private ClientCookiesAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_activity_client_cookies);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mResolver = getContentResolver();
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
AddCookieDialog dialog = new AddCookieDialog();
dialog.show(getSupportFragmentManager(), "AddCookieDialog");
}
});
mAdapter = new ClientCookiesAdapter(
this,
mResolver.query(CookieContentProvider.CONTENT_URI, CookieContentProvider.PROJECTION, null, null, null)
, 0);
mResolver.registerContentObserver(
CookieContentProvider.CONTENT_URI, true, new HSObserver(new Handler())
);
ListView cookies = (ListView) findViewById(R.id.clien_cookies_list);
cookies.setAdapter(mAdapter);
cookies.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Cursor item = (Cursor) parent.getItemAtPosition(position);
Bundle arguments = new Bundle();
arguments.putInt(
"_id", item.getInt(item.getColumnIndex(CookieContentProvider.ClientCookie._ID))
);
arguments.putString(
"domain", item.getString(item.getColumnIndex(CookieContentProvider.ClientCookie.DOMAIN))
);
arguments.putString(
"auth_cookie_value", item.getString(item.getColumnIndex(CookieContentProvider.ClientCookie.AUTH_COOKIE_VALUE))
);
arguments.putInt(
"enabled", item.getInt(item.getColumnIndex(CookieContentProvider.ClientCookie.ENABLED))
);
CookieActionsDialog dialog = new CookieActionsDialog();
dialog.setArguments(arguments);
dialog.show(getSupportFragmentManager(), "CookieActionsDialog");
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.cookie_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.cookie_restore_backup) {
if (PermissionManager.isLollipopOrHigher()
&& !PermissionManager.hasExternalWritePermission(this)) {
PermissionManager.requestExternalWritePermissions(this, WRITE_EXTERNAL_STORAGE_FROM_COOKIE_ACTIONBAR);
return true;
}
SelectCookieBackupDialog dialog = new SelectCookieBackupDialog();
dialog.show(getSupportFragmentManager(), "SelectCookieBackupDialog");
} else if (id == R.id.cookie_from_qr) {
IntentIntegrator integrator = new IntentIntegrator(ClientCookiesActivity.this);
integrator.initiateScan();
}
return super.onOptionsItemSelected(item);
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
if (grantResults.length < 1
|| grantResults[0] != PackageManager.PERMISSION_GRANTED) {
return;
}
switch (requestCode) {
case WRITE_EXTERNAL_STORAGE_FROM_COOKIE_ACTIONBAR: {
SelectCookieBackupDialog dialog = new SelectCookieBackupDialog();
dialog.show(getSupportFragmentManager(), "SelectCookieBackupDialog");
break;
}
case CookieActionsDialog.WRITE_EXTERNAL_STORAGE_FROM_COOKIE_ACTION_DIALOG: {
Toast.makeText(this, R.string.click_again_for_backup, Toast.LENGTH_LONG).show();
break;
}
}
}
@Override
protected void onActivityResult(int request, int response, Intent data) {
super.onActivityResult(request, response, data);
IntentResult scanResult = IntentIntegrator.parseActivityResult(request, response, data);
if (scanResult == null) return;
String results = scanResult.getContents();
if (results == null || results.length() < 1) return;
try {
JSONObject savedValues = new JSONObject(results);
ContentValues fields = new ContentValues();
fields.put(
CookieContentProvider.ClientCookie.DOMAIN,
savedValues.getString(CookieContentProvider.ClientCookie.DOMAIN)
);
fields.put(
CookieContentProvider.ClientCookie.AUTH_COOKIE_VALUE,
savedValues.getString(CookieContentProvider.ClientCookie.AUTH_COOKIE_VALUE)
);
mResolver.insert(CookieContentProvider.CONTENT_URI, fields);
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(this, R.string.error, Toast.LENGTH_LONG).show();
}
}
class HSObserver extends ContentObserver {
HSObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
mAdapter.changeCursor(mResolver.query(
CookieContentProvider.CONTENT_URI, CookieContentProvider.PROJECTION, null, null, null
));
}
}
}

View File

@ -0,0 +1,214 @@
package org.torproject.android.ui.hiddenservices;
import android.content.ContentResolver;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.Toast;
import org.torproject.android.R;
import org.torproject.android.ui.hiddenservices.adapters.OnionListAdapter;
import org.torproject.android.ui.hiddenservices.dialogs.HSActionsDialog;
import org.torproject.android.ui.hiddenservices.dialogs.HSDataDialog;
import org.torproject.android.ui.hiddenservices.dialogs.SelectHSBackupDialog;
import org.torproject.android.ui.hiddenservices.permissions.PermissionManager;
import org.torproject.android.ui.hiddenservices.providers.HSContentProvider;
public class HiddenServicesActivity extends AppCompatActivity {
public final int WRITE_EXTERNAL_STORAGE_FROM_ACTIONBAR = 1;
private ContentResolver mResolver;
private OnionListAdapter mAdapter;
private FloatingActionButton fab;
private String mWhere = HSContentProvider.HiddenService.CREATED_BY_USER + "=1";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_hs_list_view);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mResolver = getContentResolver();
fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
HSDataDialog dialog = new HSDataDialog();
dialog.show(getSupportFragmentManager(), "HSDataDialog");
}
});
mAdapter = new OnionListAdapter(
this,
mResolver.query(
HSContentProvider.CONTENT_URI, HSContentProvider.PROJECTION, mWhere, null, null
),
0
);
mResolver.registerContentObserver(
HSContentProvider.CONTENT_URI, true, new HSObserver(new Handler())
);
ListView onion_list = (ListView) findViewById(R.id.onion_list);
onion_list.setAdapter(mAdapter);
onion_list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Cursor item = (Cursor) parent.getItemAtPosition(position);
Bundle arguments = new Bundle();
arguments.putInt(
"_id", item.getInt(item.getColumnIndex(HSContentProvider.HiddenService._ID))
);
arguments.putString(
"port", item.getString(item.getColumnIndex(HSContentProvider.HiddenService.PORT))
);
arguments.putString(
"onion", item.getString(item.getColumnIndex(HSContentProvider.HiddenService.DOMAIN))
);
arguments.putInt(
"auth_cookie", item.getInt(item.getColumnIndex(HSContentProvider.HiddenService.AUTH_COOKIE))
);
arguments.putString(
"auth_cookie_value", item.getString(item.getColumnIndex(HSContentProvider.HiddenService.AUTH_COOKIE_VALUE))
);
HSActionsDialog dialog = new HSActionsDialog();
dialog.setArguments(arguments);
dialog.show(getSupportFragmentManager(), "HSActionsDialog");
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.hs_menu, menu);
MenuItem item = menu.findItem(R.id.hs_type);
Spinner spinner = (Spinner) MenuItemCompat.getActionView(item);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
this, R.array.array_hs_types, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> arg0, View v, int pos, long id) {
if (pos == 0) {
mWhere = HSContentProvider.HiddenService.CREATED_BY_USER + "=1";
fab.show();
} else {
mWhere = HSContentProvider.HiddenService.CREATED_BY_USER + "=0";
fab.hide();
}
mAdapter.changeCursor(mResolver.query(
HSContentProvider.CONTENT_URI, HSContentProvider.PROJECTION, mWhere, null, null
));
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {
// Do nothing
}
});
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.menu_restore_backup) {
if (PermissionManager.isLollipopOrHigher()
&& !PermissionManager.hasExternalWritePermission(this)) {
PermissionManager.requestExternalWritePermissions(this, WRITE_EXTERNAL_STORAGE_FROM_ACTIONBAR);
return true;
}
SelectHSBackupDialog dialog = new SelectHSBackupDialog();
dialog.show(getSupportFragmentManager(), "SelectHSBackupDialog");
}
return super.onOptionsItemSelected(item);
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
if (grantResults.length < 1
|| grantResults[0] != PackageManager.PERMISSION_GRANTED) {
return;
}
switch (requestCode) {
case WRITE_EXTERNAL_STORAGE_FROM_ACTIONBAR: {
SelectHSBackupDialog dialog = new SelectHSBackupDialog();
dialog.show(getSupportFragmentManager(), "SelectHSBackupDialog");
break;
}
case HSActionsDialog.WRITE_EXTERNAL_STORAGE_FROM_ACTION_DIALOG: {
Toast.makeText(this, R.string.click_again_for_backup, Toast.LENGTH_LONG).show();
break;
}
}
}
class HSObserver extends ContentObserver {
HSObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
mAdapter.changeCursor(mResolver.query(
HSContentProvider.CONTENT_URI, HSContentProvider.PROJECTION, mWhere, null, null
));
if (PermissionManager.isLollipopOrHigher()) {
Cursor active = mResolver.query(
HSContentProvider.CONTENT_URI, HSContentProvider.PROJECTION, HSContentProvider.HiddenService.ENABLED + "=1", null, null
);
if (active == null) return;
if (active.getCount() > 0) // Call only if there running services
PermissionManager.requestBatteryPermmssions(HiddenServicesActivity.this, getApplicationContext());
else // Drop whe not needed
PermissionManager.requestDropBatteryPermmssions(HiddenServicesActivity.this, getApplicationContext());
active.close();
}
}
}
}

View File

@ -0,0 +1,50 @@
package org.torproject.android.ui.hiddenservices.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import org.torproject.android.R;
import java.io.File;
import java.util.List;
public class BackupAdapter extends ArrayAdapter<File> {
private int mResource;
public BackupAdapter(Context context, int resource) {
super(context, resource);
mResource = resource;
}
public BackupAdapter(Context context, int resource, List<File> zips) {
super(context, resource, zips);
mResource = resource;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi;
vi = LayoutInflater.from(getContext());
v = vi.inflate(mResource, null);
}
File p = getItem(position);
if (p != null) {
TextView name = (TextView) v.findViewById(R.id.backup_name);
if (name != null)
name.setText(p.getName());
}
return v;
}
}

View File

@ -0,0 +1,64 @@
package org.torproject.android.ui.hiddenservices.adapters;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import org.torproject.android.R;
import org.torproject.android.ui.hiddenservices.providers.CookieContentProvider;
public class ClientCookiesAdapter extends CursorAdapter {
private LayoutInflater cursorInflater;
public ClientCookiesAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
cursorInflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
final Context mContext = context;
int id = cursor.getInt(cursor.getColumnIndex(CookieContentProvider.ClientCookie._ID));
final String where = CookieContentProvider.ClientCookie._ID + "=" + id;
TextView domain = (TextView) view.findViewById(R.id.cookie_onion);
domain.setText(cursor.getString(cursor.getColumnIndex(CookieContentProvider.ClientCookie.DOMAIN)));
Switch enabled = (Switch) view.findViewById(R.id.cookie_switch);
enabled.setChecked(
cursor.getInt(cursor.getColumnIndex(CookieContentProvider.ClientCookie.ENABLED)) == 1
);
enabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
ContentResolver resolver = mContext.getContentResolver();
ContentValues fields = new ContentValues();
fields.put(CookieContentProvider.ClientCookie.ENABLED, isChecked);
resolver.update(
CookieContentProvider.CONTENT_URI, fields, where, null
);
Toast.makeText(
mContext, R.string.please_restart_Orbot_to_enable_the_changes, Toast.LENGTH_LONG
).show();
}
});
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return cursorInflater.inflate(R.layout.layout_client_cookie_list_item, parent, false);
}
}

View File

@ -0,0 +1,68 @@
package org.torproject.android.ui.hiddenservices.adapters;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import org.torproject.android.R;
import org.torproject.android.ui.hiddenservices.providers.HSContentProvider;
public class OnionListAdapter extends CursorAdapter {
private LayoutInflater cursorInflater;
public OnionListAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
cursorInflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
final Context mContext = context;
int id = cursor.getInt(cursor.getColumnIndex(HSContentProvider.HiddenService._ID));
final String where = HSContentProvider.HiddenService._ID + "=" + id;
TextView port = (TextView) view.findViewById(R.id.hs_port);
port.setText(cursor.getString(cursor.getColumnIndex(HSContentProvider.HiddenService.PORT)));
TextView name = (TextView) view.findViewById(R.id.hs_name);
name.setText(cursor.getString(cursor.getColumnIndex(HSContentProvider.HiddenService.NAME)));
TextView domain = (TextView) view.findViewById(R.id.hs_onion);
domain.setText(cursor.getString(cursor.getColumnIndex(HSContentProvider.HiddenService.DOMAIN)));
Switch enabled = (Switch) view.findViewById(R.id.hs_switch);
enabled.setChecked(
cursor.getInt(cursor.getColumnIndex(HSContentProvider.HiddenService.ENABLED)) == 1
);
enabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
ContentResolver resolver = mContext.getContentResolver();
ContentValues fields = new ContentValues();
fields.put(HSContentProvider.HiddenService.ENABLED, isChecked);
resolver.update(
HSContentProvider.CONTENT_URI, fields, where, null
);
Toast.makeText(
mContext, R.string.please_restart_Orbot_to_enable_the_changes, Toast.LENGTH_LONG
).show();
}
});
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return cursorInflater.inflate(R.layout.layout_hs_list_item, parent, false);
}
}

View File

@ -0,0 +1,336 @@
package org.torproject.android.ui.hiddenservices.backup;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.widget.Toast;
import org.json.JSONException;
import org.json.JSONObject;
import org.torproject.android.R;
import org.torproject.android.service.TorServiceConstants;
import org.torproject.android.ui.hiddenservices.providers.CookieContentProvider;
import org.torproject.android.ui.hiddenservices.providers.HSContentProvider;
import org.torproject.android.ui.hiddenservices.storage.ExternalStorage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
public class BackupUtils {
private final String configFileName = "config.json";
private Context mContext;
private ContentResolver mResolver;
public BackupUtils(Context context) {
mContext = context;
mResolver = mContext.getContentResolver();
}
public String createZipBackup(Integer port) {
File mHSBasePath = new File(
mContext.getFilesDir().getAbsolutePath(),
TorServiceConstants.HIDDEN_SERVICES_DIR
);
String configFilePath = mHSBasePath + "/hs" + port + "/" + configFileName;
String hostnameFilePath = mHSBasePath + "/hs" + port + "/hostname";
String keyFilePath = mHSBasePath + "/hs" + port + "/private_key";
File storage_path = ExternalStorage.getOrCreateBackupDir();
if (storage_path == null)
return null;
Cursor portData = mResolver.query(
HSContentProvider.CONTENT_URI,
HSContentProvider.PROJECTION,
HSContentProvider.HiddenService.PORT + "=" + port,
null,
null
);
JSONObject config = new JSONObject();
try {
if (portData == null || portData.getCount() != 1)
return null;
portData.moveToNext();
config.put(
HSContentProvider.HiddenService.NAME,
portData.getString(portData.getColumnIndex(HSContentProvider.HiddenService.NAME))
);
config.put(
HSContentProvider.HiddenService.PORT,
portData.getInt(portData.getColumnIndex(HSContentProvider.HiddenService.PORT))
);
config.put(
HSContentProvider.HiddenService.ONION_PORT,
portData.getInt(portData.getColumnIndex(HSContentProvider.HiddenService.ONION_PORT))
);
config.put(
HSContentProvider.HiddenService.DOMAIN,
portData.getString(portData.getColumnIndex(HSContentProvider.HiddenService.DOMAIN))
);
config.put(
HSContentProvider.HiddenService.AUTH_COOKIE,
portData.getInt(portData.getColumnIndex(HSContentProvider.HiddenService.AUTH_COOKIE))
);
config.put(
HSContentProvider.HiddenService.AUTH_COOKIE_VALUE,
portData.getString(portData.getColumnIndex(HSContentProvider.HiddenService.AUTH_COOKIE_VALUE))
);
config.put(
HSContentProvider.HiddenService.CREATED_BY_USER,
portData.getInt(portData.getColumnIndex(HSContentProvider.HiddenService.CREATED_BY_USER))
);
config.put(
HSContentProvider.HiddenService.ENABLED,
portData.getInt(portData.getColumnIndex(HSContentProvider.HiddenService.ENABLED))
);
} catch (JSONException e) {
e.printStackTrace();
return null;
} catch (NullPointerException e) {
e.printStackTrace();
return null;
}
portData.close();
try {
FileWriter file = new FileWriter(configFilePath);
file.write(config.toString());
file.close();
} catch (IOException e) {
e.printStackTrace();
return null;
}
String zip_path = storage_path.getAbsolutePath() + "/hs" + port + ".zip";
String files[] = {hostnameFilePath, keyFilePath, configFilePath};
ZipIt zip = new ZipIt(files, zip_path);
if (!zip.zip())
return null;
return zip_path;
}
public void restoreZipBackup(File backup) {
File mHSBasePath = new File(
mContext.getFilesDir().getAbsolutePath(),
TorServiceConstants.HIDDEN_SERVICES_DIR
);
int port;
Cursor service;
String backupName = backup.getName();
String hsDir = backupName.substring(0, backupName.lastIndexOf('.'));
String configFilePath = mHSBasePath + "/" + hsDir + "/" + configFileName;
String jString = null;
File hsPath = new File(mHSBasePath.getAbsolutePath(), hsDir);
if (!hsPath.isDirectory())
hsPath.mkdirs();
ZipIt zip = new ZipIt(null, backup.getAbsolutePath());
zip.unzip(hsPath.getAbsolutePath());
File config = new File(configFilePath);
FileInputStream stream;
try {
stream = new FileInputStream(config);
FileChannel fc = stream.getChannel();
MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
jString = Charset.defaultCharset().decode(bb).toString();
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
if (jString == null)
Toast.makeText(mContext, R.string.error, Toast.LENGTH_LONG).show();
try {
JSONObject savedValues = new JSONObject(jString);
ContentValues fields = new ContentValues();
fields.put(
HSContentProvider.HiddenService.NAME,
savedValues.getString(HSContentProvider.HiddenService.NAME)
);
fields.put(
HSContentProvider.HiddenService.ONION_PORT,
savedValues.getInt(HSContentProvider.HiddenService.ONION_PORT)
);
fields.put(
HSContentProvider.HiddenService.DOMAIN,
savedValues.getString(HSContentProvider.HiddenService.DOMAIN)
);
fields.put(
HSContentProvider.HiddenService.AUTH_COOKIE,
savedValues.getInt(HSContentProvider.HiddenService.AUTH_COOKIE)
);
fields.put(
HSContentProvider.HiddenService.CREATED_BY_USER,
savedValues.getInt(HSContentProvider.HiddenService.CREATED_BY_USER)
);
fields.put(
HSContentProvider.HiddenService.ENABLED,
savedValues.getInt(HSContentProvider.HiddenService.ENABLED)
);
port = savedValues.getInt(HSContentProvider.HiddenService.PORT);
fields.put(HSContentProvider.HiddenService.PORT, port);
service = mResolver.query(
HSContentProvider.CONTENT_URI,
HSContentProvider.PROJECTION,
HSContentProvider.HiddenService.PORT + "=" + port,
null,
null
);
if (service == null || service.getCount() == 0) {
mResolver.insert(HSContentProvider.CONTENT_URI, fields);
} else {
mResolver.update(
HSContentProvider.CONTENT_URI,
fields,
HSContentProvider.HiddenService.PORT + "=" + port,
null
);
service.close();
}
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(mContext, R.string.error, Toast.LENGTH_LONG).show();
}
Toast.makeText(mContext, R.string.backup_restored, Toast.LENGTH_LONG).show();
}
public void restoreKeyBackup(int hsPort, Uri hsKeyPath) {
File mHSBasePath = new File(
mContext.getFilesDir().getAbsolutePath(),
TorServiceConstants.HIDDEN_SERVICES_DIR
);
File serviceDir = new File(mHSBasePath, "hs" + hsPort);
if (!serviceDir.isDirectory())
serviceDir.mkdirs();
try {
ParcelFileDescriptor mInputPFD = mContext.getContentResolver().openFileDescriptor(hsKeyPath, "r");
InputStream fileStream = new FileInputStream(mInputPFD.getFileDescriptor());
OutputStream file = new FileOutputStream(serviceDir.getAbsolutePath() + "/private_key");
byte[] buffer = new byte[1024];
int length;
while ((length = fileStream.read(buffer)) > 0) {
file.write(buffer, 0, length);
}
file.close();
} catch (IOException | NullPointerException e) {
e.printStackTrace();
}
}
public void restoreCookieBackup(File p) {
File config = new File(p.getAbsolutePath());
FileInputStream stream;
String jString = null;
try {
stream = new FileInputStream(config);
FileChannel fc = stream.getChannel();
MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
jString = Charset.defaultCharset().decode(bb).toString();
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
if (jString == null)
Toast.makeText(mContext, R.string.error, Toast.LENGTH_LONG).show();
try {
JSONObject savedValues = new JSONObject(jString);
ContentValues fields = new ContentValues();
fields.put(
CookieContentProvider.ClientCookie.DOMAIN,
savedValues.getString(CookieContentProvider.ClientCookie.DOMAIN)
);
fields.put(
CookieContentProvider.ClientCookie.AUTH_COOKIE_VALUE,
savedValues.getString(CookieContentProvider.ClientCookie.AUTH_COOKIE_VALUE)
);
fields.put(
CookieContentProvider.ClientCookie.ENABLED,
savedValues.getInt(CookieContentProvider.ClientCookie.ENABLED)
);
mResolver.insert(CookieContentProvider.CONTENT_URI, fields);
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(mContext, R.string.error, Toast.LENGTH_LONG).show();
}
Toast.makeText(mContext, R.string.backup_restored, Toast.LENGTH_LONG).show();
}
public String createCookieBackup(String domain, String cookie, Integer enabled) {
File storage_path = ExternalStorage.getOrCreateBackupDir();
String backupFile = storage_path.getAbsolutePath() + '/' + domain.replace(".onion", ".json");
JSONObject backup = new JSONObject();
try {
backup.put(CookieContentProvider.ClientCookie.DOMAIN, domain);
backup.put(CookieContentProvider.ClientCookie.AUTH_COOKIE_VALUE, cookie);
backup.put(CookieContentProvider.ClientCookie.ENABLED, enabled);
FileWriter file = new FileWriter(backupFile);
file.write(backup.toString());
file.close();
} catch (JSONException | IOException e) {
e.printStackTrace();
return null;
}
return backupFile;
}
}

View File

@ -0,0 +1,101 @@
package org.torproject.android.ui.hiddenservices.backup;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class ZipIt {
private static final int BUFFER = 2048;
private String[] _files;
private String _zipFile;
public ZipIt(@Nullable String[] files, @NonNull String zipFile) {
_files = files;
_zipFile = zipFile;
}
public boolean zip() {
try {
BufferedInputStream origin;
FileOutputStream dest = new FileOutputStream(_zipFile);
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest));
byte data[] = new byte[BUFFER];
for (String _file : _files) {
FileInputStream fi = new FileInputStream(_file);
origin = new BufferedInputStream(fi, BUFFER);
ZipEntry entry = new ZipEntry(_file.substring(_file.lastIndexOf("/") + 1));
out.putNextEntry(entry);
int count;
while ((count = origin.read(data, 0, BUFFER)) != -1) {
out.write(data, 0, count);
}
origin.close();
}
out.close();
} catch (Exception e) {
return false;
}
return true;
}
public boolean unzip(String output_path) {
InputStream is;
ZipInputStream zis;
try {
String filename;
is = new FileInputStream(_zipFile);
zis = new ZipInputStream(new BufferedInputStream(is));
ZipEntry ze;
byte[] buffer = new byte[1024];
int count;
while ((ze = zis.getNextEntry()) != null) {
// zapis do souboru
filename = ze.getName();
// Need to create directories if not exists, or
// it will generate an Exception...
if (ze.isDirectory()) {
File fmd = new File(output_path + "/" + filename);
fmd.mkdirs();
continue;
}
FileOutputStream fout = new FileOutputStream(output_path + "/" + filename);
// cteni zipu a zapis
while ((count = zis.read(buffer)) != -1) {
fout.write(buffer, 0, count);
}
fout.close();
zis.closeEntry();
}
zis.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
}

View File

@ -0,0 +1,47 @@
package org.torproject.android.ui.hiddenservices.database;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class HSDatabase extends SQLiteOpenHelper {
public static final String HS_DATA_TABLE_NAME = "hs_data";
public static final String HS_CLIENT_COOKIE_TABLE_NAME = "hs_client_cookie";
private static final int DATABASE_VERSION = 2;
private static final String DATABASE_NAME = "hidden_services";
private static final String HS_DATA_TABLE_CREATE =
"CREATE TABLE " + HS_DATA_TABLE_NAME + " (" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"name TEXT, " +
"domain TEXT, " +
"onion_port INTEGER, " +
"auth_cookie INTEGER DEFAULT 0, " +
"auth_cookie_value TEXT, " +
"created_by_user INTEGER DEFAULT 0, " +
"enabled INTEGER DEFAULT 1, " +
"port INTEGER);";
private static final String HS_CLIENT_COOKIE_TABLE_CREATE =
"CREATE TABLE " + HS_CLIENT_COOKIE_TABLE_NAME + " (" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"domain TEXT, " +
"auth_cookie_value TEXT, " +
"enabled INTEGER DEFAULT 1);";
public HSDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(HS_DATA_TABLE_CREATE);
db.execSQL(HS_CLIENT_COOKIE_TABLE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}

View File

@ -0,0 +1,84 @@
package org.torproject.android.ui.hiddenservices.dialogs;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import org.torproject.android.R;
import org.torproject.android.ui.hiddenservices.providers.CookieContentProvider;
public class AddCookieDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final View dialog_view = getActivity().getLayoutInflater().inflate(R.layout.layout_add_client_cookie_dialog, null);
final AlertDialog addCookieDialog = new AlertDialog.Builder(getActivity())
.setView(dialog_view)
.setTitle(R.string.client_cookies)
.create();
Button save = (Button) dialog_view.findViewById(R.id.cookie_dialog_save);
save.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String onion = ((EditText) dialog_view.findViewById(R.id.cookie_onion)).getText().toString();
String cookie = ((EditText) dialog_view.findViewById(R.id.cookie_value)).getText().toString();
if (checkInput(onion, cookie)) {
saveData(onion, cookie);
Toast.makeText(
v.getContext(), R.string.please_restart_Orbot_to_enable_the_changes, Toast.LENGTH_LONG
).show();
addCookieDialog.dismiss();
}
}
});
Button cancel = (Button) dialog_view.findViewById(R.id.cookie_dialog_cancel);
cancel.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
addCookieDialog.cancel();
}
});
return addCookieDialog;
}
private boolean checkInput(String onion, String cookie) {
boolean is_set = ((onion != null && onion.length() > 0) && (cookie != null && cookie.length() > 0));
if (!is_set) {
Toast.makeText(getContext(), R.string.fields_can_t_be_empty, Toast.LENGTH_SHORT).show();
return false;
}
if (!onion.matches("([a-z0-9]{16})\\.onion")) {
Toast.makeText(getContext(), R.string.invalid_onion_address, Toast.LENGTH_SHORT).show();
return false;
}
return true;
}
private void saveData(String domain, String cookie) {
ContentValues fields = new ContentValues();
fields.put(CookieContentProvider.ClientCookie.DOMAIN, domain);
fields.put(CookieContentProvider.ClientCookie.AUTH_COOKIE_VALUE, cookie);
ContentResolver cr = getContext().getContentResolver();
cr.insert(CookieContentProvider.CONTENT_URI, fields);
}
}

View File

@ -0,0 +1,95 @@
package org.torproject.android.ui.hiddenservices.dialogs;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import org.torproject.android.R;
import org.torproject.android.ui.hiddenservices.backup.BackupUtils;
import org.torproject.android.ui.hiddenservices.permissions.PermissionManager;
public class CookieActionsDialog extends DialogFragment {
public static final int WRITE_EXTERNAL_STORAGE_FROM_COOKIE_ACTION_DIALOG = 4;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle arguments = getArguments();
final View dialog_view = getActivity().getLayoutInflater().inflate(R.layout.layout_cookie_actions, null);
final AlertDialog actionDialog = new AlertDialog.Builder(getActivity())
.setView(dialog_view)
.setTitle(R.string.client_cookies)
.create();
Button backup = (Button) dialog_view.findViewById(R.id.btn_cookie_backup);
backup.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Context mContext = v.getContext();
if (PermissionManager.isLollipopOrHigher()
&& !PermissionManager.hasExternalWritePermission(mContext)) {
PermissionManager.requestExternalWritePermissions(
getActivity(), WRITE_EXTERNAL_STORAGE_FROM_COOKIE_ACTION_DIALOG);
return;
}
BackupUtils backup_utils = new BackupUtils(mContext);
String backupPath = backup_utils.createCookieBackup(
arguments.getString("domain"),
arguments.getString("auth_cookie_value"),
arguments.getInt("enabled")
);
if (backupPath == null || backupPath.length() < 1) {
Toast.makeText(mContext, R.string.error, Toast.LENGTH_LONG).show();
actionDialog.dismiss();
return;
}
Toast.makeText(mContext, R.string.backup_saved_at_external_storage, Toast.LENGTH_LONG).show();
Uri selectedUri = Uri.parse(backupPath.substring(0, backupPath.lastIndexOf("/")));
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(selectedUri, "resource/folder");
if (intent.resolveActivityInfo(mContext.getPackageManager(), 0) != null) {
startActivity(intent);
} else {
Toast.makeText(mContext, R.string.filemanager_not_available, Toast.LENGTH_LONG).show();
}
actionDialog.dismiss();
}
});
Button delete = (Button) dialog_view.findViewById(R.id.btn_cookie_delete);
delete.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
CookieDeleteDialog dialog = new CookieDeleteDialog();
dialog.setArguments(arguments);
dialog.show(getFragmentManager(), "CookieDeleteDialog");
actionDialog.dismiss();
}
});
Button cancel = (Button) dialog_view.findViewById(R.id.btn_cookie_cancel);
cancel.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
actionDialog.dismiss();
}
});
return actionDialog;
}
}

View File

@ -0,0 +1,50 @@
package org.torproject.android.ui.hiddenservices.dialogs;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import org.torproject.android.R;
import org.torproject.android.ui.hiddenservices.providers.CookieContentProvider;
public class CookieDeleteDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle arguments = getArguments();
final Context context = getContext();
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
// Delete from db
context.getContentResolver().delete(
CookieContentProvider.CONTENT_URI,
CookieContentProvider.ClientCookie._ID + "=" + arguments.getInt("_id"),
null
);
break;
case DialogInterface.BUTTON_NEGATIVE:
// Do nothing
break;
}
}
};
return new AlertDialog.Builder(context)
.setMessage(R.string.confirm_cookie_deletion)
.setPositiveButton(R.string.btn_okay, dialogClickListener)
.setNegativeButton(R.string.btn_cancel, dialogClickListener)
.create();
}
}

View File

@ -0,0 +1,130 @@
package org.torproject.android.ui.hiddenservices.dialogs;
import android.app.Dialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import org.torproject.android.R;
import org.torproject.android.ui.hiddenservices.backup.BackupUtils;
import org.torproject.android.ui.hiddenservices.permissions.PermissionManager;
public class HSActionsDialog extends DialogFragment {
public static final int WRITE_EXTERNAL_STORAGE_FROM_ACTION_DIALOG = 2;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle arguments = getArguments();
final View dialog_view = getActivity().getLayoutInflater().inflate(R.layout.layout_hs_actions, null);
final AlertDialog actionDialog = new AlertDialog.Builder(getActivity())
.setView(dialog_view)
.setTitle(R.string.hidden_services)
.create();
Button backup = (Button) dialog_view.findViewById(R.id.btn_hs_backup);
backup.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Context mContext = v.getContext();
if (PermissionManager.isLollipopOrHigher()
&& !PermissionManager.hasExternalWritePermission(mContext)) {
PermissionManager.requestExternalWritePermissions(
getActivity(), WRITE_EXTERNAL_STORAGE_FROM_ACTION_DIALOG);
return;
}
BackupUtils hsutils = new BackupUtils(mContext);
String backupPath = hsutils.createZipBackup(Integer.parseInt(arguments.getString("port")));
if (backupPath == null || backupPath.length() < 1) {
Toast.makeText(mContext, R.string.error, Toast.LENGTH_LONG).show();
actionDialog.dismiss();
return;
}
Toast.makeText(mContext, R.string.backup_saved_at_external_storage, Toast.LENGTH_LONG).show();
Uri selectedUri = Uri.parse(backupPath.substring(0, backupPath.lastIndexOf("/")));
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(selectedUri, "resource/folder");
if (intent.resolveActivityInfo(mContext.getPackageManager(), 0) != null) {
startActivity(intent);
} else {
Toast.makeText(mContext, R.string.filemanager_not_available, Toast.LENGTH_LONG).show();
}
actionDialog.dismiss();
}
});
Button copy = (Button) dialog_view.findViewById(R.id.btn_hs_clipboard);
copy.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Context mContext = v.getContext();
ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("onion", arguments.getString("onion"));
clipboard.setPrimaryClip(clip);
Toast.makeText(mContext, R.string.done, Toast.LENGTH_LONG).show();
actionDialog.dismiss();
}
});
Button showAuth = (Button) dialog_view.findViewById(R.id.bt_hs_show_auth);
showAuth.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String auth_cookie_value = arguments.getString("auth_cookie_value");
if (arguments.getInt("auth_cookie") == 1) {
if (auth_cookie_value == null || auth_cookie_value.length() < 1) {
Toast.makeText(
v.getContext(), R.string.please_restart_Orbot_to_enable_the_changes, Toast.LENGTH_LONG
).show();
} else {
HSCookieDialog dialog = new HSCookieDialog();
dialog.setArguments(arguments);
dialog.show(getFragmentManager(), "HSCookieDialog");
}
} else {
Toast.makeText(
v.getContext(), R.string.auth_cookie_was_not_configured, Toast.LENGTH_LONG
).show();
}
actionDialog.dismiss();
}
});
Button delete = (Button) dialog_view.findViewById(R.id.btn_hs_delete);
delete.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
HSDeleteDialog dialog = new HSDeleteDialog();
dialog.setArguments(arguments);
dialog.show(getFragmentManager(), "HSDeleteDialog");
actionDialog.dismiss();
}
});
Button cancel = (Button) dialog_view.findViewById(R.id.btn_hs_cancel);
cancel.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
actionDialog.dismiss();
}
});
return actionDialog;
}
}

View File

@ -0,0 +1,81 @@
package org.torproject.android.ui.hiddenservices.dialogs;
import android.app.Dialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.google.zxing.integration.android.IntentIntegrator;
import org.json.JSONException;
import org.json.JSONObject;
import org.torproject.android.R;
import org.torproject.android.ui.hiddenservices.providers.CookieContentProvider;
public class HSCookieDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final View dialog_view = getActivity().getLayoutInflater().inflate(R.layout.layout_hs_cookie, null);
final Bundle arguments = getArguments();
final String auth_cookie_value = arguments.getString("auth_cookie_value");
final AlertDialog cookieDialog = new AlertDialog.Builder(getActivity())
.setView(dialog_view)
.create();
TextView cookie = (TextView) dialog_view.findViewById(R.id.hs_cookie);
cookie.setText(auth_cookie_value);
Button clipboard = (Button) dialog_view.findViewById(R.id.hs_cookie_to_clipboard);
clipboard.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Context mContext = v.getContext();
ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("cookie", auth_cookie_value);
clipboard.setPrimaryClip(clip);
Toast.makeText(mContext, R.string.done, Toast.LENGTH_LONG).show();
cookieDialog.dismiss();
}
});
Button shareQR = (Button) dialog_view.findViewById(R.id.hs_cookie_to_qr);
shareQR.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
try {
JSONObject backup = new JSONObject();
backup.put(CookieContentProvider.ClientCookie.DOMAIN, arguments.getString("onion"));
backup.put(CookieContentProvider.ClientCookie.AUTH_COOKIE_VALUE, arguments.getString("auth_cookie_value"));
IntentIntegrator integrator = new IntentIntegrator(getActivity());
integrator.shareText(backup.toString());
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
cookieDialog.dismiss();
}
});
Button cancel = (Button) dialog_view.findViewById(R.id.hs_cookie_cancel);
cancel.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
cookieDialog.dismiss();
}
});
return cookieDialog;
}
}

View File

@ -0,0 +1,102 @@
package org.torproject.android.ui.hiddenservices.dialogs;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;
import org.torproject.android.R;
import org.torproject.android.ui.hiddenservices.providers.HSContentProvider;
public class HSDataDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Get the layout
final View dialog_view = getActivity().getLayoutInflater().inflate(R.layout.layout_hs_data_dialog, null);
// Use the Builder class for convenient dialog construction
final AlertDialog serviceDataDialog = new AlertDialog.Builder(getActivity())
.setView(dialog_view)
.setTitle(R.string.hidden_services)
.create();
// Buttons action
Button save = (Button) dialog_view.findViewById(R.id.HSDialogSave);
save.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String serverName = ((EditText) dialog_view.findViewById(R.id.hsName)).getText().toString();
Integer localPort = Integer.parseInt(
((EditText) dialog_view.findViewById(R.id.hsLocalPort)).getText().toString()
);
Integer onionPort = Integer.parseInt(
((EditText) dialog_view.findViewById(R.id.hsOnionPort)).getText().toString()
);
Boolean authCookie = ((CheckBox) dialog_view.findViewById(R.id.hsAuth)).isChecked();
if (checkInput(serverName, localPort, onionPort)) {
saveData(serverName, localPort, onionPort, authCookie);
Toast.makeText(
v.getContext(), R.string.please_restart_Orbot_to_enable_the_changes, Toast.LENGTH_LONG
).show();
serviceDataDialog.dismiss();
}
}
});
Button cancel = (Button) dialog_view.findViewById(R.id.HSDialogCancel);
cancel.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
serviceDataDialog.cancel();
}
});
return serviceDataDialog;
}
private boolean checkInput(String serverName, Integer local, Integer remote) {
boolean is_ok = true;
Integer error_msg = 0;
if ((local < 1 || local > 65535) || (remote < 1 || remote > 65535)) {
error_msg = R.string.invalid_port;
is_ok = false;
}
if (serverName == null || serverName.length() < 1) {
error_msg = R.string.name_can_t_be_empty;
is_ok = false;
}
if (!is_ok) {
Toast.makeText(getContext(), error_msg, Toast.LENGTH_SHORT).show();
}
return is_ok;
}
private void saveData(String name, Integer local, Integer remote, Boolean authCookie) {
ContentValues fields = new ContentValues();
fields.put(HSContentProvider.HiddenService.NAME, name);
fields.put(HSContentProvider.HiddenService.PORT, local);
fields.put(HSContentProvider.HiddenService.ONION_PORT, remote);
fields.put(HSContentProvider.HiddenService.AUTH_COOKIE, authCookie);
fields.put(HSContentProvider.HiddenService.CREATED_BY_USER, 1);
ContentResolver cr = getContext().getContentResolver();
cr.insert(HSContentProvider.CONTENT_URI, fields);
}
}

View File

@ -0,0 +1,65 @@
package org.torproject.android.ui.hiddenservices.dialogs;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import org.torproject.android.R;
import org.torproject.android.service.TorServiceConstants;
import org.torproject.android.ui.hiddenservices.providers.HSContentProvider;
import java.io.File;
public class HSDeleteDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle arguments = getArguments();
final Context context = getContext();
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
// Delete from db
context.getContentResolver().delete(
HSContentProvider.CONTENT_URI,
HSContentProvider.HiddenService._ID + "=" + arguments.getInt("_id"),
null
);
// Delete from interal storage
String base = context.getFilesDir().getAbsolutePath() + "/" + TorServiceConstants.HIDDEN_SERVICES_DIR;
File dir = new File(base, "hs" + arguments.getString("port"));
if (dir.isDirectory()) {
String[] children = dir.list();
for (String aChildren : children) {
new File(dir, aChildren).delete();
}
dir.delete();
}
break;
case DialogInterface.BUTTON_NEGATIVE:
// Do nothing
break;
}
}
};
return new AlertDialog.Builder(context)
.setMessage(R.string.confirm_service_deletion)
.setPositiveButton(R.string.btn_okay, dialogClickListener)
.setNegativeButton(R.string.btn_cancel, dialogClickListener)
.create();
}
}

View File

@ -0,0 +1,84 @@
package org.torproject.android.ui.hiddenservices.dialogs;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import org.torproject.android.R;
import org.torproject.android.ui.hiddenservices.adapters.BackupAdapter;
import org.torproject.android.ui.hiddenservices.backup.BackupUtils;
import org.torproject.android.ui.hiddenservices.storage.ExternalStorage;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SelectCookieBackupDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder cookieBackupDialog = new AlertDialog.Builder(getActivity());
cookieBackupDialog.setTitle(R.string.restore_backup);
File backupDir = ExternalStorage.getOrCreateBackupDir();
File[] files = null;
try {
files = backupDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".json");
}
});
} catch (NullPointerException e) {
// Silent block
}
if (files == null || files.length < 1) {
cookieBackupDialog.setMessage(R.string.create_a_backup_first);
cookieBackupDialog.setNegativeButton(R.string.btn_cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
return cookieBackupDialog.create();
}
final View dialog_view = getActivity().getLayoutInflater().inflate(R.layout.layout_hs_backups_list, null);
cookieBackupDialog.setView(dialog_view);
cookieBackupDialog.setPositiveButton(R.string.btn_okay, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
ListView backups = (ListView) dialog_view.findViewById(R.id.listview_hs_backups);
List<File> json_backups = new ArrayList<>();
Collections.addAll(json_backups, files);
backups.setAdapter(new BackupAdapter(getContext(), R.layout.layout_hs_backups_list_item, json_backups));
backups.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
BackupUtils backupUtils = new BackupUtils(view.getContext().getApplicationContext());
File p = (File) parent.getItemAtPosition(position);
backupUtils.restoreCookieBackup(p);
}
});
return cookieBackupDialog.create();
}
}

View File

@ -0,0 +1,84 @@
package org.torproject.android.ui.hiddenservices.dialogs;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import org.torproject.android.R;
import org.torproject.android.ui.hiddenservices.adapters.BackupAdapter;
import org.torproject.android.ui.hiddenservices.backup.BackupUtils;
import org.torproject.android.ui.hiddenservices.storage.ExternalStorage;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SelectHSBackupDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder backupsDialog = new AlertDialog.Builder(getActivity());
backupsDialog.setTitle(R.string.restore_backup);
File backupDir = ExternalStorage.getOrCreateBackupDir();
File[] files = null;
try {
files = backupDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".zip");
}
});
} catch (NullPointerException e) {
// Silent block
}
if (files == null || files.length < 1) {
backupsDialog.setMessage(R.string.create_a_backup_first);
backupsDialog.setNegativeButton(R.string.btn_cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
return backupsDialog.create();
}
final View dialog_view = getActivity().getLayoutInflater().inflate(R.layout.layout_hs_backups_list, null);
backupsDialog.setView(dialog_view);
backupsDialog.setPositiveButton(R.string.btn_okay, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
ListView backups = (ListView) dialog_view.findViewById(R.id.listview_hs_backups);
List<File> zips = new ArrayList<>();
Collections.addAll(zips, files);
backups.setAdapter(new BackupAdapter(getContext(), R.layout.layout_hs_backups_list_item, zips));
backups.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
BackupUtils backupUtils = new BackupUtils(view.getContext().getApplicationContext());
File p = (File) parent.getItemAtPosition(position);
backupUtils.restoreZipBackup(p);
}
});
return backupsDialog.create();
}
}

View File

@ -0,0 +1,103 @@
package org.torproject.android.ui.hiddenservices.permissions;
import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.PowerManager;
import android.provider.Settings;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import org.torproject.android.R;
public class PermissionManager {
public static int VERY_LONG_LENGTH = 6000;
public static boolean isLollipopOrHigher() {
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP);
}
@SuppressLint("NewApi")
public static boolean hasExternalWritePermission(Context context) {
return (context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
}
public static void requestExternalWritePermissions(FragmentActivity activity, int action) {
final int mAction = action;
final FragmentActivity mActivity = activity;
if (ActivityCompat.shouldShowRequestPermissionRationale
(mActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
Snackbar.make(mActivity.findViewById(android.R.id.content),
R.string.please_grant_permissions_for_external_storage,
Snackbar.LENGTH_INDEFINITE).setAction(R.string.activate,
new View.OnClickListener() {
@Override
public void onClick(View v) {
ActivityCompat.requestPermissions(mActivity,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
mAction);
}
}).show();
} else {
ActivityCompat.requestPermissions(mActivity,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
mAction);
}
}
@TargetApi(Build.VERSION_CODES.M)
public static void requestBatteryPermmssions(FragmentActivity activity, Context context) {
final Context mContext = context;
final String packageName = mContext.getPackageName();
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
if (pm.isIgnoringBatteryOptimizations(packageName))
return;
Snackbar.make(activity.findViewById(android.R.id.content),
R.string.consider_disable_battery_optimizations,
VERY_LONG_LENGTH).setAction(R.string.disable,
new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
mContext.startActivity(intent);
}
}).show();
}
@TargetApi(Build.VERSION_CODES.M)
public static void requestDropBatteryPermmssions(FragmentActivity activity, Context context) {
final Context mContext = context;
final String packageName = context.getPackageName();
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (!pm.isIgnoringBatteryOptimizations(packageName))
return;
Snackbar.make(activity.findViewById(android.R.id.content),
R.string.consider_enable_battery_optimizations,
VERY_LONG_LENGTH).setAction(R.string.enable,
new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
mContext.startActivity(intent);
}
}).show();
}
}

View File

@ -0,0 +1,134 @@
package org.torproject.android.ui.hiddenservices.providers;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.provider.BaseColumns;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.torproject.android.ui.hiddenservices.database.HSDatabase;
public class CookieContentProvider extends ContentProvider {
public static final String[] PROJECTION = new String[]{
ClientCookie._ID,
ClientCookie.DOMAIN,
ClientCookie.AUTH_COOKIE_VALUE,
ClientCookie.ENABLED
};
private static final String AUTH = "org.torproject.android.ui.hiddenservices.providers.cookie";
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTH + "/cookie");
//UriMatcher
private static final int COOKIES = 1;
private static final int COOKIE_ID = 2;
private static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTH, "hs", COOKIES);
uriMatcher.addURI(AUTH, "hs/#", COOKIE_ID);
}
private HSDatabase mServervices;
private Context mContext;
@Override
public boolean onCreate() {
mContext = getContext();
mServervices = new HSDatabase(mContext);
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String where = selection;
if (uriMatcher.match(uri) == COOKIE_ID) {
where = "_id=" + uri.getLastPathSegment();
}
SQLiteDatabase db = mServervices.getReadableDatabase();
return db.query(HSDatabase.HS_CLIENT_COOKIE_TABLE_NAME, projection, where,
selectionArgs, null, null, sortOrder);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
int match = uriMatcher.match(uri);
switch (match) {
case COOKIES:
return "vnd.android.cursor.dir/vnd.torproject.cookies";
case COOKIE_ID:
return "vnd.android.cursor.item/vnd.torproject.cookie";
default:
return null;
}
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
long regId;
SQLiteDatabase db = mServervices.getWritableDatabase();
regId = db.insert(HSDatabase.HS_CLIENT_COOKIE_TABLE_NAME, null, values);
mContext.getContentResolver().notifyChange(CONTENT_URI, null);
return ContentUris.withAppendedId(CONTENT_URI, regId);
}
@Override
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
String where = selection;
if (uriMatcher.match(uri) == COOKIE_ID) {
where = "_id=" + uri.getLastPathSegment();
}
SQLiteDatabase db = mServervices.getWritableDatabase();
Integer rows = db.delete(HSDatabase.HS_CLIENT_COOKIE_TABLE_NAME, where, selectionArgs);
mContext.getContentResolver().notifyChange(CONTENT_URI, null);
return rows;
}
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SQLiteDatabase db = mServervices.getWritableDatabase();
String where = selection;
if (uriMatcher.match(uri) == COOKIE_ID) {
where = "_id=" + uri.getLastPathSegment();
}
Integer rows = db.update(HSDatabase.HS_CLIENT_COOKIE_TABLE_NAME, values, where, null);
mContext.getContentResolver().notifyChange(CONTENT_URI, null);
return rows;
}
public static final class ClientCookie implements BaseColumns {
public static final String DOMAIN = "domain";
public static final String AUTH_COOKIE_VALUE = "auth_cookie_value";
public static final String ENABLED = "enabled";
private ClientCookie() {
}
}
}

View File

@ -0,0 +1,144 @@
package org.torproject.android.ui.hiddenservices.providers;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.provider.BaseColumns;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.torproject.android.ui.hiddenservices.database.HSDatabase;
public class HSContentProvider extends ContentProvider {
public static final String[] PROJECTION = new String[]{
HiddenService._ID,
HiddenService.NAME,
HiddenService.PORT,
HiddenService.DOMAIN,
HiddenService.ONION_PORT,
HiddenService.AUTH_COOKIE,
HiddenService.AUTH_COOKIE_VALUE,
HiddenService.CREATED_BY_USER,
HiddenService.ENABLED
};
private static final String AUTH = "org.torproject.android.ui.hiddenservices.providers";
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTH + "/hs");
//UriMatcher
private static final int ONIONS = 1;
private static final int ONION_ID = 2;
private static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTH, "hs", ONIONS);
uriMatcher.addURI(AUTH, "hs/#", ONION_ID);
}
private HSDatabase mServervices;
private Context mContext;
@Override
public boolean onCreate() {
mContext = getContext();
mServervices = new HSDatabase(mContext);
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String where = selection;
if (uriMatcher.match(uri) == ONION_ID) {
where = "_id=" + uri.getLastPathSegment();
}
SQLiteDatabase db = mServervices.getReadableDatabase();
return db.query(HSDatabase.HS_DATA_TABLE_NAME, projection, where,
selectionArgs, null, null, sortOrder);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
int match = uriMatcher.match(uri);
switch (match) {
case ONIONS:
return "vnd.android.cursor.dir/vnd.torproject.onions";
case ONION_ID:
return "vnd.android.cursor.item/vnd.torproject.onion";
default:
return null;
}
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
long regId;
SQLiteDatabase db = mServervices.getWritableDatabase();
regId = db.insert(HSDatabase.HS_DATA_TABLE_NAME, null, values);
mContext.getContentResolver().notifyChange(CONTENT_URI, null);
return ContentUris.withAppendedId(CONTENT_URI, regId);
}
@Override
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
String where = selection;
if (uriMatcher.match(uri) == ONION_ID) {
where = "_id=" + uri.getLastPathSegment();
}
SQLiteDatabase db = mServervices.getWritableDatabase();
Integer rows = db.delete(HSDatabase.HS_DATA_TABLE_NAME, where, selectionArgs);
mContext.getContentResolver().notifyChange(CONTENT_URI, null);
return rows;
}
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SQLiteDatabase db = mServervices.getWritableDatabase();
String where = selection;
if (uriMatcher.match(uri) == ONION_ID) {
where = "_id=" + uri.getLastPathSegment();
}
Integer rows = db.update(HSDatabase.HS_DATA_TABLE_NAME, values, where, null);
mContext.getContentResolver().notifyChange(CONTENT_URI, null);
return rows;
}
public static final class HiddenService implements BaseColumns {
public static final String NAME = "name";
public static final String PORT = "port";
public static final String ONION_PORT = "onion_port";
public static final String DOMAIN = "domain";
public static final String AUTH_COOKIE = "auth_cookie";
public static final String AUTH_COOKIE_VALUE = "auth_cookie_value";
public static final String CREATED_BY_USER = "created_by_user";
public static final String ENABLED = "enabled";
private HiddenService() {
}
}
}

View File

@ -0,0 +1,34 @@
package org.torproject.android.ui.hiddenservices.storage;
import android.os.Environment;
import java.io.File;
public class ExternalStorage {
private static final String ORBOT_BACKUPS_DIR = "Orbot";
public static File getOrCreateBackupDir() {
if (!isExternalStorageWritable())
return null;
File dir = new File(Environment.getExternalStorageDirectory(), ORBOT_BACKUPS_DIR);
if (!dir.isDirectory() && !dir.mkdirs())
return null;
return dir;
}
/* Checks if external storage is available for read and write */
public static boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
/* Checks if external storage is available to at least read */
public static boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
}
}

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="org.torproject.android.ui.hiddenservices.ClientCookiesActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/DefaultTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/DefaultTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/layout_content_client_cookies" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@android:drawable/stat_notify_more"
app:backgroundTint="@android:color/darker_gray" />
</android.support.design.widget.CoordinatorLayout>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:paddingTop="5dp"
android:paddingBottom="5dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/label_onion_name"
android:text="@string/onion"
android:textAppearance="@style/TextAppearance.AppCompat.Widget.PopupMenu.Small"
android:paddingLeft="5dp" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:ems="10"
android:id="@+id/cookie_onion" />
<TextView
android:text="@string/auth_cookie"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/label_cookie_value"
android:textAppearance="@style/TextAppearance.AppCompat.Widget.PopupMenu.Small"
android:paddingLeft="5dp" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:id="@+id/cookie_value"
android:inputType="text" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="@string/btn_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/cookie_dialog_cancel"
android:layout_weight="1"
style="@style/Widget.AppCompat.Button.Borderless.Colored" />
<Button
android:text="@string/save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/cookie_dialog_save"
android:layout_weight="1"
style="@style/Widget.AppCompat.Button.Borderless.Colored" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="15dp">
<TextView
android:id="@+id/cookie_onion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:layout_weight="1" />
<Switch
android:id="@+id/cookie_switch"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:focusable="false"
android:focusableInTouchMode="false"
android:switchPadding="30dp" />
</LinearLayout>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/content_client_cookies"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="org.torproject.android.ui.hiddenservices.ClientCookiesActivity"
tools:showIn="@layout/layout_activity_client_cookies">
<ListView
android:id="@+id/clien_cookies_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="@string/backup_cookie"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_cookie_backup" />
<Button
android:text="@string/delete_cookie"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_cookie_delete" />
<Button
android:text="@string/btn_cancel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_cookie_cancel" />
</LinearLayout>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="@string/copy_address_to_clipboard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_hs_clipboard" />
<Button
android:text="@string/show_auth_cookie"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/bt_hs_show_auth" />
<Button
android:text="@string/backup_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_hs_backup" />
<Button
android:text="@string/delete_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_hs_delete" />
<Button
android:text="@string/btn_cancel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_hs_cancel" />
</LinearLayout>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/listview_hs_backups"
android:layout_marginTop="15dp" />
</LinearLayout>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/backup_list_item"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="15dp"
tools:paddingLeft="15dp"
android:paddingRight="15dp"
tools:paddingRight="15dp">
<TextView
android:id="@+id/backup_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="30sp"
android:layout_marginBottom="10dp" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/hs_cookie"
android:paddingBottom="5dp"
android:paddingLeft="5dp"
android:paddingTop="5dp" />
<Button
android:text="@string/copy_cookie_to_clipboard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/hs_cookie_to_clipboard" />
<Button
android:text="@string/share_as_qr"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/hs_cookie_to_qr" />
<Button
android:text="@string/btn_cancel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/hs_cookie_cancel" />
</LinearLayout>

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:paddingTop="5dp"
android:paddingBottom="5dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/HSNameLabel"
android:text="@string/name"
android:textAppearance="@style/TextAppearance.AppCompat.Widget.PopupMenu.Small"
android:paddingLeft="5dp" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:ems="10"
android:id="@+id/hsName" />
<TextView
android:text="@string/local_port"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/HSLocalPortLabel"
android:textAppearance="@style/TextAppearance.AppCompat.Widget.PopupMenu.Small"
android:paddingLeft="5dp" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:id="@+id/hsLocalPort"
android:inputType="number" />
<TextView
android:text="@string/onion_port"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/HSOnionPortLabel"
android:textAppearance="@style/TextAppearance.AppCompat.Widget.PopupMenu.Small"
android:paddingLeft="5dp" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:id="@+id/hsOnionPort"
android:inputType="number" />
<CheckBox
android:text="@string/auth_cookie"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/hsAuth"
android:paddingTop="5dp"
android:paddingBottom="10dp"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="@string/btn_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/HSDialogCancel"
android:layout_weight="1"
style="@style/Widget.AppCompat.Button.Borderless.Colored" />
<Button
android:text="@string/save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/HSDialogSave"
android:layout_weight="1"
style="@style/Widget.AppCompat.Button.Borderless.Colored" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="15dp"
android:paddingRight="15dp"
tools:paddingLeft="15dp"
tools:paddingRight="15dp">
<TextView
android:id="@+id/hs_port"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingRight="10dp"
android:paddingTop="10dp"
android:textSize="35sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/hs_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:paddingLeft="10dp"
android:textSize="24sp" />
<TextView
android:id="@+id/hs_onion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:paddingLeft="10dp"
android:textSize="18sp" />
</LinearLayout>
<Switch
android:id="@+id/hs_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:focusableInTouchMode="false"
android:switchPadding="30dp" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="org.torproject.android.ui.hiddenservices.HiddenServicesActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/layout_hs_list_view_main" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@android:drawable/stat_notify_more"
app:backgroundTint="@android:color/darker_gray" />
</android.support.design.widget.CoordinatorLayout>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/content_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="org.torproject.android.ui.hiddenservices.HiddenServicesActivity"
tools:showIn="@layout/layout_hs_list_view">
<ListView
android:id="@+id/onion_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/cookie_restore_backup"
android:title="@string/restore_backup" />
<item
android:id="@+id/cookie_from_qr"
android:title="@string/cookie_from_QR" />
</menu>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/hs_type"
android:background="#ff00"
android:title="@string/service_type"
app:actionViewClass="android.widget.Spinner"
app:showAsAction="always" />
<item
android:id="@+id/menu_restore_backup"
android:title="@string/restore_backup" />
</menu>

View File

@ -43,7 +43,23 @@
/> />
</menu> </menu>
</item> </item>
<item
android:title="@string/menu_hidden_services"
yourapp:showAsAction="never"
>
<menu>
<item android:id="@+id/menu_hidden_services"
android:title="@string/hosted_services"
yourapp:showAsAction="never"
/>
<item android:id="@+id/menu_client_cookies"
android:title="@string/client_cookies"
yourapp:showAsAction="never"
/>
</menu>
</item>
<!-- <!--
<item android:id="@+id/menu_promo_apps" <item android:id="@+id/menu_promo_apps"

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="bridge_options">
<item>Obfs4 (Preferido)</item>
<item>Túnel a través de Azure</item>
<item>Túnel a través de Amazon</item>
<item>Obtener nuevos puentes</item>
<item></item>
</string-array>
<string-array name="array_hs_types">
<item>Usuarios</item>
<item>Aplicaciones</item>
</string-array>
</resources>

View File

@ -291,4 +291,36 @@ direcciones (o rangos). No prevalecen sobre las configuraciones de exclusión de
<string name="standard_browser">Navegador estándar</string> <string name="standard_browser">Navegador estándar</string>
<string name="note_only_standard_tor_bridges_work_on_intel_x86_atom_devices">NOTA: Sólo los repetidores puente (bridges) estándar de Tor funcionan en dispositivos Intel X86/ATOM</string> <string name="note_only_standard_tor_bridges_work_on_intel_x86_atom_devices">NOTA: Sólo los repetidores puente (bridges) estándar de Tor funcionan en dispositivos Intel X86/ATOM</string>
<string name="vpn_default_world">Mundo</string> <string name="vpn_default_world">Mundo</string>
<string name="hidden_services">Servicios Ocultos</string>
<string name="title_activity_hidden_services">Servicios Ocultos</string>
<string name="menu_hidden_services">Servicios Ocultos</string>
<string name="save">Guardar</string>
<string name="ports">Puertos</string>
<string name="local_port">Puerto Local</string>
<string name="onion_port">Puerto Onion</string>
<string name="name">Nombre</string>
<string name="done">Hecho!</string>
<string name="invalid_port">Puerto no vàlido</string>
<string name="copy_address_to_clipboard">Copiar dirección a portapapeles</string>
<string name="show_auth_cookie">Mostra cookie de autenticación</string>
<string name="backup_service">Crear backup</string>
<string name="delete_service">Borrar servicio</string>
<string name="backup_saved_at_external_storage">Backup guardado en almacenamiento externo</string>
<string name="backup_restored">Backup restaurado</string>
<string name="filemanager_not_available">Gestor de archivos no disponible</string>
<string name="please_grant_permissions_for_external_storage">Permitir permisos para almacenamiento externo</string>
<string name="permission_granted">Permisos concedidos</string>
<string name="permission_denied">Permisos denegados</string>
<string name="restore_backup">Restaurar Backup</string>
<string name="create_a_backup_first">Crear primero una copia de seguridad</string>
<string name="name_can_t_be_empty">Nombre no puede estar vacio</string>
<string name="start_tor_again_for_finish_the_process">Inicie Tor de nuevo para finalizar el proceso</string>
<string name="confirm_service_deletion">Confirmar eliminación de servicio</string>
<string name="click_again_for_backup">Haga clic de nuevo para realizar copias de seguridad</string>
<string name="service_type">Tipo de servicio</string>
<string name="auth_cookie">Cookie de autenticación</string>
<string name="copy_cookie_to_clipboard">Copiar cookie al portapapeles</string>
<string name="auth_cookie_was_not_configured">La cookie de autenticación no estaba configurada</string>
<string name="please_restart_Orbot_to_enable_the_changes">Reinicie Orbot para habilitar los cambios</string>
</resources> </resources>

View File

@ -0,0 +1 @@
<resources></resources>

View File

@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View File

@ -9,5 +9,8 @@
<item>Get New Bridges</item> <item>Get New Bridges</item>
<item></item> <item></item>
</string-array> </string-array>
<string-array name="array_hs_types">
<item>User services</item>
<item>App services</item>
</string-array>
</resources> </resources>

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<!--
/* //device/apps/common/assets/res/any/dimens.xml /* //device/apps/common/assets/res/any/dimens.xml
** **
** Copyright 2006, The Android Open Source Project ** Copyright 2006, The Android Open Source Project
@ -118,26 +117,27 @@
<!-- The platform's desired minimum size for a dialog's width when it <!-- The platform's desired minimum size for a dialog's width when it
is along the major axis (that is the screen is landscape). This may is along the major axis (that is the screen is landscape). This may
be either a fraction or a dimension. --> be either a fraction or a dimension. -->
<item type="dimen" name="dialog_min_width_major">65%</item> <item name="dialog_min_width_major" type="dimen">65%</item>
<!-- The platform's desired fixed width for a dialog along the major axis <!-- The platform's desired fixed width for a dialog along the major axis
(the screen is in landscape). This may be either a fraction or a dimension.--> (the screen is in landscape). This may be either a fraction or a dimension.-->
<item type="dimen" name="dialog_fixed_width_major">320dp</item> <item name="dialog_fixed_width_major" type="dimen">320dp</item>
<!-- The platform's desired fixed width for a dialog along the minor axis <!-- The platform's desired fixed width for a dialog along the minor axis
(the screen is in portrait). This may be either a fraction or a dimension.--> (the screen is in portrait). This may be either a fraction or a dimension.-->
<item type="dimen" name="dialog_fixed_width_minor">320dp</item> <item name="dialog_fixed_width_minor" type="dimen">320dp</item>
<!-- The platform's desired fixed height for a dialog along the major axis <!-- The platform's desired fixed height for a dialog along the major axis
(the screen is in portrait). This may be either a fraction or a dimension.--> (the screen is in portrait). This may be either a fraction or a dimension.-->
<item type="dimen" name="dialog_fixed_height_major">80%</item> <item name="dialog_fixed_height_major" type="dimen">80%</item>
<!-- The platform's desired fixed height for a dialog along the minor axis <!-- The platform's desired fixed height for a dialog along the minor axis
(the screen is in landscape). This may be either a fraction or a dimension.--> (the screen is in landscape). This may be either a fraction or a dimension.-->
<item type="dimen" name="dialog_fixed_height_minor">100%</item> <item name="dialog_fixed_height_minor" type="dimen">100%</item>
<!-- Preference activity, vertical padding for the header list --> <!-- Preference activity, vertical padding for the header list -->
<dimen name="preference_screen_header_vertical_padding">0dp</dimen> <dimen name="preference_screen_header_vertical_padding">0dp</dimen>
<dimen name="preference_screen_header_padding_side">16dip</dimen> <dimen name="preference_screen_header_padding_side">16dip</dimen>
<integer name="preference_screen_header_scrollbarStyle">0x02000000</integer> <!-- outsideOverlay --> <integer name="preference_screen_header_scrollbarStyle">0x02000000
</integer> <!-- outsideOverlay -->
<integer name="preference_fragment_scrollbarStyle">0x02000000</integer> <!-- outsideOverlay --> <integer name="preference_fragment_scrollbarStyle">0x02000000</integer> <!-- outsideOverlay -->
@ -148,7 +148,7 @@
<!-- The platform's desired minimum size for a dialog's width when it <!-- The platform's desired minimum size for a dialog's width when it
is along the minor axis (that is the screen is portrait). This may is along the minor axis (that is the screen is portrait). This may
be either a fraction or a dimension. --> be either a fraction or a dimension. -->
<item type="dimen" name="dialog_min_width_minor">95%</item> <item name="dialog_min_width_minor" type="dimen">95%</item>
<!-- The width of the big icons in notifications. --> <!-- The width of the big icons in notifications. -->
<dimen name="notification_large_icon_width">64dp</dimen> <dimen name="notification_large_icon_width">64dp</dimen>
@ -358,4 +358,8 @@
<!-- width of ImmersiveModeConfirmation (-1 for match_parent) --> <!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
<dimen name="immersive_mode_cling_width">-1px</dimen> <dimen name="immersive_mode_cling_width">-1px</dimen>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="fab_margin">16dp</dimen>
</resources> </resources>

View File

@ -128,21 +128,21 @@
<string name="pref_entrance_node_dialog">Enter Entrance Nodes</string> <string name="pref_entrance_node_dialog">Enter Entrance Nodes</string>
<string name="pref_allow_background_starts_title">Allow Background Starts</string> <string name="pref_allow_background_starts_title">Allow Background Starts</string>
<string name="pref_allow_background_starts_summary">Let any app tell Orbot to start Tor and related services</string> <string name="pref_allow_background_starts_summary">Let any app tell Orbot to start Tor and related services</string>
<string name="button_proxy_all">Proxy All</string> <string name="button_proxy_all">Proxy All</string>
<string name="button_proxy_none">Proxy None</string> <string name="button_proxy_none">Proxy None</string>
<string name="button_invert_selection">Invert Selection</string> <string name="button_invert_selection">Invert Selection</string>
<string name="pref_proxy_title">Outbound Network Proxy (Optional)</string> <string name="pref_proxy_title">Outbound Network Proxy (Optional)</string>
<string name="pref_proxy_type_title">Outbound Proxy Type</string> <string name="pref_proxy_type_title">Outbound Proxy Type</string>
<string name="pref_proxy_type_summary">Protocol to use for proxy server: HTTP, HTTPS, Socks4, Socks5</string> <string name="pref_proxy_type_summary">Protocol to use for proxy server: HTTP, HTTPS, Socks4, Socks5</string>
<string name="pref_proxy_type_dialog">Enter Proxy Type</string> <string name="pref_proxy_type_dialog">Enter Proxy Type</string>
<string name="pref_proxy_host_title">Outbound Proxy Host</string> <string name="pref_proxy_host_title">Outbound Proxy Host</string>
<string name="pref_proxy_host_summary">Proxy Server hostname</string> <string name="pref_proxy_host_summary">Proxy Server hostname</string>
<string name="pref_proxy_host_dialog">Enter Proxy Host</string> <string name="pref_proxy_host_dialog">Enter Proxy Host</string>
<string name="pref_proxy_port_title">Outbound Proxy Port</string> <string name="pref_proxy_port_title">Outbound Proxy Port</string>
<string name="pref_proxy_port_summary">Proxy Server port</string> <string name="pref_proxy_port_summary">Proxy Server port</string>
<string name="pref_proxy_port_dialog">Enter Proxy port</string> <string name="pref_proxy_port_dialog">Enter Proxy port</string>
@ -150,13 +150,12 @@
<string name="pref_proxy_username_title">Outbound Proxy Username</string> <string name="pref_proxy_username_title">Outbound Proxy Username</string>
<string name="pref_proxy_username_summary">Proxy Username (Optional)</string> <string name="pref_proxy_username_summary">Proxy Username (Optional)</string>
<string name="pref_proxy_username_dialog">Enter Proxy Username</string> <string name="pref_proxy_username_dialog">Enter Proxy Username</string>
<string name="pref_proxy_password_title">Outbound Proxy Password</string> <string name="pref_proxy_password_title">Outbound Proxy Password</string>
<string name="pref_proxy_password_summary">Proxy Password (Optional)</string> <string name="pref_proxy_password_summary">Proxy Password (Optional)</string>
<string name="pref_proxy_password_dialog">Enter Proxy Password</string> <string name="pref_proxy_password_dialog">Enter Proxy Password</string>
<string name="status">Status</string> <string name="status">Status</string>
<string name="setting_up_full_transparent_proxying_">Setting up full transparent proxying&#8230;</string> <string name="setting_up_full_transparent_proxying_">Setting up full transparent proxying&#8230;</string>
<string name="setting_up_app_based_transparent_proxying_">Setting up app-based transparent proxying&#8230;</string> <string name="setting_up_app_based_transparent_proxying_">Setting up app-based transparent proxying&#8230;</string>
@ -227,17 +226,17 @@
<string name="unable_to_reset_tor">Reboot your device, unable to reset Tor!</string> <string name="unable_to_reset_tor">Reboot your device, unable to reset Tor!</string>
<string name="pref_use_sys_iptables_title">Use Default Iptables</string> <string name="pref_use_sys_iptables_title">Use Default Iptables</string>
<string name="pref_use_sys_iptables_summary">use the built-in iptables binary instead of the one bundled with Orbot</string> <string name="pref_use_sys_iptables_summary">use the built-in iptables binary instead of the one bundled with Orbot</string>
<string name="error_installing_binares">The Tor binaries were not able to be installed or upgraded.</string> <string name="error_installing_binares">The Tor binaries were not able to be installed or upgraded.</string>
<string name="pref_use_persistent_notifications">Always keep the icon in toolbar when Orbot is connected</string> <string name="pref_use_persistent_notifications">Always keep the icon in toolbar when Orbot is connected</string>
<string name="pref_use_persistent_notifications_title">Always-On Notifications</string> <string name="pref_use_persistent_notifications_title">Always-On Notifications</string>
<string name="pref_use_expanded_notifications">Show expanded notification with Tor exit country and IP</string> <string name="pref_use_expanded_notifications">Show expanded notification with Tor exit country and IP</string>
<string name="pref_use_expanded_notifications_title">Expanded Notifications</string> <string name="pref_use_expanded_notifications_title">Expanded Notifications</string>
<string name="notification_using_bridges">Bridges enabled!</string> <string name="notification_using_bridges">Bridges enabled!</string>
<string name="default_bridges"/> <string name="default_bridges" />
<string name="set_locale_title">Language</string> <string name="set_locale_title">Language</string>
<string name="set_locale_summary">Choose the locale and language for Orbot</string> <string name="set_locale_summary">Choose the locale and language for Orbot</string>
<string name="wizard_locale_title">Choose Language</string> <string name="wizard_locale_title">Choose Language</string>
@ -251,8 +250,8 @@
<string name="pref_disable_network_title">No Network Auto-Sleep</string> <string name="pref_disable_network_title">No Network Auto-Sleep</string>
<string name="pref_disable_network_summary">Put Tor to sleep when there is no internet available</string> <string name="pref_disable_network_summary">Put Tor to sleep when there is no internet available</string>
<string name="newnym">You\'ve switched to a new Tor identity!</string> <string name="newnym">You\'ve switched to a new Tor identity!</string>
<string name="menu_verify_browser">Browser</string> <string name="menu_verify_browser">Browser</string>
<string name="menu_use_chatsecure">Use ChatSecure</string> <string name="menu_use_chatsecure">Use ChatSecure</string>
<string name="permission_manage_tor_label">Manage Tor</string> <string name="permission_manage_tor_label">Manage Tor</string>
@ -262,78 +261,123 @@
<string name="no_network_connectivity_putting_tor_to_sleep_">No network connectivity. Putting Tor to sleep…</string> <string name="no_network_connectivity_putting_tor_to_sleep_">No network connectivity. Putting Tor to sleep…</string>
<string name="network_connectivity_is_good_waking_tor_up_">Network connectivity is good. Waking Tor up…</string> <string name="network_connectivity_is_good_waking_tor_up_">Network connectivity is good. Waking Tor up…</string>
<string name="updating_settings_in_tor_service">updating settings in Tor service</string> <string name="updating_settings_in_tor_service">updating settings in Tor service</string>
<string name="pref_socks_title">Tor SOCKS</string> <string name="pref_socks_title">Tor SOCKS</string>
<string name="pref_socks_summary">Port that Tor offers its SOCKS proxy on (default: 9050 or 0 to disable)</string> <string name="pref_socks_summary">Port that Tor offers its SOCKS proxy on (default: 9050 or 0 to disable)</string>
<string name="pref_socks_dialog">SOCKS Port Config</string> <string name="pref_socks_dialog">SOCKS Port Config</string>
<string name="pref_transport_title">Tor TransProxy Port</string> <string name="pref_transport_title">Tor TransProxy Port</string>
<string name="pref_transport_summary">Port that Tor offers its Transparent Proxy on (default: 9040 or 0 to disable)</string> <string name="pref_transport_summary">Port that Tor offers its Transparent Proxy on (default: 9040 or 0 to disable)</string>
<string name="pref_transport_dialog">TransProxy Port Config</string> <string name="pref_transport_dialog">TransProxy Port Config</string>
<string name="pref_dnsport_title">Tor DNS Port</string> <string name="pref_dnsport_title">Tor DNS Port</string>
<string name="pref_dnsport_summary">Port that Tor offers its DNS on (default: 5400 or 0 to disable)</string> <string name="pref_dnsport_summary">Port that Tor offers its DNS on (default: 5400 or 0 to disable)</string>
<string name="pref_dnsport_dialog">DNS Port Config</string> <string name="pref_dnsport_dialog">DNS Port Config</string>
<string name="pref_torrc_title">Torrc Custom Config</string> <string name="pref_torrc_title">Torrc Custom Config</string>
<string name="pref_torrc_summary">EXPERTS ONLY: enter direct torrc config lines</string> <string name="pref_torrc_summary">EXPERTS ONLY: enter direct torrc config lines</string>
<string name="pref_torrc_dialog">Custom Torrc</string> <string name="pref_torrc_dialog">Custom Torrc</string>
<string name="wizard_tips_martus">Mobile Martus - Benetech Human Rights Documentation App</string> <string name="wizard_tips_martus">Mobile Martus - Benetech Human Rights Documentation App</string>
<string name="your_tor_public_ips_">Your Tor Public IPs:</string> <string name="your_tor_public_ips_">Your Tor Public IPs:</string>
<string name="please_disable_this_app_in_android_settings_apps_if_you_are_having_problems_with_orbot_">"Please disable this app in Android->Settings->Apps if you are having problems with Orbot: "</string> <string name="please_disable_this_app_in_android_settings_apps_if_you_are_having_problems_with_orbot_">"Please disable this app in Android->Settings->Apps if you are having problems with Orbot: "</string>
<string name="app_conflict">App Conflict</string> <string name="app_conflict">App Conflict</string>
<string name="pref_transproxy_refresh_title">Transproxy Auto-Refresh</string> <string name="pref_transproxy_refresh_title">Transproxy Auto-Refresh</string>
<string name="pref_transproxy_refresh_summary">Re-apply Transproxy rules when the network state changes</string> <string name="pref_transproxy_refresh_summary">Re-apply Transproxy rules when the network state changes</string>
<string name="pref_transproxy_flush_title">Transproxy FORCE REMOVE</string> <string name="pref_transproxy_flush_title">Transproxy FORCE REMOVE</string>
<string name="pref_transproxy_flush_summary">Tap here to flush all transproxy network rules NOW</string> <string name="pref_transproxy_flush_summary">Tap here to flush all transproxy network rules NOW</string>
<string name="transparent_proxy_rules_flushed_">Transparent proxy rules flushed!</string> <string name="transparent_proxy_rules_flushed_">Transparent proxy rules flushed!</string>
<string name="you_do_not_have_root_access_enabled">You do not have ROOT access enabled</string> <string name="you_do_not_have_root_access_enabled">You do not have ROOT access enabled</string>
<string name="you_may_need_to_stop_and_start_orbot_for_settings_change_to_be_enabled_">You may need to stop and start Orbot for settings change to be enabled.</string> <string name="you_may_need_to_stop_and_start_orbot_for_settings_change_to_be_enabled_">You may need to stop and start Orbot for settings change to be enabled.</string>
<string name="menu_vpn">Apps</string> <string name="menu_vpn">Apps</string>
<string name="kbps">kbps</string> <string name="kbps">kbps</string>
<string name="mbps">mbps</string> <string name="mbps">mbps</string>
<string name="kb">KB</string> <string name="kb">KB</string>
<string name="mb">MB</string> <string name="mb">MB</string>
<string name="bridges_updated">Bridges Updated</string> <string name="bridges_updated">Bridges Updated</string>
<string name="restart_orbot_to_use_this_bridge_">Please restart Orbot to enable the changes</string> <string name="restart_orbot_to_use_this_bridge_">Please restart Orbot to enable the changes</string>
<string name="menu_qr">QR Codes</string> <string name="menu_qr">QR Codes</string>
<string name="if_your_mobile_network_actively_blocks_tor_you_can_use_a_tor_bridge_to_access_the_network_another_way_to_get_bridges_is_to_send_an_email_to_bridges_torproject_org_please_note_that_you_must_send_the_email_using_an_address_from_one_of_the_following_email_providers_riseup_gmail_or_yahoo_">If your mobile network actively blocks Tor, you can use a Bridge to access the network. SELECT one of the bridge types above to enable bridges.</string> <string name="if_your_mobile_network_actively_blocks_tor_you_can_use_a_tor_bridge_to_access_the_network_another_way_to_get_bridges_is_to_send_an_email_to_bridges_torproject_org_please_note_that_you_must_send_the_email_using_an_address_from_one_of_the_following_email_providers_riseup_gmail_or_yahoo_">If your mobile network actively blocks Tor, you can use a Bridge to access the network. SELECT one of the bridge types above to enable bridges.</string>
<string name="bridge_mode">Bridge Mode</string> <string name="bridge_mode">Bridge Mode</string>
<string name="get_bridges_email">Email</string> <string name="get_bridges_email">Email</string>
<string name="get_bridges_web">Web</string> <string name="get_bridges_web">Web</string>
<string name="activate">Activate</string> <string name="activate">Activate</string>
<string name="apps_mode">Apps VPN Mode</string> <string name="apps_mode">Apps VPN Mode</string>
<string name="you_can_enable_all_apps_on_your_device_to_run_through_the_tor_network_using_the_vpn_feature_of_android_">You can enable all apps on your device to run through the Tor network using the VPN feature of Android.\n\n*WARNING* This is a new, experimental feature and in some cases may not start automatically, or may stop. It should NOT be used for anonymity, and ONLY used for getting through firewalls and filters.</string> <string name="you_can_enable_all_apps_on_your_device_to_run_through_the_tor_network_using_the_vpn_feature_of_android_">You can enable all apps on your device to run through the Tor network using the VPN feature of Android.\n\n*WARNING* This is a new, experimental feature and in some cases may not start automatically, or may stop. It should NOT be used for anonymity, and ONLY used for getting through firewalls and filters.</string>
<string name="send_email">Send Email</string> <string name="send_email">Send Email</string>
<string name="you_must_get_a_bridge_address_by_email_web_or_from_a_friend_once_you_have_this_address_please_paste_it_into_the_bridges_preference_in_orbot_s_setting_and_restart_">You can get a bridge address through email, the web or by scanning a bridge QR code. Select \'Email\' or \'Web\' below to request a bridge address.\n\nOnce you have an address, copy &amp; paste it into the \"Bridges\" preference in Orbot\'s setting and restart.</string> <string name="you_must_get_a_bridge_address_by_email_web_or_from_a_friend_once_you_have_this_address_please_paste_it_into_the_bridges_preference_in_orbot_s_setting_and_restart_">You can get a bridge address through email, the web or by scanning a bridge QR code. Select \'Email\' or \'Web\' below to request a bridge address.\n\nOnce you have an address, copy &amp; paste it into the \"Bridges\" preference in Orbot\'s setting and restart.</string>
<string name="install_orweb">Install Orfox</string> <string name="install_orweb">Install Orfox</string>
<string name="standard_browser">Standard Browser</string> <string name="standard_browser">Standard Browser</string>
<string name="note_only_standard_tor_bridges_work_on_intel_x86_atom_devices">NOTE: Only standard Tor bridges work on Intel X86/ATOM devices</string> <string name="note_only_standard_tor_bridges_work_on_intel_x86_atom_devices">NOTE: Only standard Tor bridges work on Intel X86/ATOM devices</string>
<string name="vpn_default_world">World (Location)</string> <string name="vpn_default_world">World (Location)</string>
<string name="hidden_services">Hidden Services</string>
<string name="title_activity_hidden_services">Hidden Services</string>
<string name="menu_hidden_services">Hidden Services</string>
<string name="save">Save</string>
<string name="ports">Ports</string>
<string name="local_port">Local Port</string>
<string name="onion_port">Onion Port</string>
<string name="name">Name</string>
<string name="done">Done!</string>
<string name="invalid_port">Invalid Port</string>
<string name="copy_address_to_clipboard">Copy address to clipboard</string>
<string name="show_auth_cookie">Show auth cookie</string>
<string name="backup_service">Backup Service</string>
<string name="delete_service">Delete Service</string>
<string name="backup_saved_at_external_storage">Backup saved at external storage</string>
<string name="backup_restored">Backup restored</string>
<string name="filemanager_not_available">Filemanager not available</string>
<string name="please_grant_permissions_for_external_storage">Please grant permissions for external storage</string>
<string name="permission_granted">Permission granted</string>
<string name="permission_denied">Permission denied</string>
<string name="restore_backup">Restore Backup</string>
<string name="create_a_backup_first">Create a backup first</string>
<string name="name_can_t_be_empty">Name can\'t be empty</string>
<string name="fields_can_t_be_empty">Fields can\'t be empty</string>
<string name="start_tor_again_for_finish_the_process">Start Tor again for finish the process</string>
<string name="confirm_service_deletion">Confirm service deletion</string>
<string name="click_again_for_backup">Click again for backup</string>
<string name="service_type">Service type</string>
<string name="auth_cookie">Auth cookie</string>
<string name="copy_cookie_to_clipboard">Copy cookie to clipboard</string>
<string name="auth_cookie_was_not_configured">Auth cookie was not configured</string>
<string name="please_restart_Orbot_to_enable_the_changes">Please restart Orbot to enable the changes</string>
<string name="client_cookies">Client cookies</string>
<string name="onion">.onion</string>
<string name="invalid_onion_address">Invalid .onion address</string>
<string name="cookie_from_QR">Read from QR</string>
<string name="backup_cookie">Backup cookie</string>
<string name="delete_cookie">Delete cookie</string>
<string name="confirm_cookie_deletion">Confirm cookie deletion</string>
<string name="hosted_services">Hosted Services</string>
<string name="share_as_qr">Share as QR</string>
<string name="disable">Disable</string>
<string name="enable">Enable</string>
<string name="consider_disable_battery_optimizations">Consider disable battery optimizations</string>
<string name="consider_enable_battery_optimizations">Consider enable battery optimizations</string>
</resources> </resources>

View File

@ -1,5 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="NotificationText" parent="android:TextAppearance.StatusBar.EventContent" /> <style name="NotificationText" parent="android:TextAppearance.StatusBar.EventContent" />
<style name="NotificationTitle" parent="android:TextAppearance.StatusBar.EventContent.Title" /> <style name="NotificationTitle" parent="android:TextAppearance.StatusBar.EventContent.Title" />
<style name="DefaultTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="DefaultTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources> </resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="hidden-services" path="hidden_services/" />
</paths>

View File

@ -157,17 +157,6 @@ android:dialogTitle="@string/enter_ports"
/> />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/pref_hs_group">
<CheckBoxPreference android:title="@string/enable_hidden_services"
android:summary="@string/run_servers_accessible_via_the_tor_network" android:key="pref_hs_enable"></CheckBoxPreference>
<EditTextPreference android:summary="@string/enter_localhost_ports_for_hidden_services"
android:title="@string/hidden_service_ports" android:enabled="false" android:key="pref_hs_ports"></EditTextPreference>
<EditTextPreference android:key="pref_hs_hostname"
android:summary="@string/the_addressable_name_for_your_hidden_service_generated_automatically_"
android:title=".Onion Hostname"></EditTextPreference>
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_proxy_title"> <PreferenceCategory android:title="@string/pref_proxy_title">
<EditTextPreference android:key="pref_proxy_type" <EditTextPreference android:key="pref_proxy_type"
android:title="@string/pref_proxy_type_title" android:title="@string/pref_proxy_type_title"

View File

@ -16,19 +16,22 @@ import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.ContextWrapper; import android.content.ContextWrapper;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.provider.BaseColumns;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils; import android.text.TextUtils;
@ -62,7 +65,6 @@ import java.io.PrintStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.net.Socket; import java.net.Socket;
import java.text.Normalizer; import java.text.Normalizer;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
@ -124,10 +126,54 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
public static File fileObfsclient; public static File fileObfsclient;
public static File fileXtables; public static File fileXtables;
public static File fileTorRc; public static File fileTorRc;
private File mHSBasePath;
private Shell mShell; private Shell mShell;
private Shell mShellPolipo; private Shell mShellPolipo;
private static final Uri HS_CONTENT_URI = Uri.parse("content://org.torproject.android.ui.hiddenservices.providers/hs");
private static final Uri COOKIE_CONTENT_URI = Uri.parse("content://org.torproject.android.ui.hiddenservices.providers.cookie/cookie");
public static final class HiddenService implements BaseColumns {
public static final String NAME = "name";
public static final String PORT = "port";
public static final String ONION_PORT = "onion_port";
public static final String DOMAIN = "domain";
public static final String AUTH_COOKIE = "auth_cookie";
public static final String AUTH_COOKIE_VALUE = "auth_cookie_value";
public static final String CREATED_BY_USER = "created_by_user";
public static final String ENABLED = "enabled";
private HiddenService() {
}
}
public static final class ClientCookie implements BaseColumns {
public static final String DOMAIN = "domain";
public static final String AUTH_COOKIE_VALUE = "auth_cookie_value";
public static final String ENABLED = "enabled";
private ClientCookie() {
}
}
private String[] hsProjection = new String[]{
HiddenService._ID,
HiddenService.NAME,
HiddenService.DOMAIN,
HiddenService.PORT,
HiddenService.AUTH_COOKIE,
HiddenService.AUTH_COOKIE_VALUE,
HiddenService.ONION_PORT,
HiddenService.ENABLED};
private String[] cookieProjection = new String[]{
ClientCookie._ID,
ClientCookie.DOMAIN,
ClientCookie.AUTH_COOKIE_VALUE,
ClientCookie.ENABLED};
public void debug(String msg) public void debug(String msg)
{ {
if (Prefs.useDebugLogging()) if (Prefs.useDebugLogging())
@ -441,75 +487,6 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
sendCallbackStatus(STATUS_OFF); sendCallbackStatus(STATUS_OFF);
} }
private String getHiddenServiceHostname ()
{
SharedPreferences prefs = TorServiceUtils.getSharedPrefs(getApplicationContext());
boolean enableHiddenServices = prefs.getBoolean("pref_hs_enable", false);
StringBuffer result = new StringBuffer();
if (enableHiddenServices)
{
String hsPorts = prefs.getString("pref_hs_ports","");
StringTokenizer st = new StringTokenizer (hsPorts,",");
String hsPortConfig = null;
while (st.hasMoreTokens())
{
int hsPort = Integer.parseInt(st.nextToken().split(" ")[0]);;
File fileDir = new File(appCacheHome, "hs" + hsPort);
File file = new File(fileDir, "hostname");
if (file.exists())
{
try {
String onionHostname = Utils.readString(new FileInputStream(file)).trim();
if (result.length() > 0)
result.append(",");
result.append(onionHostname);
} catch (FileNotFoundException e) {
logException("unable to read onion hostname file",e);
showToolbarNotification(getString(R.string.unable_to_read_hidden_service_name), HS_NOTIFY_ID, R.drawable.ic_stat_notifyerr);
return null;
}
}
else
{
showToolbarNotification(getString(R.string.unable_to_read_hidden_service_name), HS_NOTIFY_ID, R.drawable.ic_stat_notifyerr);
return null;
}
}
if (result.length() > 0)
{
String onionHostname = result.toString();
showToolbarNotification(getString(R.string.hidden_service_on) + ' ' + onionHostname, HS_NOTIFY_ID, R.drawable.ic_stat_tor);
Editor pEdit = prefs.edit();
pEdit.putString("pref_hs_hostname",onionHostname);
pEdit.commit();
return onionHostname;
}
}
return null;
}
private void killAllDaemons() throws Exception { private void killAllDaemons() throws Exception {
if (conn != null) { if (conn != null) {
logNotice("Using control port to shutdown Tor"); logNotice("Using control port to shutdown Tor");
@ -577,6 +554,14 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
fileXtables = new File(appBinHome, TorServiceConstants.IPTABLES_ASSET_KEY); fileXtables = new File(appBinHome, TorServiceConstants.IPTABLES_ASSET_KEY);
fileTorRc = new File(appBinHome, TorServiceConstants.TORRC_ASSET_KEY); fileTorRc = new File(appBinHome, TorServiceConstants.TORRC_ASSET_KEY);
mHSBasePath = new File(
getFilesDir().getAbsolutePath(),
TorServiceConstants.HIDDEN_SERVICES_DIR
);
if (!mHSBasePath.isDirectory())
mHSBasePath.mkdirs();
mEventHandler = new TorEventHandler(this); mEventHandler = new TorEventHandler(this);
if (mNotificationManager == null) if (mNotificationManager == null)
@ -809,7 +794,56 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
enableTransparentProxy(); enableTransparentProxy();
} }
getHiddenServiceHostname (); // Tor is running, update new .onion names at db
ContentResolver mCR = getApplicationContext().getContentResolver();
Cursor hidden_services = mCR.query(HS_CONTENT_URI, hsProjection, null, null, null);
if(hidden_services != null) {
try {
while (hidden_services.moveToNext()) {
String HSDomain = hidden_services.getString(hidden_services.getColumnIndex(HiddenService.DOMAIN));
Integer HSLocalPort = hidden_services.getInt(hidden_services.getColumnIndex(HiddenService.PORT));
Integer HSAuthCookie = hidden_services.getInt(hidden_services.getColumnIndex(HiddenService.AUTH_COOKIE));
String HSAuthCookieValue = hidden_services.getString(hidden_services.getColumnIndex(HiddenService.AUTH_COOKIE_VALUE));
// Update only new domains or restored from backup with auth cookie
if((HSDomain == null || HSDomain.length() < 1) || (HSAuthCookie == 1 && (HSAuthCookieValue == null || HSAuthCookieValue.length() < 1))) {
String hsDirPath = new File(mHSBasePath.getAbsolutePath(),"hs" + HSLocalPort).getCanonicalPath();
File file = new File(hsDirPath, "hostname");
if (file.exists())
{
ContentValues fields = new ContentValues();
try {
String onionHostname = Utils.readString(new FileInputStream(file)).trim();
if(HSAuthCookie == 1) {
String[] aux = onionHostname.split(" ");
onionHostname = aux[0];
fields.put(HiddenService.AUTH_COOKIE_VALUE, aux[1]);
}
fields.put(HiddenService.DOMAIN, onionHostname);
mCR.update(HS_CONTENT_URI, fields, "port=" + HSLocalPort , null);
} catch (FileNotFoundException e) {
logException("unable to read onion hostname file",e);
showToolbarNotification(getString(R.string.unable_to_read_hidden_service_name), HS_NOTIFY_ID, R.drawable.ic_stat_notifyerr);
}
}
else
{
showToolbarNotification(getString(R.string.unable_to_read_hidden_service_name), HS_NOTIFY_ID, R.drawable.ic_stat_notifyerr);
}
}
}
} catch (NumberFormatException e) {
Log.e(OrbotConstants.TAG,"error parsing hsport",e);
} catch (Exception e) {
Log.e(OrbotConstants.TAG,"error starting share server",e);
}
hidden_services.close();
}
} catch (Exception e) { } catch (Exception e) {
logException("Unable to start Tor: " + e.toString(), e); logException("Unable to start Tor: " + e.toString(), e);
@ -1574,7 +1608,6 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
boolean becomeRelay = prefs.getBoolean(OrbotConstants.PREF_OR, false); boolean becomeRelay = prefs.getBoolean(OrbotConstants.PREF_OR, false);
boolean ReachableAddresses = prefs.getBoolean(OrbotConstants.PREF_REACHABLE_ADDRESSES,false); boolean ReachableAddresses = prefs.getBoolean(OrbotConstants.PREF_REACHABLE_ADDRESSES,false);
boolean enableHiddenServices = prefs.getBoolean("pref_hs_enable", false);
boolean enableStrictNodes = prefs.getBoolean("pref_strict_nodes", false); boolean enableStrictNodes = prefs.getBoolean("pref_strict_nodes", false);
String entranceNodes = prefs.getString("pref_entrance_nodes", ""); String entranceNodes = prefs.getString("pref_entrance_nodes", "");
@ -1777,49 +1810,52 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
return false; return false;
} }
if (enableHiddenServices) ContentResolver mCR = getApplicationContext().getContentResolver();
{
logNotice("hidden services are enabled"); /* ---- Hidden Services ---- */
Cursor hidden_services = mCR.query(HS_CONTENT_URI, hsProjection, HiddenService.ENABLED + "=1", null, null);
//updateConfiguration("RendPostPeriod", "600 seconds", false); //possible feature to investigate if(hidden_services != null) {
try {
String hsPorts = prefs.getString("pref_hs_ports",""); while (hidden_services.moveToNext()) {
String HSname = hidden_services.getString(hidden_services.getColumnIndex(HiddenService.NAME));
StringTokenizer st = new StringTokenizer (hsPorts,","); Integer HSLocalPort = hidden_services.getInt(hidden_services.getColumnIndex(HiddenService.PORT));
String hsPortConfig = null; Integer HSOnionPort = hidden_services.getInt(hidden_services.getColumnIndex(HiddenService.ONION_PORT));
int hsPort = -1; Integer HSAuthCookie = hidden_services.getInt(hidden_services.getColumnIndex(HiddenService.AUTH_COOKIE));
String hsDirPath = new File(mHSBasePath.getAbsolutePath(),"hs" + HSLocalPort).getCanonicalPath();
while (st.hasMoreTokens())
{ debug("Adding hidden service on port: " + HSLocalPort);
try
{
hsPortConfig = st.nextToken().trim();
if (hsPortConfig.indexOf(":")==-1) //setup the port to localhost if not specifed
{
hsPortConfig = hsPortConfig + " 127.0.0.1:" + hsPortConfig;
}
hsPort = Integer.parseInt(hsPortConfig.split(" ")[0]);
String hsDirPath = new File(appCacheHome,"hs" + hsPort).getCanonicalPath();
debug("Adding hidden service on port: " + hsPortConfig);
extraLines.append("HiddenServiceDir" + ' ' + hsDirPath).append('\n'); extraLines.append("HiddenServiceDir" + ' ' + hsDirPath).append('\n');
extraLines.append("HiddenServicePort" + ' ' + hsPortConfig).append('\n'); extraLines.append("HiddenServicePort" + ' ' + HSOnionPort + " 127.0.0.1:" + HSLocalPort).append('\n');
} catch (NumberFormatException e) { if(HSAuthCookie == 1)
Log.e(OrbotConstants.TAG,"error parsing hsport",e); extraLines.append("HiddenServiceAuthorizeClient stealth " + HSname).append('\n');
} catch (Exception e) {
Log.e(OrbotConstants.TAG,"error starting share server",e);
} }
} catch (NumberFormatException e) {
Log.e(OrbotConstants.TAG,"error parsing hsport",e);
} catch (Exception e) {
Log.e(OrbotConstants.TAG,"error starting share server",e);
} }
hidden_services.close();
} }
/* ---- Client Cookies ---- */
Cursor client_cookies = mCR.query(COOKIE_CONTENT_URI, cookieProjection, ClientCookie.ENABLED + "=1", null, null);
if(client_cookies != null) {
try {
while (client_cookies.moveToNext()) {
String domain = client_cookies.getString(client_cookies.getColumnIndex(ClientCookie.DOMAIN));
String cookie = client_cookies.getString(client_cookies.getColumnIndex(ClientCookie.AUTH_COOKIE_VALUE));
extraLines.append("HidServAuth" + ' ' + domain + ' ' + cookie).append('\n');
}
} catch (Exception e) {
Log.e(OrbotConstants.TAG,"error starting share server",e);
}
client_cookies.close();
}
return true; return true;
} }

View File

@ -161,4 +161,6 @@ public interface TorServiceConstants {
"meek_lite 0.0.2.0:3 url=https://az668014.vo.msecnd.net/ front=ajax.aspnetcdn.com" "meek_lite 0.0.2.0:3 url=https://az668014.vo.msecnd.net/ front=ajax.aspnetcdn.com"
}; };
public static final String HIDDEN_SERVICES_DIR = "hidden_services";
} }