account: import/export
Add an option to export an account
Allow the user to import an account from his storage.
Ask permission if necessary.
Update translations.
Tuleap: #335
Change-Id: I6afefefb252871319930a0eb55bcf1a028a3da10
diff --git a/ring-android/app/src/main/AndroidManifest.xml b/ring-android/app/src/main/AndroidManifest.xml
index c466991..45c3e23 100644
--- a/ring-android/app/src/main/AndroidManifest.xml
+++ b/ring-android/app/src/main/AndroidManifest.xml
@@ -1,20 +1,20 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright (C) 2004-2016 Savoir-faire Linux Inc.
-
+
Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
Adrien Beraud <adrien.beraud@savoirfairelinux.com>
-
+
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/ring-android/app/src/main/java/cx/ring/client/AccountEditionActivity.java b/ring-android/app/src/main/java/cx/ring/client/AccountEditionActivity.java
index bf173da..ffbcdfc 100644
--- a/ring-android/app/src/main/java/cx/ring/client/AccountEditionActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/client/AccountEditionActivity.java
@@ -21,46 +21,69 @@
package cx.ring.client;
+import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.app.FragmentManager;
-import android.content.*;
+import android.app.ProgressDialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
import android.os.Bundle;
+import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteException;
+import android.support.annotation.NonNull;
+import android.support.design.widget.Snackbar;
import android.support.v13.app.FragmentPagerAdapter;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.util.Pair;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.widget.TextView;
+import android.widget.Toast;
import com.astuetz.PagerSlidingTabStrip;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Observable;
+import java.util.Observer;
+
import cx.ring.R;
import cx.ring.fragments.AdvancedAccountFragment;
+import cx.ring.fragments.GeneralAccountFragment;
import cx.ring.fragments.MediaPreferenceFragment;
import cx.ring.fragments.SecurityAccountFragment;
import cx.ring.model.account.Account;
import cx.ring.service.IDRingService;
import cx.ring.service.LocalService;
-import java.util.ArrayList;
-import java.util.Map;
-import java.util.Observable;
-import java.util.Observer;
-
-import cx.ring.fragments.GeneralAccountFragment;
public class AccountEditionActivity extends AppCompatActivity implements LocalService.Callbacks, GeneralAccountFragment.Callbacks, MediaPreferenceFragment.Callbacks,
AdvancedAccountFragment.Callbacks, SecurityAccountFragment.Callbacks {
private static final String TAG = AccountEditionActivity.class.getSimpleName();
public static final Uri CONTENT_URI = Uri.withAppendedPath(LocalService.AUTHORITY_URI, "accounts");
+ private static final int REQUEST_WRITE_STORAGE = 112;
private boolean mBound = false;
private LocalService service;
@@ -87,6 +110,9 @@
Log.i(TAG, "Service connected " + className.getClassName() + " " + getIntent().getData().toString());
acc_selected = service.getAccount(account_id);
+ if (acc_selected == null)
+ finish();
+
acc_selected.addObserver(mAccountObserver);
getSupportActionBar().setTitle(acc_selected.getAlias());
@@ -97,8 +123,7 @@
fragments.add(new Pair<String, Fragment>(getString(R.string.account_preferences_basic_tab), new GeneralAccountFragment()));
fragments.add(new Pair<String, Fragment>(getString(R.string.account_preferences_media_tab), new MediaPreferenceFragment()));
fragments.add(new Pair<String, Fragment>(getString(R.string.account_preferences_advanced_tab), new AdvancedAccountFragment()));
- if(acc_selected.isSip())
- {
+ if (acc_selected.isSip()) {
fragments.add(new Pair<String, Fragment>(getString(R.string.account_preferences_security_tab), new SecurityAccountFragment()));
}
}
@@ -154,15 +179,18 @@
@Override
public boolean onOptionsItemSelected(MenuItem item) {
-
+ AlertDialog dialog;
switch (item.getItemId()) {
- case android.R.id.home:
- finish();
- return true;
- case R.id.menuitem_delete:
- AlertDialog dialog = createDeleteDialog();
- dialog.show();
- break;
+ case android.R.id.home:
+ finish();
+ return true;
+ case R.id.menuitem_delete:
+ dialog = createDeleteDialog();
+ dialog.show();
+ break;
+ case R.id.menuitem_export:
+ startExport();
+ break;
}
return true;
@@ -176,7 +204,7 @@
private void processAccount() {
final Account acc = acc_selected;
final IDRingService remote = getRemoteService();
- getSupportActionBar().setTitle(acc.getAlias());;
+ getSupportActionBar().setTitle(acc.getAlias());
service.getThreadPool().submit(new Runnable() {
@Override
public void run() {
@@ -193,8 +221,8 @@
private AlertDialog createDeleteDialog() {
Activity ownerActivity = this;
AlertDialog.Builder builder = new AlertDialog.Builder(ownerActivity);
- builder.setMessage("Do you really want to delete this account").setTitle("Delete Account")
- .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
+ builder.setMessage(R.string.account_delete_confirmation).setTitle(R.string.account_delete_dialog_title)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
Bundle bundle = new Bundle();
@@ -207,12 +235,12 @@
}
finish();
}
- }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
+ }).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
/* Terminate with no action */
- }
- });
+ }
+ });
AlertDialog alertDialog = builder.create();
alertDialog.setOwnerActivity(ownerActivity);
@@ -220,6 +248,180 @@
return alertDialog;
}
+ boolean checkPassword(@NonNull TextView pwd, TextView confirm) {
+ boolean error = false;
+ if (pwd.getText().length() < 6) {
+ pwd.setError(getString(R.string.error_password_char_count));
+ error = true;
+ } else {
+ pwd.setError(null);
+ }
+ if (confirm != null) {
+ if (!pwd.getText().toString().equals(confirm.getText().toString())) {
+ confirm.setError(getString(R.string.error_passwords_not_equals));
+ error = true;
+ } else {
+ confirm.setError(null);
+ }
+ }
+ return error;
+ }
+
+ private void startExport()
+ {
+ boolean hasPermission = (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
+ if (hasPermission) {
+ showExportDialog();
+ } else {
+ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_STORAGE);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ switch (requestCode) {
+ case REQUEST_WRITE_STORAGE: {
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ showExportDialog();
+ } else {
+ Toast.makeText(this, R.string.permission_write_denied, Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+ }
+
+ private AlertDialog showExportDialog() {
+ Activity ownerActivity = this;
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(ownerActivity);
+ LayoutInflater inflater = getLayoutInflater();
+ ViewGroup v = (ViewGroup) inflater.inflate(R.layout.dialog_account_export, null);
+ final TextView pwd = (TextView) v.findViewById(R.id.newpwd_txt);
+ final TextView pwd_confirm = (TextView) v.findViewById(R.id.newpwd_confirm_txt);
+ builder.setMessage(R.string.account_export_message)
+ .setTitle(R.string.account_export_title)
+ .setPositiveButton(R.string.account_export, null)
+ .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ /* Terminate with no action */
+ }
+ }).setView(v);
+
+
+ final AlertDialog alertDialog = builder.create();
+ pwd.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ Log.w(TAG, "onEditorAction " + actionId + " " + (event == null ? null : event.toString()));
+ if (actionId == EditorInfo.IME_ACTION_NEXT)
+ return checkPassword(v, null);
+ return false;
+ }
+ });
+ pwd.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (!hasFocus) {
+ checkPassword((TextView) v, null);
+ }
+ else {
+ alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+ }
+ }
+ });
+ pwd_confirm.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ Log.w(TAG, "onEditorAction " + actionId + " " + (event == null ? null : event.toString()));
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ if (!checkPassword(pwd, v)) {
+ alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).callOnClick();
+ return true;
+ }
+ }
+ return false;
+ }
+ });
+
+
+ alertDialog.setOwnerActivity(ownerActivity);
+ alertDialog.show();
+ alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!checkPassword(pwd, pwd_confirm)) {
+ final String pwd_txt = pwd.getText().toString();
+ alertDialog.dismiss();
+ new ExportAccountTask().execute(pwd_txt);
+ }
+ }
+ });
+
+ return alertDialog;
+ }
+
+ public File getExportStorageDir() {
+ // Get the directory for the user's public pictures directory.
+ String env = Environment.DIRECTORY_DOWNLOADS;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
+ env = Environment.DIRECTORY_DOCUMENTS;
+
+ File path = Environment.getExternalStoragePublicDirectory(env);
+
+ if (!path.mkdirs() && !path.isDirectory()) {
+ Log.w(TAG, "Directory " + path.getAbsolutePath() + " not created, using fallback");
+ // Fallback case, use Downloads
+ env = Environment.DIRECTORY_DOWNLOADS;
+ path = Environment.getExternalStoragePublicDirectory(env);
+
+ if (!path.mkdirs() && !path.isDirectory())
+ Log.e(TAG, "Fallback on " + path.getAbsolutePath() + " failed!");
+ }
+
+ return new File(path, getAccount().getAlias() + ".ring");
+ }
+
+ private class ExportAccountTask extends AsyncTask<String, Void, Integer> {
+ private ProgressDialog loading_dialog = null;
+ private String path;
+
+ @Override
+ protected void onPreExecute() {
+ loading_dialog = ProgressDialog.show(AccountEditionActivity.this,
+ getString(R.string.export_dialog_title),
+ getString(R.string.import_export_wait), true);
+ loading_dialog.setCancelable(false);
+ }
+
+ protected Integer doInBackground(String... args) {
+ int ret = 1;
+ ArrayList<String> ids = new ArrayList<>(1);
+ ids.add(acc_selected.getAccountID());
+ File fpath = getExportStorageDir();
+ path = fpath.getAbsolutePath();
+ try {
+ ret = getRemoteService().exportAccounts(ids, path, args[0]);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ return ret;
+ }
+
+ protected void onPostExecute(Integer ret) {
+ if (loading_dialog != null)
+ loading_dialog.dismiss();
+ Log.d(TAG, "Account export to " + path + " returned " + ret);
+ if (ret == 0) {
+ Snackbar.make(findViewById(android.R.id.content), getString(R.string.account_export_result, path), Snackbar.LENGTH_INDEFINITE).show();
+ } else
+ new AlertDialog.Builder(AccountEditionActivity.this).setTitle(R.string.export_failed_dialog_title)
+ .setMessage(R.string.export_failed_dialog_msg)
+ .setPositiveButton(android.R.string.ok, null).show();
+ }
+ }
+
@Override
public IDRingService getRemoteService() {
return service.getRemoteService();
@@ -230,6 +432,16 @@
return service;
}
+ @Override
+ public void onStart() {
+ super.onStart();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ }
+
private static class PreferencesPagerAdapter extends FragmentPagerAdapter {
private final ArrayList<Pair<String, Fragment>> fragments;
diff --git a/ring-android/app/src/main/java/cx/ring/client/AccountWizard.java b/ring-android/app/src/main/java/cx/ring/client/AccountWizard.java
index 58ed1ef..91a624f 100644
--- a/ring-android/app/src/main/java/cx/ring/client/AccountWizard.java
+++ b/ring-android/app/src/main/java/cx/ring/client/AccountWizard.java
@@ -21,15 +21,15 @@
package cx.ring.client;
-import android.app.Fragment;
-import android.app.FragmentManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
-import android.support.v13.app.FragmentStatePagerAdapter;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentStatePagerAdapter;
+import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
@@ -71,7 +71,7 @@
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
- SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(AccountWizard.this, getFragmentManager());
+ SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(AccountWizard.this, getSupportFragmentManager());
mViewPager.setAdapter(mSectionsPagerAdapter);
if (!mBound) {
@@ -111,14 +111,13 @@
public SectionsPagerAdapter(Context c, FragmentManager fm) {
super(fm);
mContext = c;
- fragments = new ArrayList<Fragment>();
+ fragments = new ArrayList<>();
fragments.add(new AccountCreationFragment());
}
@Override
public Fragment getItem(int i) {
-
return fragments.get(i);
}
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/AccountCreationFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/AccountCreationFragment.java
index 4633b20..fc964be 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/AccountCreationFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/AccountCreationFragment.java
@@ -20,15 +20,31 @@
package cx.ring.fragments;
+import android.Manifest;
+import android.annotation.SuppressLint;
import android.app.Activity;
-import android.app.Fragment;
+import android.app.AlertDialog;
import android.app.ProgressDialog;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Bundle;
+import android.os.Environment;
import android.os.RemoteException;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -48,6 +64,9 @@
public class AccountCreationFragment extends Fragment {
static final String TAG = AccountCreationFragment.class.getSimpleName();
+ static private final int FILE_SELECT_CODE = 2;
+ private static final int REQUEST_READ_STORAGE = 113;
+
// Values for email and password at the time of the login attempt.
private String mAlias;
private String mHostname;
@@ -61,6 +80,8 @@
private EditText mUsernameView;
private EditText mPasswordView;
+ private String mDataPath;
+
private LocalService.Callbacks mCallbacks = LocalService.DUMMY_CALLBACKS;
private boolean creatingAccount = false;
@@ -108,10 +129,261 @@
attemptCreation();
}
});
+ inflatedView.findViewById(R.id.select_file_button).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startImport();
+ }
+ });
return inflatedView;
}
+
+ /**
+ * Get a file path from a Uri. This will get the the path for Storage Access
+ * Framework Documents, as well as the _data field for the MediaStore and
+ * other file-based ContentProviders.
+ *
+ * @param context The context.
+ * @param uri The Uri to query.
+ * @author paulburke
+ */
+ @SuppressLint("NewApi")
+ public static String getPath(final Context context, final Uri uri) {
+
+ final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+
+ // DocumentProvider
+ if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
+ // ExternalStorageProvider
+ if (isExternalStorageDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ if ("primary".equalsIgnoreCase(type)) {
+ return Environment.getExternalStorageDirectory() + "/" + split[1];
+ }
+
+ // TODO handle non-primary volumes
+ }
+ // DownloadsProvider
+ else if (isDownloadsDocument(uri)) {
+
+ final String id = DocumentsContract.getDocumentId(uri);
+ final Uri contentUri = ContentUris.withAppendedId(
+ Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
+
+ return getDataColumn(context, contentUri, null, null);
+ }
+ // MediaProvider
+ else if (isMediaDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ Uri contentUri = null;
+ if ("image".equals(type)) {
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ } else if ("video".equals(type)) {
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+ } else if ("audio".equals(type)) {
+ contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ }
+
+ final String selection = "_id=?";
+ final String[] selectionArgs = new String[]{
+ split[1]
+ };
+
+ return getDataColumn(context, contentUri, selection, selectionArgs);
+ }
+ }
+ // MediaStore (and general)
+ else if ("content".equalsIgnoreCase(uri.getScheme())) {
+ return getDataColumn(context, uri, null, null);
+ }
+ // File
+ else if ("file".equalsIgnoreCase(uri.getScheme())) {
+ return uri.getPath();
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the value of the data column for this Uri. This is useful for
+ * MediaStore Uris, and other file-based ContentProviders.
+ *
+ * @param context The context.
+ * @param uri The Uri to query.
+ * @param selection (Optional) Filter used in the query.
+ * @param selectionArgs (Optional) Selection arguments used in the query.
+ * @return The value of the _data column, which is typically a file path.
+ */
+ public static String getDataColumn(Context context, Uri uri, String selection,
+ String[] selectionArgs) {
+
+ Cursor cursor = null;
+ final String column = "_data";
+ final String[] projection = {
+ column
+ };
+
+ try {
+ cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
+ null);
+ if (cursor != null && cursor.moveToFirst()) {
+ final int column_index = cursor.getColumnIndexOrThrow(column);
+ return cursor.getString(column_index);
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Can't find data column", e);
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ return null;
+ }
+
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is ExternalStorageProvider.
+ */
+ public static boolean isExternalStorageDocument(Uri uri) {
+ return "com.android.externalstorage.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is DownloadsProvider.
+ */
+ public static boolean isDownloadsDocument(Uri uri) {
+ return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is MediaProvider.
+ */
+ public static boolean isMediaDocument(Uri uri) {
+ return "com.android.providers.media.documents".equals(uri.getAuthority());
+ }
+
+ private void presentFilePicker() {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("*/*");
+ startActivityForResult(intent, FILE_SELECT_CODE);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case FILE_SELECT_CODE:
+ if (resultCode == Activity.RESULT_OK) {
+ Log.w(TAG, "onActivityResult " + data.getDataString());
+ this.mDataPath = getPath(getActivity(), data.getData());
+ if (TextUtils.isEmpty(this.mDataPath))
+ Toast.makeText(getActivity(), "Can't read " + data.getData(), Toast.LENGTH_LONG).show();
+ else
+ showImportDialog();
+ }
+ break;
+ }
+ }
+
+ private AlertDialog showImportDialog() {
+ Activity ownerActivity = getActivity();
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(ownerActivity);
+ LayoutInflater inflater = ownerActivity.getLayoutInflater();
+ ViewGroup v = (ViewGroup) inflater.inflate(R.layout.dialog_account_import, null);
+ final TextView pwd = (TextView) v.findViewById(R.id.pwd_txt);
+ builder.setMessage(R.string.account_import_message)
+ .setTitle(R.string.account_import_account)
+ .setPositiveButton(R.string.account_import_account, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (!TextUtils.isEmpty(mDataPath)) {
+ new ImportAccountTask().execute(mDataPath, pwd.getText().toString());
+ }
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ /* Terminate with no action */
+ }
+ }).setView(v);
+
+ final AlertDialog alertDialog = builder.create();
+ alertDialog.show();
+ return alertDialog;
+ }
+
+ private void startImport() {
+ Activity activity = getActivity();
+ if (null != activity) {
+ boolean hasPermission = (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
+ if (hasPermission) {
+ presentFilePicker();
+ } else {
+ requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_READ_STORAGE);
+ }
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ switch (requestCode) {
+ case REQUEST_READ_STORAGE: {
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ presentFilePicker();
+ } else {
+ Activity activity = getActivity();
+ if (null != activity) {
+ Toast.makeText(activity, R.string.permission_read_denied, Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+ }
+ }
+
+ private class ImportAccountTask extends AsyncTask<String, Void, Integer> {
+ private ProgressDialog loading_dialog = null;
+
+ @Override
+ protected void onPreExecute() {
+ loading_dialog = ProgressDialog.show(getActivity(),
+ getActivity().getString(R.string.import_dialog_title),
+ getActivity().getString(R.string.import_export_wait), true);
+ }
+
+ protected Integer doInBackground(String... args) {
+ int ret = 1;
+ try {
+ ret = mCallbacks.getRemoteService().importAccounts(args[0], args[1]);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ return ret;
+ }
+
+ protected void onPostExecute(Integer ret) {
+ if (loading_dialog != null)
+ loading_dialog.dismiss();
+ if (ret == 0)
+ getActivity().finish();
+ else
+ new AlertDialog.Builder(getActivity()).setTitle(R.string.import_failed_dialog_title)
+ .setMessage(R.string.import_failed_dialog_msg)
+ .setPositiveButton(android.R.string.ok, null).show();
+ }
+ }
+
@Override
public void onResume() {
super.onResume();
@@ -120,7 +392,7 @@
@Override
public void onStart() {
super.onStart();
- ((AppCompatActivity)getActivity()).getSupportActionBar().setTitle(R.string.ab_account_creation);
+ ((AppCompatActivity) getActivity()).getSupportActionBar().setTitle(R.string.ab_account_creation);
}
@Override
@@ -188,9 +460,7 @@
@SuppressWarnings("unchecked")
private void initCreation() {
-
try {
-
HashMap<String, String> accountDetails = (HashMap<String, String>) mCallbacks.getRemoteService().getAccountTemplate(mAccountType);
accountDetails.put(AccountDetailBasic.CONFIG_ACCOUNT_TYPE, mAccountType);
accountDetails.put(AccountDetailBasic.CONFIG_VIDEO_ENABLED, "true");
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/AccountsManagementFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/AccountsManagementFragment.java
index ebbd68b..fde9527 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/AccountsManagementFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/AccountsManagementFragment.java
@@ -98,9 +98,7 @@
mAccountsAdapter = new AccountsAdapter(getActivity());
this.setHasOptionsMenu(true);
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(LocalService.ACTION_ACCOUNT_UPDATE);
- getActivity().registerReceiver(mReceiver, intentFilter);
+ getActivity().registerReceiver(mReceiver, new IntentFilter(LocalService.ACTION_ACCOUNT_UPDATE));
}
@Override
diff --git a/ring-android/app/src/main/java/cx/ring/service/DRingService.java b/ring-android/app/src/main/java/cx/ring/service/DRingService.java
index f4486a3..fc3420c 100644
--- a/ring-android/app/src/main/java/cx/ring/service/DRingService.java
+++ b/ring-android/app/src/main/java/cx/ring/service/DRingService.java
@@ -1426,5 +1426,25 @@
});
}
+ public int exportAccounts(final List accountIDs, final String toDir, final String password) {
+ return getExecutor().executeAndReturn(new SipRunnableWithReturn<Integer>() {
+ @Override
+ protected Integer doRun() throws SameThreadException, RemoteException {
+ StringVect ids = new StringVect();
+ for (Object s : accountIDs)
+ ids.add((String)s);
+ return Ringservice.exportAccounts(ids, toDir, password);
+ }
+ });
+ }
+
+ public int importAccounts(final String archivePath, final String password) {
+ return getExecutor().executeAndReturn(new SipRunnableWithReturn<Integer>() {
+ @Override
+ protected Integer doRun() throws SameThreadException, RemoteException {
+ return Ringservice.importAccounts(archivePath, password);
+ }
+ });
+ }
};
}
diff --git a/ring-android/app/src/main/java/cx/ring/service/IDRingService.aidl b/ring-android/app/src/main/java/cx/ring/service/IDRingService.aidl
index 20bf9d3..1093d78 100644
--- a/ring-android/app/src/main/java/cx/ring/service/IDRingService.aidl
+++ b/ring-android/app/src/main/java/cx/ring/service/IDRingService.aidl
@@ -108,4 +108,6 @@
Map getConference(in String id);
+ int exportAccounts(in List accountIDs, in String toDir, in String password);
+ int importAccounts(in String archivePath, in String password);
}
diff --git a/ring-android/app/src/main/jni/configurationmanager.i b/ring-android/app/src/main/jni/configurationmanager.i
index e6322b0..173c6cc 100644
--- a/ring-android/app/src/main/jni/configurationmanager.i
+++ b/ring-android/app/src/main/jni/configurationmanager.i
@@ -164,6 +164,9 @@
void sendTrustRequest(const std::string& accountId, const std::string& to, const std::vector<uint8_t>& payload);
+int exportAccounts(std::vector<std::string> accountIDs, std::string toDir, std::string password);
+int importAccounts(std::string archivePath, std::string password);
+
}
class ConfigurationCallback {
diff --git a/ring-android/app/src/main/res/drawable-hdpi/ic_archive_black_48dp.png b/ring-android/app/src/main/res/drawable-hdpi/ic_archive_black_48dp.png
new file mode 100644
index 0000000..d6d60f6
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-hdpi/ic_archive_black_48dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-hdpi/ic_archive_white_24dp.png b/ring-android/app/src/main/res/drawable-hdpi/ic_archive_white_24dp.png
new file mode 100644
index 0000000..bb72e89
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-hdpi/ic_archive_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-hdpi/ic_unarchive_white_24dp.png b/ring-android/app/src/main/res/drawable-hdpi/ic_unarchive_white_24dp.png
new file mode 100644
index 0000000..18730f1
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-hdpi/ic_unarchive_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-mdpi/ic_archive_black_48dp.png b/ring-android/app/src/main/res/drawable-mdpi/ic_archive_black_48dp.png
new file mode 100644
index 0000000..9b88218
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-mdpi/ic_archive_black_48dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-mdpi/ic_archive_white_24dp.png b/ring-android/app/src/main/res/drawable-mdpi/ic_archive_white_24dp.png
new file mode 100644
index 0000000..f6aa3f9
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-mdpi/ic_archive_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-mdpi/ic_unarchive_white_24dp.png b/ring-android/app/src/main/res/drawable-mdpi/ic_unarchive_white_24dp.png
new file mode 100644
index 0000000..8ec62cd
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-mdpi/ic_unarchive_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-xhdpi/ic_archive_black_48dp.png b/ring-android/app/src/main/res/drawable-xhdpi/ic_archive_black_48dp.png
new file mode 100644
index 0000000..b8c0376
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-xhdpi/ic_archive_black_48dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-xhdpi/ic_archive_white_24dp.png b/ring-android/app/src/main/res/drawable-xhdpi/ic_archive_white_24dp.png
new file mode 100644
index 0000000..3513bd9
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-xhdpi/ic_archive_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-xhdpi/ic_unarchive_white_24dp.png b/ring-android/app/src/main/res/drawable-xhdpi/ic_unarchive_white_24dp.png
new file mode 100644
index 0000000..a0a1509
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-xhdpi/ic_unarchive_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/ic_archive_black_48dp.png b/ring-android/app/src/main/res/drawable-xxhdpi/ic_archive_black_48dp.png
new file mode 100644
index 0000000..6c956e6
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-xxhdpi/ic_archive_black_48dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/ic_archive_white_24dp.png b/ring-android/app/src/main/res/drawable-xxhdpi/ic_archive_white_24dp.png
new file mode 100644
index 0000000..00e04e4
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-xxhdpi/ic_archive_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/ic_unarchive_white_24dp.png b/ring-android/app/src/main/res/drawable-xxhdpi/ic_unarchive_white_24dp.png
new file mode 100644
index 0000000..20d0157
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-xxhdpi/ic_unarchive_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-xxxhdpi/ic_archive_black_48dp.png b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_archive_black_48dp.png
new file mode 100644
index 0000000..abdadd0
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_archive_black_48dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-xxxhdpi/ic_archive_white_24dp.png b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_archive_white_24dp.png
new file mode 100644
index 0000000..34cd3fd
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_archive_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/drawable-xxxhdpi/ic_unarchive_white_24dp.png b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_unarchive_white_24dp.png
new file mode 100644
index 0000000..a789520
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_unarchive_white_24dp.png
Binary files differ
diff --git a/ring-android/app/src/main/res/layout/dialog_account_export.xml b/ring-android/app/src/main/res/layout/dialog_account_export.xml
new file mode 100644
index 0000000..e5eb17b
--- /dev/null
+++ b/ring-android/app/src/main/res/layout/dialog_account_export.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <EditText
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword"
+ android:ems="10"
+ android:id="@+id/newpwd_txt"
+ android:layout_gravity="center_horizontal"
+ android:hint="@string/account_export_new_password"
+ android:layout_marginLeft="20dp"
+ android:layout_marginRight="20dp"
+ android:layout_marginTop="8dp" />
+
+ <EditText
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword"
+ android:ems="10"
+ android:id="@+id/newpwd_confirm_txt"
+ android:hint="@string/account_export_confirm_password"
+ android:layout_marginLeft="20dp"
+ android:layout_marginRight="20dp"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="8dp" />
+</LinearLayout>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/layout/dialog_account_import.xml b/ring-android/app/src/main/res/layout/dialog_account_import.xml
new file mode 100644
index 0000000..eb93f08
--- /dev/null
+++ b/ring-android/app/src/main/res/layout/dialog_account_import.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <EditText
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword"
+ android:ems="10"
+ android:id="@+id/pwd_txt"
+ android:layout_gravity="center_horizontal"
+ android:hint="@string/account_import_decryption_password"
+ android:layout_marginLeft="20dp"
+ android:layout_marginRight="20dp"
+ android:layout_marginTop="8dp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/layout/frag_account_creation.xml b/ring-android/app/src/main/res/layout/frag_account_creation.xml
index 96f3f13..9e74f05 100644
--- a/ring-android/app/src/main/res/layout/frag_account_creation.xml
+++ b/ring-android/app/src/main/res/layout/frag_account_creation.xml
@@ -53,12 +53,14 @@
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_margin="16dp"
+ android:contentDescription="@string/app_name"
android:src="@drawable/ring_logo_48dp" />
<TextView
android:id="@+id/ring_acc_title_txt"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@+id/imageView6"
@@ -76,6 +78,7 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
+ android:layout_alignParentLeft="true"
android:layout_below="@+id/ring_acc_title_txt"
android:padding="16dp"
android:singleLine="false"
@@ -90,10 +93,13 @@
android:layout_height="wrap_content"
android:layout_alignParentEnd="false"
android:layout_alignParentStart="false"
+ android:layout_alignParentRight="false"
+ android:layout_alignParentLeft="false"
android:layout_below="@+id/textView"
android:layout_margin="8dp"
android:padding="8dp"
- android:text="@string/create_ring_account" />
+ android:text="@string/create_ring_account"
+ android:gravity="center" />
</RelativeLayout>
</android.support.v7.widget.CardView>
@@ -134,24 +140,27 @@
android:id="@+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/textView3"
- android:padding="16dp"
android:singleLine="false"
android:text="@string/help_sip"
android:textColor="@color/text_color_primary"
- android:textSize="14sp" />
+ android:textSize="14sp"
+ android:padding="16dp" />
<EditText
android:id="@+id/alias"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_below="@+id/textView4"
android:layout_marginBottom="8dp"
- android:layout_marginLeft="16dp"
- android:layout_marginRight="16dp"
+ android:layout_marginLeft="12dp"
+ android:layout_marginRight="12dp"
android:hint="@string/prompt_alias"
android:singleLine="true" />
@@ -159,11 +168,12 @@
android:id="@+id/hostname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_below="@+id/alias"
android:layout_marginBottom="8dp"
- android:layout_marginLeft="16dp"
- android:layout_marginRight="16dp"
+ android:layout_marginLeft="12dp"
+ android:layout_marginRight="12dp"
android:hint="@string/prompt_hostname"
android:singleLine="true"
android:typeface="monospace" />
@@ -176,8 +186,8 @@
android:layout_alignParentStart="true"
android:layout_below="@+id/hostname"
android:layout_marginBottom="8dp"
- android:layout_marginLeft="16dp"
- android:layout_marginRight="16dp"
+ android:layout_marginLeft="12dp"
+ android:layout_marginRight="12dp"
android:hint="@string/prompt_username"
android:singleLine="true"
android:typeface="monospace" />
@@ -190,8 +200,8 @@
android:layout_alignParentStart="true"
android:layout_below="@+id/username"
android:layout_marginBottom="8dp"
- android:layout_marginLeft="16dp"
- android:layout_marginRight="16dp"
+ android:layout_marginLeft="12dp"
+ android:layout_marginRight="12dp"
android:hint="@string/prompt_password"
android:imeActionLabel="@string/action_create_short"
android:inputType="textPassword"
@@ -209,7 +219,8 @@
android:layout_below="@+id/password"
android:layout_margin="8dp"
android:padding="8dp"
- android:text="@string/create_sip_account" />
+ android:text="@string/create_sip_account"
+ android:gravity="center" />
<ImageView
android:id="@+id/imageView5"
@@ -219,11 +230,65 @@
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_margin="16dp"
+ android:contentDescription="@string/help_sip_title"
android:src="@drawable/ic_dialer_sip_black_48dp" />
</RelativeLayout>
</android.support.v7.widget.CardView>
+ <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/card_import"
+ android:layout_width="280dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_margin="8dp"
+ card_view:cardCornerRadius="2dp">
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:nestedScrollingEnabled="false"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="false"
+ android:layout_alignParentTop="true"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:paddingTop="24dp"
+ android:singleLine="false"
+ android:text="@string/account_import_title"
+ android:textColor="@color/text_color_primary"
+ android:textSize="24sp"
+ android:id="@+id/import_acc_title_txt" />
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentTop="true"
+ android:layout_margin="16dp"
+ android:src="@drawable/ic_archive_black_48dp"
+ android:contentDescription="@string/account_import_title"
+ android:id="@+id/imageView4" />
+
+ <Button
+ android:id="@+id/select_file_button"
+ style="@style/Widget.AppCompat.Button.Borderless.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/import_acc_title_txt"
+ android:layout_margin="8dp"
+ android:padding="8dp"
+ android:text="@string/account_import"
+ android:gravity="center" />
+
+ </RelativeLayout>
+
+ </android.support.v7.widget.CardView>
</LinearLayout>
diff --git a/ring-android/app/src/main/res/layout/frag_accounts_list.xml b/ring-android/app/src/main/res/layout/frag_accounts_list.xml
index 3de8015..6cd6910 100644
--- a/ring-android/app/src/main/res/layout/frag_accounts_list.xml
+++ b/ring-android/app/src/main/res/layout/frag_accounts_list.xml
@@ -10,23 +10,23 @@
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
- android:paddingStart="72dp"
android:paddingEnd="72dp"
+ android:paddingStart="72dp"
android:text="@string/normal_accounts_titles" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:background="@color/white"
+ android:elevation="2dp"
android:minHeight="72dp">
<cx.ring.views.dragsortlv.DragSortListView
android:id="@+id/accounts_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@color/white"
android:choiceMode="multipleChoice"
android:dividerHeight="1px"
- android:elevation="2dp"
android:visibility="visible"
dslv:collapsed_height="1px"
dslv:drag_enabled="true"
@@ -52,11 +52,4 @@
</RelativeLayout>
- <ListView
- android:id="@+id/ip2ip"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@color/white"
- android:elevation="2dp" />
-
</LinearLayout>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/menu/account_edition.xml b/ring-android/app/src/main/res/menu/account_edition.xml
index 0454d01..8d7965a 100644
--- a/ring-android/app/src/main/res/menu/account_edition.xml
+++ b/ring-android/app/src/main/res/menu/account_edition.xml
@@ -8,4 +8,10 @@
android:title="@string/ab_account_edition_1"
app:showAsAction="ifRoom"/>
+ <item
+ android:id="@+id/menuitem_export"
+ android:icon="@drawable/ic_archive_white_24dp"
+ android:title="@string/account_export_title"
+ app:showAsAction="ifRoom"/>
+
</menu>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/values/strings.xml b/ring-android/app/src/main/res/values/strings.xml
index ba55dbc..3a88484 100644
--- a/ring-android/app/src/main/res/values/strings.xml
+++ b/ring-android/app/src/main/res/values/strings.xml
@@ -134,4 +134,7 @@
<string name="scan_qr">Scan QR Code</string>
<string name="ab_action_flipcamera">Flip camera</string>
+ <string name="permission_read_denied">The app was not allowed to read to your storage. Hence, it cannot function properly. Please consider granting it this permission</string>
+ <string name="permission_write_denied">The app was not allowed to write to your storage. Hence, it cannot function properly. Please consider granting it this permission</string>
+
</resources>
diff --git a/ring-android/app/src/main/res/values/strings_account.xml b/ring-android/app/src/main/res/values/strings_account.xml
index 5d22f41..46f0af3 100644
--- a/ring-android/app/src/main/res/values/strings_account.xml
+++ b/ring-android/app/src/main/res/values/strings_account.xml
@@ -20,7 +20,7 @@
<resources>
- <!-- Strings related to login -->
+ <!-- Strings related to account creation -->
<string name="prompt_alias">Alias</string>
<string name="prompt_hostname">Hostname</string>
<string name="prompt_username">Username</string>
@@ -31,7 +31,6 @@
<string name="dialog_wait_create">Adding account</string>
<string name="dialog_wait_create_details">Please wait while your new account is added…</string>
- <!-- Strings related to account creation -->
<string-array name="accountType">
<item>RING</item>
<item>SIP</item>
@@ -47,6 +46,10 @@
<string name="configure_sip_account">Configure</string>
<string name="share_number">Share number</string>
+ <!-- Strings related to account deletion -->
+ <string name="account_delete_confirmation">Do you really want to delete this account</string>
+ <string name="account_delete_dialog_title">Delete Account</string>
+
<!-- AccountManagementFragment -->
<string name="preference_section1">Accounts</string>
<string name="empty_account_list">No account registered</string>
@@ -142,4 +145,26 @@
<string name="account_rtp_min_port">Minimum</string>
<string name="account_rtp_port_range">Audio RTP Port Range</string>
+ <!-- Import/export -->
+ <string name="account_export">Export</string>
+ <string name="account_import">Import</string>
+ <string name="account_import_account">Import account</string>
+ <string name="account_export_title">Export account</string>
+ <string name="account_import_title">Import accounts</string>
+ <string name="account_export_message">Save account settings and credentials, password encrypted.</string>
+ <string name="account_export_new_password">New password</string>
+ <string name="account_export_confirm_password">Confirm password</string>
+ <string name="account_import_message">Enter password to decrypt the file.</string>
+ <string name="account_import_decryption_password">Decryption password</string>
+ <string name="import_dialog_title">Importing account</string>
+ <string name="import_export_wait">Please wait…</string>
+ <string name="export_dialog_title">Exporting account</string>
+ <string name="account_export_result">"Account exported to %1$s"</string>
+ <string name="export_failed_dialog_title">Export failed</string>
+ <string name="export_failed_dialog_msg">An error occurred</string>
+ <string name="error_password_char_count">6 characters minimum</string>
+ <string name="error_passwords_not_equals">Passwords do not match</string>
+ <string name="import_failed_dialog_title">Import failed</string>
+ <string name="import_failed_dialog_msg">An error occured</string>
+
</resources>