[tor-commits] [orbot/master] big refactor

n8fr8 at torproject.org n8fr8 at torproject.org
Thu Mar 2 04:10:21 UTC 2017


commit d28db417827c601eddaf6be9f01326c68b9c3e03
Author: arrase <arrase at 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";





More information about the tor-commits mailing list