commit d28db417827c601eddaf6be9f01326c68b9c3e03 Author: arrase arrase@gmail.com Date: Thu Nov 24 02:56:11 2016 +0100
big refactor --- app/src/main/AndroidManifest.xml | 10 +- .../org/torproject/android/OrbotMainActivity.java | 6 +- .../org/torproject/android/backup/BackupUtils.java | 65 ---------- .../java/org/torproject/android/backup/ZipIt.java | 98 --------------- .../android/storage/AppDataProvider.java | 39 ------ .../android/storage/ExternalStorage.java | 34 ------ .../android/storage/PermissionManager.java | 50 -------- .../ui/hiddenservices/HiddenServicesActivity.java | 129 ++++++++++++++++++++ .../ui/hiddenservices/adapters/BackupAdapter.java | 50 ++++++++ .../hiddenservices/adapters/OnionListAdapter.java | 38 ++++++ .../ui/hiddenservices/backup/BackupUtils.java | 65 ++++++++++ .../android/ui/hiddenservices/backup/ZipIt.java | 98 +++++++++++++++ .../ui/hiddenservices/database/HSDatabase.java | 35 ++++++ .../ui/hiddenservices/dialogs/HSActionsDialog.java | 103 ++++++++++++++++ .../ui/hiddenservices/dialogs/HSDataDialog.java | 89 ++++++++++++++ .../hiddenservices/dialogs/SelectBackupDialog.java | 81 +++++++++++++ .../providers/HSContentProvider.java | 133 +++++++++++++++++++++ .../ui/hiddenservices/storage/AppDataProvider.java | 39 ++++++ .../ui/hiddenservices/storage/ExternalStorage.java | 34 ++++++ .../hiddenservices/storage/PermissionManager.java | 50 ++++++++ .../android/ui/hs/HiddenServicesActivity.java | 129 -------------------- .../android/ui/hs/adapters/BackupAdapter.java | 50 -------- .../android/ui/hs/adapters/OnionListAdapter.java | 38 ------ .../android/ui/hs/database/HSDatabase.java | 35 ------ .../android/ui/hs/dialogs/HSActionsDialog.java | 109 ----------------- .../android/ui/hs/dialogs/HSDataDialog.java | 89 -------------- .../android/ui/hs/dialogs/SelectBackupDialog.java | 81 ------------- .../android/ui/hs/providers/HSContentProvider.java | 133 --------------------- app/src/main/res/layout/layout_hs_list_view.xml | 2 +- .../main/res/layout/layout_hs_list_view_main.xml | 2 +- .../org/torproject/android/service/TorService.java | 2 +- 31 files changed, 955 insertions(+), 961 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c099076..b7a5e87 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -137,7 +137,7 @@ </receiver>
<activity - android:name=".ui.hs.HiddenServicesActivity" + android:name=".ui.hiddenservices.HiddenServicesActivity" android:label="@string/title_activity_hidden_services" android:theme="@style/DefaultTheme" > <meta-data @@ -146,13 +146,13 @@ </activity>
<provider - android:name=".ui.hs.providers.HSContentProvider" + android:name=".ui.hiddenservices.providers.HSContentProvider" android:exported="false" - android:authorities="org.torproject.android.ui.hs.providers" /> + android:authorities="org.torproject.android.ui.hiddenservices.providers" />
<provider - android:name="org.torproject.android.storage.AppDataProvider" - android:authorities="org.torproject.android.storage" + android:name=".ui.hiddenservices.storage.AppDataProvider" + android:authorities="org.torproject.android.ui.hiddenservices.storage" android:exported="false" android:grantUriPermissions="true"> <meta-data diff --git a/app/src/main/java/org/torproject/android/OrbotMainActivity.java b/app/src/main/java/org/torproject/android/OrbotMainActivity.java index 1ea9b6e..9a5bfb5 100644 --- a/app/src/main/java/org/torproject/android/OrbotMainActivity.java +++ b/app/src/main/java/org/torproject/android/OrbotMainActivity.java @@ -23,13 +23,13 @@ import org.torproject.android.service.TorServiceConstants; import org.torproject.android.service.util.TorServiceUtils; import org.torproject.android.settings.SettingsPreferences; import org.torproject.android.ui.AppManager; -import org.torproject.android.ui.hs.HiddenServicesActivity; +import org.torproject.android.ui.hiddenservices.HiddenServicesActivity; import org.torproject.android.ui.ImageProgressView; import org.torproject.android.ui.PromoAppsActivity; import org.torproject.android.ui.Rotate3dAnimation; -import org.torproject.android.ui.hs.providers.HSContentProvider; +import org.torproject.android.ui.hiddenservices.providers.HSContentProvider; import org.torproject.android.vpn.VPNEnableActivity; -import org.torproject.android.backup.BackupUtils; +import org.torproject.android.ui.hiddenservices.backup.BackupUtils;
import android.annotation.SuppressLint; import android.app.ActivityManager; diff --git a/app/src/main/java/org/torproject/android/backup/BackupUtils.java b/app/src/main/java/org/torproject/android/backup/BackupUtils.java deleted file mode 100644 index 57d8c8d..0000000 --- a/app/src/main/java/org/torproject/android/backup/BackupUtils.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.torproject.android.backup; - -import android.app.Application; -import android.content.Context; -import android.widget.Toast; - -import org.torproject.android.service.R; -import org.torproject.android.service.TorServiceConstants; -import org.torproject.android.storage.ExternalStorage; - -import java.io.File; -import java.io.IOException; - -public class BackupUtils { - private File mHSBasePath; - private Context mContext; - - public BackupUtils(Context context) { - mContext = context; - mHSBasePath = mContext.getDir( - TorServiceConstants.DIRECTORY_TOR_DATA, - Application.MODE_PRIVATE - ); - } - - public String createZipBackup(Integer port) { - - File storage_path = ExternalStorage.getOrCreateBackupDir(); - - if (storage_path == null) - return null; - - String zip_path = storage_path.getAbsolutePath() + "/hs" + port + ".zip"; - String files[] = { - mHSBasePath + "/" + TorServiceConstants.HIDDEN_SERVICES_DIR + "/hs" + port + "/hostname", - mHSBasePath + "/" + TorServiceConstants.HIDDEN_SERVICES_DIR + "/hs" + port + "/private_key" - }; - - ZipIt zip = new ZipIt(files, zip_path); - - if (!zip.zip()) { - return null; - } - - return zip_path; - } - - public void restoreZipBackup(Integer port, String path) { - String hsBasePath; - - try { - hsBasePath = mHSBasePath.getCanonicalPath() + "/" + TorServiceConstants.HIDDEN_SERVICES_DIR; - File hsPath = new File(hsBasePath, "/hs" + port); - if (hsPath.mkdirs()) { - ZipIt zip = new ZipIt(null, path); - zip.unzip(hsPath.getCanonicalPath()); - return; - } - } catch (IOException e) { - e.printStackTrace(); - } - - Toast.makeText(mContext, R.string.error, Toast.LENGTH_LONG).show(); - } -} diff --git a/app/src/main/java/org/torproject/android/backup/ZipIt.java b/app/src/main/java/org/torproject/android/backup/ZipIt.java deleted file mode 100644 index bde29f0..0000000 --- a/app/src/main/java/org/torproject/android/backup/ZipIt.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.torproject.android.backup; - - -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(String[] files, String zipFile) { - _files = files; - _zipFile = zipFile; - } - - public boolean zip() { - try { - BufferedInputStream origin = null; - 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; - } -} \ No newline at end of file diff --git a/app/src/main/java/org/torproject/android/storage/AppDataProvider.java b/app/src/main/java/org/torproject/android/storage/AppDataProvider.java deleted file mode 100644 index c04a293..0000000 --- a/app/src/main/java/org/torproject/android/storage/AppDataProvider.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.torproject.android.storage; - - -import android.app.Application; -import android.content.Context; - -import com.commonsware.cwac.provider.LocalPathStrategy; -import com.commonsware.cwac.provider.StreamProvider; -import com.commonsware.cwac.provider.StreamStrategy; - -import org.torproject.android.service.TorServiceConstants; - -import java.io.IOException; -import java.util.HashMap; - -public class AppDataProvider extends StreamProvider { - private static final String TAG = "app-data-path"; - - @Override - protected StreamStrategy buildStrategy(Context context, - String tag, String name, - String path, - HashMap<String, String> attrs) - throws IOException { - - if (TAG.equals(tag)) { - return (new LocalPathStrategy( - name, - context.getDir( - TorServiceConstants.DIRECTORY_TOR_DATA, - Application.MODE_PRIVATE - ) - ) - ); - } - - return (super.buildStrategy(context, tag, name, path, attrs)); - } -} diff --git a/app/src/main/java/org/torproject/android/storage/ExternalStorage.java b/app/src/main/java/org/torproject/android/storage/ExternalStorage.java deleted file mode 100644 index bea196e..0000000 --- a/app/src/main/java/org/torproject/android/storage/ExternalStorage.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.torproject.android.storage; - -import android.os.Environment; - -import java.io.File; - -public class ExternalStorage { - private static final String BACKUPS_DIR = "Orbot-HiddenServices"; - - public static File getOrCreateBackupDir() { - if (!isExternalStorageWritable()) - return null; - - File dir = new File(Environment.getExternalStorageDirectory(), 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); - } -} diff --git a/app/src/main/java/org/torproject/android/storage/PermissionManager.java b/app/src/main/java/org/torproject/android/storage/PermissionManager.java deleted file mode 100644 index 128db8e..0000000 --- a/app/src/main/java/org/torproject/android/storage/PermissionManager.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.torproject.android.storage; - - -import android.Manifest; -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.Build; -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 { - private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1; - - public static boolean usesRuntimePermissions() { - return (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1); - } - - @SuppressLint("NewApi") - public static boolean hasExternalWritePermission(Context context) { - return (context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED); - } - - public static void requestPermissions(FragmentActivity activity) { - 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("ENABLE", - new View.OnClickListener() { - @Override - public void onClick(View v) { - ActivityCompat.requestPermissions(mActivity, - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE); - } - }).show(); - } else { - ActivityCompat.requestPermissions(mActivity, - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE); - } - } -} 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 new file mode 100644 index 0000000..119b13a --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/HiddenServicesActivity.java @@ -0,0 +1,129 @@ +package org.torproject.android.ui.hiddenservices; + + +import android.content.ContentResolver; +import android.database.ContentObserver; +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.TextView; + +import org.torproject.android.R; +import org.torproject.android.ui.hiddenservices.storage.PermissionManager; +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.providers.HSContentProvider; + +public class HiddenServicesActivity extends AppCompatActivity { + private ContentResolver mCR; + private OnionListAdapter mAdapter; + private Toolbar toolbar; + + private String[] mProjection = new String[]{ + HSContentProvider.HiddenService._ID, + HSContentProvider.HiddenService.NAME, + HSContentProvider.HiddenService.PORT, + HSContentProvider.HiddenService.DOMAIN, + HSContentProvider.HiddenService.CREATED_BY_USER + }; + + 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) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + mCR = getContentResolver(); + + FloatingActionButton 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, + mCR.query(HSContentProvider.CONTENT_URI, mProjection, mWhere, null, null), + 0 + ); + + mCR.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) { + TextView port = (TextView) view.findViewById(R.id.hs_port); + TextView onion = (TextView) view.findViewById(R.id.hs_onion); + + Bundle arguments = new Bundle(); + arguments.putString("port", port.getText().toString()); + arguments.putString("onion", onion.getText().toString()); + + HSActionsDialog dialog = new HSActionsDialog(); + dialog.setArguments(arguments); + dialog.show(getSupportFragmentManager(), "HSActionsDialog"); + } + }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.hs_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + + if (id == R.id.menu_restore_backup) { + if (PermissionManager.usesRuntimePermissions() + && !PermissionManager.hasExternalWritePermission(this)) { + PermissionManager.requestPermissions(this); + return true; + } + + SelectBackupDialog dialog = new SelectBackupDialog(); + dialog.show(getSupportFragmentManager(), "SelectBackupDialog"); + return true; + } + + return super.onOptionsItemSelected(item); + } + + class HSObserver extends ContentObserver { + HSObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + mAdapter.changeCursor(mCR.query( + HSContentProvider.CONTENT_URI, mProjection, mWhere, null, null + )); + } + } +} diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/adapters/BackupAdapter.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/adapters/BackupAdapter.java new file mode 100644 index 0000000..196023b --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/adapters/BackupAdapter.java @@ -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; + } +} diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/adapters/OnionListAdapter.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/adapters/OnionListAdapter.java new file mode 100644 index 0000000..1edbef2 --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/adapters/OnionListAdapter.java @@ -0,0 +1,38 @@ +package org.torproject.android.ui.hiddenservices.adapters; + +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.TextView; + +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) { + 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))); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return cursorInflater.inflate(R.layout.layout_hs_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 new file mode 100644 index 0000000..c60beb9 --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/backup/BackupUtils.java @@ -0,0 +1,65 @@ +package org.torproject.android.ui.hiddenservices.backup; + +import android.app.Application; +import android.content.Context; +import android.widget.Toast; + +import org.torproject.android.service.R; +import org.torproject.android.service.TorServiceConstants; +import org.torproject.android.ui.hiddenservices.storage.ExternalStorage; + +import java.io.File; +import java.io.IOException; + +public class BackupUtils { + private File mHSBasePath; + private Context mContext; + + public BackupUtils(Context context) { + mContext = context; + mHSBasePath = mContext.getDir( + TorServiceConstants.DIRECTORY_TOR_DATA, + Application.MODE_PRIVATE + ); + } + + public String createZipBackup(Integer port) { + + File storage_path = ExternalStorage.getOrCreateBackupDir(); + + if (storage_path == null) + return null; + + String zip_path = storage_path.getAbsolutePath() + "/hs" + port + ".zip"; + String files[] = { + mHSBasePath + "/" + TorServiceConstants.HIDDEN_SERVICES_DIR + "/hs" + port + "/hostname", + mHSBasePath + "/" + TorServiceConstants.HIDDEN_SERVICES_DIR + "/hs" + port + "/private_key" + }; + + ZipIt zip = new ZipIt(files, zip_path); + + if (!zip.zip()) { + return null; + } + + return zip_path; + } + + public void restoreZipBackup(Integer port, String path) { + String hsBasePath; + + try { + hsBasePath = mHSBasePath.getCanonicalPath() + "/" + TorServiceConstants.HIDDEN_SERVICES_DIR; + File hsPath = new File(hsBasePath, "/hs" + port); + if (hsPath.mkdirs()) { + ZipIt zip = new ZipIt(null, path); + zip.unzip(hsPath.getCanonicalPath()); + return; + } + } catch (IOException e) { + e.printStackTrace(); + } + + Toast.makeText(mContext, R.string.error, Toast.LENGTH_LONG).show(); + } +} diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/backup/ZipIt.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/backup/ZipIt.java new file mode 100644 index 0000000..42e0bf9 --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/backup/ZipIt.java @@ -0,0 +1,98 @@ +package org.torproject.android.ui.hiddenservices.backup; + + +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(String[] files, String zipFile) { + _files = files; + _zipFile = zipFile; + } + + public boolean zip() { + try { + BufferedInputStream origin = null; + 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; + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..8f1123f --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/database/HSDatabase.java @@ -0,0 +1,35 @@ +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"; + 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, " + + "created_by_user INTEGER DEFAULT 0, " + + "port INTEGER);"; + + public HSDatabase(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(HS_DATA_TABLE_CREATE); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + } +} + diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/HSActionsDialog.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/HSActionsDialog.java new file mode 100644 index 0000000..103f87b --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/HSActionsDialog.java @@ -0,0 +1,103 @@ +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.storage.PermissionManager; +import org.torproject.android.ui.hiddenservices.providers.HSContentProvider; + +public class HSActionsDialog extends DialogFragment { + public final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1; + + @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.usesRuntimePermissions() + && !PermissionManager.hasExternalWritePermission(mContext)) { + PermissionManager.requestPermissions(getActivity()); + 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 delete = (Button) dialog_view.findViewById(R.id.btn_hs_delete); + delete.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + v.getContext().getContentResolver().delete( + HSContentProvider.CONTENT_URI, "port=" + arguments.getString("port"), null + ); + 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; + } +} diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/HSDataDialog.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/HSDataDialog.java new file mode 100644 index 0000000..0b04135 --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/HSDataDialog.java @@ -0,0 +1,89 @@ +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.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() + ); + + if (checkInput(localPort, onionPort)) { + saveData(serverName, localPort, onionPort); + 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(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 (!is_ok) { + Toast.makeText(getContext(), error_msg, Toast.LENGTH_SHORT).show(); + } + + return is_ok; + } + + private void saveData(String name, Integer local, Integer remote) { + ContentValues fields = new ContentValues(); + fields.put("name", name); + fields.put("port", local); + fields.put("onion_port", remote); + fields.put("created_by_user", 1); + + ContentResolver cr = getContext().getContentResolver(); + + cr.insert(HSContentProvider.CONTENT_URI, fields); + } +} 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/SelectBackupDialog.java new file mode 100644 index 0000000..ce2c717 --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/dialogs/SelectBackupDialog.java @@ -0,0 +1,81 @@ +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.storage.ExternalStorage; +import org.torproject.android.ui.hiddenservices.adapters.BackupAdapter; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SelectBackupDialog 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) { + // TODO + } + }); + + return backupsDialog.create(); + } +} diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/providers/HSContentProvider.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/providers/HSContentProvider.java new file mode 100644 index 0000000..856c685 --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/providers/HSContentProvider.java @@ -0,0 +1,133 @@ +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 { + 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; + + //Inicializamos el UriMatcher + static { + uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + uriMatcher.addURI(AUTH, "hs", ONIONS); + uriMatcher.addURI(AUTH, "hs/#", ONION_ID); + } + + private HSDatabase mServerDB; + private Context mContext; + + @Override + public boolean onCreate() { + mContext = getContext(); + mServerDB = new HSDatabase(mContext); + return true; + } + + @Nullable + @Override + public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + //Si es una consulta a un ID concreto construimos el WHERE + String where = selection; + if (uriMatcher.match(uri) == ONION_ID) { + where = "_id=" + uri.getLastPathSegment(); + } + + SQLiteDatabase db = mServerDB.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 = mServerDB.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) { + + //Si es una consulta a un ID concreto construimos el WHERE + String where = selection; + if (uriMatcher.match(uri) == ONION_ID) { + where = "_id=" + uri.getLastPathSegment(); + } + + SQLiteDatabase db = mServerDB.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 = mServerDB.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 CREATED_BY_USER = "created_by_user"; + + private HiddenService() { + } + } +} diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/storage/AppDataProvider.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/storage/AppDataProvider.java new file mode 100644 index 0000000..6e55569 --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/storage/AppDataProvider.java @@ -0,0 +1,39 @@ +package org.torproject.android.ui.hiddenservices.storage; + + +import android.app.Application; +import android.content.Context; + +import com.commonsware.cwac.provider.LocalPathStrategy; +import com.commonsware.cwac.provider.StreamProvider; +import com.commonsware.cwac.provider.StreamStrategy; + +import org.torproject.android.service.TorServiceConstants; + +import java.io.IOException; +import java.util.HashMap; + +public class AppDataProvider extends StreamProvider { + private static final String TAG = "app-data-path"; + + @Override + protected StreamStrategy buildStrategy(Context context, + String tag, String name, + String path, + HashMap<String, String> attrs) + throws IOException { + + if (TAG.equals(tag)) { + return (new LocalPathStrategy( + name, + context.getDir( + TorServiceConstants.DIRECTORY_TOR_DATA, + Application.MODE_PRIVATE + ) + ) + ); + } + + return (super.buildStrategy(context, tag, name, path, attrs)); + } +} 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 new file mode 100644 index 0000000..cda3f8c --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/storage/ExternalStorage.java @@ -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 BACKUPS_DIR = "Orbot-HiddenServices"; + + public static File getOrCreateBackupDir() { + if (!isExternalStorageWritable()) + return null; + + File dir = new File(Environment.getExternalStorageDirectory(), 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); + } +} diff --git a/app/src/main/java/org/torproject/android/ui/hiddenservices/storage/PermissionManager.java b/app/src/main/java/org/torproject/android/ui/hiddenservices/storage/PermissionManager.java new file mode 100644 index 0000000..80706c5 --- /dev/null +++ b/app/src/main/java/org/torproject/android/ui/hiddenservices/storage/PermissionManager.java @@ -0,0 +1,50 @@ +package org.torproject.android.ui.hiddenservices.storage; + + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; +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 { + private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1; + + public static boolean usesRuntimePermissions() { + return (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1); + } + + @SuppressLint("NewApi") + public static boolean hasExternalWritePermission(Context context) { + return (context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED); + } + + public static void requestPermissions(FragmentActivity activity) { + 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("ENABLE", + new View.OnClickListener() { + @Override + public void onClick(View v) { + ActivityCompat.requestPermissions(mActivity, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE); + } + }).show(); + } else { + ActivityCompat.requestPermissions(mActivity, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE); + } + } +} diff --git a/app/src/main/java/org/torproject/android/ui/hs/HiddenServicesActivity.java b/app/src/main/java/org/torproject/android/ui/hs/HiddenServicesActivity.java deleted file mode 100644 index 4474668..0000000 --- a/app/src/main/java/org/torproject/android/ui/hs/HiddenServicesActivity.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.torproject.android.ui.hs; - - -import android.content.ContentResolver; -import android.database.ContentObserver; -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.TextView; - -import org.torproject.android.R; -import org.torproject.android.storage.PermissionManager; -import org.torproject.android.ui.hs.adapters.OnionListAdapter; -import org.torproject.android.ui.hs.dialogs.HSActionsDialog; -import org.torproject.android.ui.hs.dialogs.HSDataDialog; -import org.torproject.android.ui.hs.dialogs.SelectBackupDialog; -import org.torproject.android.ui.hs.providers.HSContentProvider; - -public class HiddenServicesActivity extends AppCompatActivity { - private ContentResolver mCR; - private OnionListAdapter mAdapter; - private Toolbar toolbar; - - private String[] mProjection = new String[]{ - HSContentProvider.HiddenService._ID, - HSContentProvider.HiddenService.NAME, - HSContentProvider.HiddenService.PORT, - HSContentProvider.HiddenService.DOMAIN, - HSContentProvider.HiddenService.CREATED_BY_USER - }; - - 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) findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - mCR = getContentResolver(); - - FloatingActionButton 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, - mCR.query(HSContentProvider.CONTENT_URI, mProjection, mWhere, null, null), - 0 - ); - - mCR.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) { - TextView port = (TextView) view.findViewById(R.id.hs_port); - TextView onion = (TextView) view.findViewById(R.id.hs_onion); - - Bundle arguments = new Bundle(); - arguments.putString("port", port.getText().toString()); - arguments.putString("onion", onion.getText().toString()); - - HSActionsDialog dialog = new HSActionsDialog(); - dialog.setArguments(arguments); - dialog.show(getSupportFragmentManager(), "HSActionsDialog"); - } - }); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.hs_menu, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); - - if (id == R.id.menu_restore_backup) { - if (PermissionManager.usesRuntimePermissions() - && !PermissionManager.hasExternalWritePermission(this)) { - PermissionManager.requestPermissions(this); - return true; - } - - SelectBackupDialog dialog = new SelectBackupDialog(); - dialog.show(getSupportFragmentManager(), "SelectBackupDialog"); - return true; - } - - return super.onOptionsItemSelected(item); - } - - class HSObserver extends ContentObserver { - HSObserver(Handler handler) { - super(handler); - } - - @Override - public void onChange(boolean selfChange) { - mAdapter.changeCursor(mCR.query( - HSContentProvider.CONTENT_URI, mProjection, mWhere, null, null - )); - } - } -} diff --git a/app/src/main/java/org/torproject/android/ui/hs/adapters/BackupAdapter.java b/app/src/main/java/org/torproject/android/ui/hs/adapters/BackupAdapter.java deleted file mode 100644 index bcc7a5a..0000000 --- a/app/src/main/java/org/torproject/android/ui/hs/adapters/BackupAdapter.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.torproject.android.ui.hs.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; - } -} diff --git a/app/src/main/java/org/torproject/android/ui/hs/adapters/OnionListAdapter.java b/app/src/main/java/org/torproject/android/ui/hs/adapters/OnionListAdapter.java deleted file mode 100644 index 0ae10cb..0000000 --- a/app/src/main/java/org/torproject/android/ui/hs/adapters/OnionListAdapter.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.torproject.android.ui.hs.adapters; - -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.TextView; - -import org.torproject.android.R; -import org.torproject.android.ui.hs.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) { - 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))); - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return cursorInflater.inflate(R.layout.layout_hs_list_item, parent, false); - } -} diff --git a/app/src/main/java/org/torproject/android/ui/hs/database/HSDatabase.java b/app/src/main/java/org/torproject/android/ui/hs/database/HSDatabase.java deleted file mode 100644 index 07e2ca2..0000000 --- a/app/src/main/java/org/torproject/android/ui/hs/database/HSDatabase.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.torproject.android.ui.hs.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"; - 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, " + - "created_by_user INTEGER DEFAULT 0, " + - "port INTEGER);"; - - public HSDatabase(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL(HS_DATA_TABLE_CREATE); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - } -} - diff --git a/app/src/main/java/org/torproject/android/ui/hs/dialogs/HSActionsDialog.java b/app/src/main/java/org/torproject/android/ui/hs/dialogs/HSActionsDialog.java deleted file mode 100644 index d5603b7..0000000 --- a/app/src/main/java/org/torproject/android/ui/hs/dialogs/HSActionsDialog.java +++ /dev/null @@ -1,109 +0,0 @@ -package org.torproject.android.ui.hs.dialogs; - - -import android.Manifest; -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.design.widget.Snackbar; -import android.support.v4.app.ActivityCompat; -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.backup.BackupUtils; -import org.torproject.android.storage.PermissionManager; -import org.torproject.android.ui.hs.providers.HSContentProvider; - -public class HSActionsDialog extends DialogFragment { - public final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1; - - @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.usesRuntimePermissions() - && !PermissionManager.hasExternalWritePermission(mContext)) { - PermissionManager.requestPermissions(getActivity()); - 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 delete = (Button) dialog_view.findViewById(R.id.btn_hs_delete); - delete.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - v.getContext().getContentResolver().delete( - HSContentProvider.CONTENT_URI, "port=" + arguments.getString("port"), null - ); - 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; - } -} diff --git a/app/src/main/java/org/torproject/android/ui/hs/dialogs/HSDataDialog.java b/app/src/main/java/org/torproject/android/ui/hs/dialogs/HSDataDialog.java deleted file mode 100644 index 922b7f0..0000000 --- a/app/src/main/java/org/torproject/android/ui/hs/dialogs/HSDataDialog.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.torproject.android.ui.hs.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.hs.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() - ); - - if (checkInput(localPort, onionPort)) { - saveData(serverName, localPort, onionPort); - 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(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 (!is_ok) { - Toast.makeText(getContext(), error_msg, Toast.LENGTH_SHORT).show(); - } - - return is_ok; - } - - private void saveData(String name, Integer local, Integer remote) { - ContentValues fields = new ContentValues(); - fields.put("name", name); - fields.put("port", local); - fields.put("onion_port", remote); - fields.put("created_by_user", 1); - - ContentResolver cr = getContext().getContentResolver(); - - cr.insert(HSContentProvider.CONTENT_URI, fields); - } -} diff --git a/app/src/main/java/org/torproject/android/ui/hs/dialogs/SelectBackupDialog.java b/app/src/main/java/org/torproject/android/ui/hs/dialogs/SelectBackupDialog.java deleted file mode 100644 index 37c722b..0000000 --- a/app/src/main/java/org/torproject/android/ui/hs/dialogs/SelectBackupDialog.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.torproject.android.ui.hs.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.storage.ExternalStorage; -import org.torproject.android.ui.hs.adapters.BackupAdapter; - -import java.io.File; -import java.io.FilenameFilter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class SelectBackupDialog 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) { - // TODO - } - }); - - return backupsDialog.create(); - } -} diff --git a/app/src/main/java/org/torproject/android/ui/hs/providers/HSContentProvider.java b/app/src/main/java/org/torproject/android/ui/hs/providers/HSContentProvider.java deleted file mode 100644 index f222b85..0000000 --- a/app/src/main/java/org/torproject/android/ui/hs/providers/HSContentProvider.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.torproject.android.ui.hs.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.hs.database.HSDatabase; - - -public class HSContentProvider extends ContentProvider { - private static final String AUTH = "org.torproject.android.ui.hs.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; - - //Inicializamos el UriMatcher - static { - uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); - uriMatcher.addURI(AUTH, "hs", ONIONS); - uriMatcher.addURI(AUTH, "hs/#", ONION_ID); - } - - private HSDatabase mServerDB; - private Context mContext; - - @Override - public boolean onCreate() { - mContext = getContext(); - mServerDB = new HSDatabase(mContext); - return true; - } - - @Nullable - @Override - public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - //Si es una consulta a un ID concreto construimos el WHERE - String where = selection; - if (uriMatcher.match(uri) == ONION_ID) { - where = "_id=" + uri.getLastPathSegment(); - } - - SQLiteDatabase db = mServerDB.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 = mServerDB.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) { - - //Si es una consulta a un ID concreto construimos el WHERE - String where = selection; - if (uriMatcher.match(uri) == ONION_ID) { - where = "_id=" + uri.getLastPathSegment(); - } - - SQLiteDatabase db = mServerDB.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 = mServerDB.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 CREATED_BY_USER = "created_by_user"; - - private HiddenService() { - } - } -} diff --git a/app/src/main/res/layout/layout_hs_list_view.xml b/app/src/main/res/layout/layout_hs_list_view.xml index c47a392..3e7b32f 100644 --- a/app/src/main/res/layout/layout_hs_list_view.xml +++ b/app/src/main/res/layout/layout_hs_list_view.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" - tools:context="org.torproject.android.ui.hs.HiddenServicesActivity"> + tools:context="org.torproject.android.ui.hiddenservices.HiddenServicesActivity">
<android.support.design.widget.AppBarLayout android:layout_width="match_parent" diff --git a/app/src/main/res/layout/layout_hs_list_view_main.xml b/app/src/main/res/layout/layout_hs_list_view_main.xml index bb1c94c..284a01c 100644 --- a/app/src/main/res/layout/layout_hs_list_view_main.xml +++ b/app/src/main/res/layout/layout_hs_list_view_main.xml @@ -10,7 +10,7 @@ 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.hs.HiddenServicesActivity" + tools:context="org.torproject.android.ui.hiddenservices.HiddenServicesActivity" tools:showIn="@layout/layout_hs_list_view">
<ListView diff --git a/orbotservice/src/main/java/org/torproject/android/service/TorService.java b/orbotservice/src/main/java/org/torproject/android/service/TorService.java index f8ed3b3..6d12424 100644 --- a/orbotservice/src/main/java/org/torproject/android/service/TorService.java +++ b/orbotservice/src/main/java/org/torproject/android/service/TorService.java @@ -132,7 +132,7 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon private Shell mShellPolipo;
- private static final Uri CONTENT_URI = Uri.parse("content://org.torproject.android.ui.hs.providers/hs"); + private static final Uri CONTENT_URI = Uri.parse("content://org.torproject.android.ui.hiddenservices.providers/hs");
public static final class HiddenService implements BaseColumns { public static final String NAME = "name";
tor-commits@lists.torproject.org