diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 826ce67f..7dd9c1cf 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,28 +1,31 @@
-
-
-
-
-
-
-
+ -->
+
+
+
+
+
+
+ android:launchMode="singleTop">
-
-
+
+
+
+
-
-
+
+
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ android:stopWithTask="false">
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
+
+
+
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ android:theme="@style/DefaultTheme">
+ android:value=".OrbotMainActivity" />
-
+ android:authorities="org.torproject.android.ui.hiddenservices.providers"
+ android:exported="false" />
+ android:resource="@xml/hidden_services_paths" />
-
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/org/torproject/android/OrbotMainActivity.java b/app/src/main/java/org/torproject/android/OrbotMainActivity.java
index caed0c3f..3abcdb82 100644
--- a/app/src/main/java/org/torproject/android/OrbotMainActivity.java
+++ b/app/src/main/java/org/torproject/android/OrbotMainActivity.java
@@ -27,6 +27,7 @@ import org.torproject.android.ui.AppManager;
import org.torproject.android.ui.ImageProgressView;
import org.torproject.android.ui.PromoAppsActivity;
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.providers.HSContentProvider;
@@ -513,6 +514,8 @@ 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);
diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/ClientCookiesActivity.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/ClientCookiesActivity.java
new file mode 100644
index 00000000..0b948126
--- /dev/null
+++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/ClientCookiesActivity.java
@@ -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.ClienCookiesAdapter;
+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.providers.CookieContentProvider;
+import org.torproject.android.ui.hiddenservices.storage.PermissionManager;
+
+public class ClientCookiesActivity extends AppCompatActivity {
+ public final int WRITE_EXTERNAL_STORAGE_FROM_COOKIE_ACTIONBAR = 3;
+
+ private ContentResolver mResolver;
+ private ClienCookiesAdapter 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 ClienCookiesAdapter(
+ 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.usesRuntimePermissions()
+ && !PermissionManager.hasExternalWritePermission(this)) {
+ PermissionManager.requestPermissions(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
+ ));
+ }
+ }
+
+}
diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/HiddenServicesActivity.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/HiddenServicesActivity.java
index c4f55a1c..ae2cd554 100644
--- a/app/src/main/java/org/torproject/android/ui/hiddenservices/HiddenServicesActivity.java
+++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/HiddenServicesActivity.java
@@ -24,7 +24,7 @@ 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.SelectBackupDialog;
+import org.torproject.android.ui.hiddenservices.dialogs.SelectHSBackupDialog;
import org.torproject.android.ui.hiddenservices.providers.HSContentProvider;
import org.torproject.android.ui.hiddenservices.storage.PermissionManager;
@@ -156,9 +156,8 @@ public class HiddenServicesActivity extends AppCompatActivity {
return true;
}
- SelectBackupDialog dialog = new SelectBackupDialog();
- dialog.show(getSupportFragmentManager(), "SelectBackupDialog");
- return true;
+ SelectHSBackupDialog dialog = new SelectHSBackupDialog();
+ dialog.show(getSupportFragmentManager(), "SelectHSBackupDialog");
}
return super.onOptionsItemSelected(item);
@@ -174,8 +173,8 @@ public class HiddenServicesActivity extends AppCompatActivity {
switch (requestCode) {
case WRITE_EXTERNAL_STORAGE_FROM_ACTIONBAR: {
- SelectBackupDialog dialog = new SelectBackupDialog();
- dialog.show(getSupportFragmentManager(), "SelectBackupDialog");
+ SelectHSBackupDialog dialog = new SelectHSBackupDialog();
+ dialog.show(getSupportFragmentManager(), "SelectHSBackupDialog");
break;
}
case HSActionsDialog.WRITE_EXTERNAL_STORAGE_FROM_ACTION_DIALOG: {
diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/adapters/ClienCookiesAdapter.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/adapters/ClienCookiesAdapter.java
new file mode 100644
index 00000000..a9d782ba
--- /dev/null
+++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/adapters/ClienCookiesAdapter.java
@@ -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 ClienCookiesAdapter extends CursorAdapter {
+ private LayoutInflater cursorInflater;
+
+ public ClienCookiesAdapter(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);
+ }
+}
diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/backup/BackupUtils.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/backup/BackupUtils.java
index 58911d9f..a4790dd8 100644
--- a/app/src/main/java/org/torproject/android/ui/hiddenservices/backup/BackupUtils.java
+++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/backup/BackupUtils.java
@@ -12,6 +12,7 @@ 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;
@@ -28,21 +29,19 @@ import java.nio.charset.Charset;
public class BackupUtils {
private final String configFileName = "config.json";
- private File mHSBasePath;
private Context mContext;
private ContentResolver mResolver;
public BackupUtils(Context context) {
mContext = context;
- mHSBasePath = new File(
- mContext.getFilesDir().getAbsolutePath(),
- TorServiceConstants.HIDDEN_SERVICES_DIR
- );
-
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";
@@ -115,6 +114,8 @@ public class BackupUtils {
return null;
}
+ portData.close();
+
try {
FileWriter file = new FileWriter(configFilePath);
file.write(config.toString());
@@ -124,8 +125,6 @@ public class BackupUtils {
return null;
}
- portData.close();
-
String zip_path = storage_path.getAbsolutePath() + "/hs" + port + ".zip";
String files[] = {hostnameFilePath, keyFilePath, configFilePath};
@@ -139,6 +138,11 @@ public class BackupUtils {
public void restoreZipBackup(File backup) {
+ File mHSBasePath = new File(
+ mContext.getFilesDir().getAbsolutePath(),
+ TorServiceConstants.HIDDEN_SERVICES_DIR
+ );
+
int port;
Cursor service;
String backupName = backup.getName();
@@ -154,7 +158,7 @@ public class BackupUtils {
zip.unzip(hsPath.getAbsolutePath());
File config = new File(configFilePath);
- FileInputStream stream = null;
+ FileInputStream stream;
try {
stream = new FileInputStream(config);
@@ -236,6 +240,10 @@ public class BackupUtils {
}
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);
@@ -258,4 +266,71 @@ public class BackupUtils {
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;
+ }
}
diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/database/HSDatabase.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/database/HSDatabase.java
index 779c7756..18bc7aa0 100644
--- a/app/src/main/java/org/torproject/android/ui/hiddenservices/database/HSDatabase.java
+++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/database/HSDatabase.java
@@ -8,6 +8,7 @@ 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 =
@@ -22,6 +23,13 @@ public class HSDatabase extends SQLiteOpenHelper {
"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);
}
@@ -29,6 +37,7 @@ public class HSDatabase extends SQLiteOpenHelper {
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(HS_DATA_TABLE_CREATE);
+ db.execSQL(HS_CLIENT_COOKIE_TABLE_CREATE);
}
@Override
diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/AddCookieDialog.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/AddCookieDialog.java
new file mode 100644
index 00000000..75663648
--- /dev/null
+++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/AddCookieDialog.java
@@ -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);
+ }
+}
diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/CookieActionsDialog.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/CookieActionsDialog.java
new file mode 100644
index 00000000..e5683da5
--- /dev/null
+++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/CookieActionsDialog.java
@@ -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.storage.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.usesRuntimePermissions()
+ && !PermissionManager.hasExternalWritePermission(mContext)) {
+
+ PermissionManager.requestPermissions(
+ 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;
+ }
+}
diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/CookieDeleteDialog.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/CookieDeleteDialog.java
new file mode 100644
index 00000000..60c4d8e3
--- /dev/null
+++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/CookieDeleteDialog.java
@@ -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();
+ }
+}
diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/HSCookieDialog.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/HSCookieDialog.java
index ff53cec1..39c1b510 100644
--- a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/HSCookieDialog.java
+++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/HSCookieDialog.java
@@ -13,7 +13,12 @@ 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 {
@@ -44,6 +49,26 @@ public class HSCookieDialog extends DialogFragment {
}
});
+ 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) {
diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectCookieBackupDialog.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectCookieBackupDialog.java
new file mode 100644
index 00000000..de5b7848
--- /dev/null
+++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectCookieBackupDialog.java
@@ -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 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();
+ }
+}
diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectBackupDialog.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectHSBackupDialog.java
similarity index 98%
rename from app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectBackupDialog.java
rename to app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectHSBackupDialog.java
index 0c0d9438..a6e3bac9 100644
--- a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectBackupDialog.java
+++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectHSBackupDialog.java
@@ -21,7 +21,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-public class SelectBackupDialog extends DialogFragment {
+public class SelectHSBackupDialog extends DialogFragment {
@NonNull
@Override
diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/providers/CookieContentProvider.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/providers/CookieContentProvider.java
new file mode 100644
index 00000000..f420a6d8
--- /dev/null
+++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/providers/CookieContentProvider.java
@@ -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() {
+ }
+ }
+}
diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/storage/ExternalStorage.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/storage/ExternalStorage.java
index cda3f8ce..c50108b7 100644
--- a/app/src/main/java/org/torproject/android/ui/hiddenservices/storage/ExternalStorage.java
+++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/storage/ExternalStorage.java
@@ -5,13 +5,13 @@ import android.os.Environment;
import java.io.File;
public class ExternalStorage {
- private static final String BACKUPS_DIR = "Orbot-HiddenServices";
+ private static final String ORBOT_BACKUPS_DIR = "Orbot";
public static File getOrCreateBackupDir() {
if (!isExternalStorageWritable())
return null;
- File dir = new File(Environment.getExternalStorageDirectory(), BACKUPS_DIR);
+ File dir = new File(Environment.getExternalStorageDirectory(), ORBOT_BACKUPS_DIR);
if (!dir.isDirectory() && !dir.mkdirs())
return null;
diff --git a/app/src/main/res/layout/layout_activity_client_cookies.xml b/app/src/main/res/layout/layout_activity_client_cookies.xml
new file mode 100644
index 00000000..b8f0626e
--- /dev/null
+++ b/app/src/main/res/layout/layout_activity_client_cookies.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_add_client_cookie_dialog.xml b/app/src/main/res/layout/layout_add_client_cookie_dialog.xml
new file mode 100644
index 00000000..4669a921
--- /dev/null
+++ b/app/src/main/res/layout/layout_add_client_cookie_dialog.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_client_cookie_list_item.xml b/app/src/main/res/layout/layout_client_cookie_list_item.xml
new file mode 100644
index 00000000..80eefb94
--- /dev/null
+++ b/app/src/main/res/layout/layout_client_cookie_list_item.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_content_client_cookies.xml b/app/src/main/res/layout/layout_content_client_cookies.xml
new file mode 100644
index 00000000..3ee9a897
--- /dev/null
+++ b/app/src/main/res/layout/layout_content_client_cookies.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_cookie_actions.xml b/app/src/main/res/layout/layout_cookie_actions.xml
new file mode 100644
index 00000000..64d8c1c6
--- /dev/null
+++ b/app/src/main/res/layout/layout_cookie_actions.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_hs_cookie.xml b/app/src/main/res/layout/layout_hs_cookie.xml
index 398178f5..6d1a8935 100644
--- a/app/src/main/res/layout/layout_hs_cookie.xml
+++ b/app/src/main/res/layout/layout_hs_cookie.xml
@@ -17,6 +17,12 @@
android:layout_height="wrap_content"
android:id="@+id/hs_cookie_to_clipboard" />
+
+