restore backup dialog
This commit is contained in:
parent
f79d2d9005
commit
872ec40214
|
@ -2,17 +2,22 @@ package org.torproject.android.backup;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.torproject.android.service.R;
|
||||||
import org.torproject.android.service.TorServiceConstants;
|
import org.torproject.android.service.TorServiceConstants;
|
||||||
import org.torproject.android.storage.ExternalStorage;
|
import org.torproject.android.storage.ExternalStorage;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
public class BackupUtils {
|
public class BackupUtils {
|
||||||
private File mHSBasePath;
|
private File mHSBasePath;
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
public BackupUtils(Context context) {
|
public BackupUtils(Context context) {
|
||||||
mHSBasePath = context.getDir(
|
mContext = context;
|
||||||
|
mHSBasePath = mContext.getDir(
|
||||||
TorServiceConstants.DIRECTORY_TOR_DATA,
|
TorServiceConstants.DIRECTORY_TOR_DATA,
|
||||||
Application.MODE_PRIVATE
|
Application.MODE_PRIVATE
|
||||||
);
|
);
|
||||||
|
@ -20,13 +25,12 @@ public class BackupUtils {
|
||||||
|
|
||||||
public String createZipBackup(Integer port) {
|
public String createZipBackup(Integer port) {
|
||||||
|
|
||||||
ExternalStorage storage = new ExternalStorage();
|
File storage_path = ExternalStorage.getOrCreateBackupDir();
|
||||||
String storage_path = storage.createBackupDir();
|
|
||||||
if (storage_path == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String zip_path = storage_path + "/hs" + port + ".zip";
|
if (storage_path == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
String zip_path = storage_path.getAbsolutePath() + "/hs" + port + ".zip";
|
||||||
String files[] = {
|
String files[] = {
|
||||||
mHSBasePath + "/" + TorServiceConstants.HIDDEN_SERVICES_DIR + "/hs" + port + "/hostname",
|
mHSBasePath + "/" + TorServiceConstants.HIDDEN_SERVICES_DIR + "/hs" + port + "/hostname",
|
||||||
mHSBasePath + "/" + TorServiceConstants.HIDDEN_SERVICES_DIR + "/hs" + port + "/private_key"
|
mHSBasePath + "/" + TorServiceConstants.HIDDEN_SERVICES_DIR + "/hs" + port + "/private_key"
|
||||||
|
@ -42,7 +46,20 @@ public class BackupUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void restoreZipBackup(Integer port, String 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);
|
ZipIt zip = new ZipIt(null, path);
|
||||||
zip.unzip(mHSBasePath + "/hs" + port);
|
zip.unzip(hsPath.getCanonicalPath());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(mContext, R.string.error, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,38 +5,30 @@ import android.os.Environment;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
public class ExternalStorage {
|
public class ExternalStorage {
|
||||||
private final String BACKUPS_DIR = "Orbot-HiddenServices";
|
private static final String BACKUPS_DIR = "Orbot-HiddenServices";
|
||||||
|
|
||||||
public String createBackupDir() {
|
public static File getOrCreateBackupDir() {
|
||||||
if (!isExternalStorageWritable()) {
|
if (!isExternalStorageWritable())
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
File path = Environment.getExternalStoragePublicDirectory(BACKUPS_DIR);
|
File dir = new File(Environment.getExternalStorageDirectory(), BACKUPS_DIR);
|
||||||
|
|
||||||
if (!path.mkdirs()) {
|
if (!dir.isDirectory() && !dir.mkdirs())
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
return path.getAbsolutePath();
|
return dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checks if external storage is available for read and write */
|
/* Checks if external storage is available for read and write */
|
||||||
public boolean isExternalStorageWritable() {
|
public static boolean isExternalStorageWritable() {
|
||||||
String state = Environment.getExternalStorageState();
|
String state = Environment.getExternalStorageState();
|
||||||
if (Environment.MEDIA_MOUNTED.equals(state)) {
|
return Environment.MEDIA_MOUNTED.equals(state);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checks if external storage is available to at least read */
|
/* Checks if external storage is available to at least read */
|
||||||
public boolean isExternalStorageReadable() {
|
public static boolean isExternalStorageReadable() {
|
||||||
String state = Environment.getExternalStorageState();
|
String state = Environment.getExternalStorageState();
|
||||||
if (Environment.MEDIA_MOUNTED.equals(state) ||
|
return Environment.MEDIA_MOUNTED.equals(state) ||
|
||||||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
|
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,9 +16,11 @@ import android.widget.ListView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.torproject.android.R;
|
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.adapters.OnionListAdapter;
|
||||||
import org.torproject.android.ui.hs.dialogs.HSActionsDialog;
|
import org.torproject.android.ui.hs.dialogs.HSActionsDialog;
|
||||||
import org.torproject.android.ui.hs.dialogs.HSDataDialog;
|
import org.torproject.android.ui.hs.dialogs.HSDataDialog;
|
||||||
|
import org.torproject.android.ui.hs.dialogs.SelectBackupDialog;
|
||||||
import org.torproject.android.ui.hs.providers.HSContentProvider;
|
import org.torproject.android.ui.hs.providers.HSContentProvider;
|
||||||
|
|
||||||
public class HiddenServicesActivity extends AppCompatActivity {
|
public class HiddenServicesActivity extends AppCompatActivity {
|
||||||
|
@ -98,7 +100,14 @@ public class HiddenServicesActivity extends AppCompatActivity {
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
|
|
||||||
if (id == R.id.menu_restore_backup) {
|
if (id == R.id.menu_restore_backup) {
|
||||||
// TODO: Restore backup
|
if (PermissionManager.usesRuntimePermissions()
|
||||||
|
&& !PermissionManager.hasExternalWritePermission(this)) {
|
||||||
|
PermissionManager.requestPermissions(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectBackupDialog dialog = new SelectBackupDialog();
|
||||||
|
dialog.show(getSupportFragmentManager(), "SelectBackupDialog");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import android.widget.Toast;
|
||||||
|
|
||||||
import org.torproject.android.R;
|
import org.torproject.android.R;
|
||||||
import org.torproject.android.backup.BackupUtils;
|
import org.torproject.android.backup.BackupUtils;
|
||||||
|
import org.torproject.android.storage.PermissionManager;
|
||||||
import org.torproject.android.ui.hs.providers.HSContentProvider;
|
import org.torproject.android.ui.hs.providers.HSContentProvider;
|
||||||
|
|
||||||
public class HSActionsDialog extends DialogFragment {
|
public class HSActionsDialog extends DialogFragment {
|
||||||
|
@ -44,8 +45,9 @@ public class HSActionsDialog extends DialogFragment {
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
Context mContext = v.getContext();
|
Context mContext = v.getContext();
|
||||||
|
|
||||||
if (usesRuntimePermissions() && !hasExternalWritePermission(mContext)) {
|
if (PermissionManager.usesRuntimePermissions()
|
||||||
requestPermissions();
|
&& !PermissionManager.hasExternalWritePermission(mContext)) {
|
||||||
|
PermissionManager.requestPermissions(getActivity());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +60,7 @@ public class HSActionsDialog extends DialogFragment {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Toast.makeText(mContext, R.string.done, Toast.LENGTH_LONG).show();
|
Toast.makeText(mContext, R.string.backup_saved_at_external_storage, Toast.LENGTH_LONG).show();
|
||||||
|
|
||||||
Uri selectedUri = Uri.parse(backupPath.substring(0, backupPath.lastIndexOf("/")));
|
Uri selectedUri = Uri.parse(backupPath.substring(0, backupPath.lastIndexOf("/")));
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
@ -104,34 +106,4 @@ public class HSActionsDialog extends DialogFragment {
|
||||||
|
|
||||||
return actionDialog;
|
return actionDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean usesRuntimePermissions() {
|
|
||||||
return (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
|
||||||
private boolean hasExternalWritePermission(Context context) {
|
|
||||||
return (context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void requestPermissions() {
|
|
||||||
if (ActivityCompat.shouldShowRequestPermissionRationale
|
|
||||||
(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
|
||||||
Snackbar.make(getActivity().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(getActivity(),
|
|
||||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
|
||||||
PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
|
|
||||||
}
|
|
||||||
}).show();
|
|
||||||
} else {
|
|
||||||
ActivityCompat.requestPermissions(getActivity(),
|
|
||||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
|
||||||
PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical" android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/listview_hs_backups" />
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/backup_list_item"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingLeft="15dp"
|
||||||
|
tools:paddingLeft="15dp"
|
||||||
|
android:paddingRight="15dp"
|
||||||
|
tools:paddingRight="15dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/backup_name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="35sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
|
@ -337,14 +337,16 @@
|
||||||
<string name="local_port">Local Port</string>
|
<string name="local_port">Local Port</string>
|
||||||
<string name="onion_port">Onion Port</string>
|
<string name="onion_port">Onion Port</string>
|
||||||
<string name="name">Name</string>
|
<string name="name">Name</string>
|
||||||
|
<string name="done">Done!</string>
|
||||||
<string name="invalid_port">Invalid Port</string>
|
<string name="invalid_port">Invalid Port</string>
|
||||||
<string name="copy_address_to_clipboard">Copy address to clipboard</string>
|
<string name="copy_address_to_clipboard">Copy address to clipboard</string>
|
||||||
<string name="backup_service">Backup Service</string>
|
<string name="backup_service">Backup Service</string>
|
||||||
<string name="delete_service">Delete Service</string>
|
<string name="delete_service">Delete Service</string>
|
||||||
<string name="done">Done!</string>
|
<string name="backup_saved_at_external_storage">Backup saved at external storage</string>
|
||||||
<string name="filemanager_not_available">Filemanager not available</string>
|
<string name="filemanager_not_available">Filemanager not available</string>
|
||||||
<string name="please_grant_permissions_for_external_storage">Please grant permissions for external storage</string>
|
<string name="please_grant_permissions_for_external_storage">Please grant permissions for external storage</string>
|
||||||
<string name="permission_granted">Permission granted</string>
|
<string name="permission_granted">Permission granted</string>
|
||||||
<string name="permission_denied">Permission denied</string>
|
<string name="permission_denied">Permission denied</string>
|
||||||
<string name="restore_backup">Restore Backup</string>
|
<string name="restore_backup">Restore Backup</string>
|
||||||
|
<string name="create_a_backup_first">Create a backup first</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue