Skip to content
Snippets Groups Projects
Commit d288e197 authored by Sébastien Blin's avatar Sébastien Blin Committed by Adrien Béraud
Browse files

accountsummary: add a way to export account's archive

Change-Id: Iddf9e80956bfd695b1adfdb62c432085ddb8d0f9
Gitlab: #507
parent 83095070
Branches
Tags
No related merge requests found
Showing
with 281 additions and 33 deletions
/*
* Copyright (C) 2004-2019 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cx.ring.account;
import android.app.Dialog;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.google.android.material.textfield.TextInputLayout;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import butterknife.BindString;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnEditorAction;
import cx.ring.R;
public class BackupAccountDialog extends DialogFragment {
static final String TAG = BackupAccountDialog.class.getSimpleName();
@BindView(R.id.password_txt_box)
protected TextInputLayout mPasswordTxtBox;
@BindView(R.id.password_txt)
protected EditText mPasswordTxt;
@BindString(R.string.enter_password)
protected String mPromptPassword;
private String mAccountId;
private UnlockAccountListener mListener = null;
public void setListener(UnlockAccountListener listener) {
mListener = listener;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View view = requireActivity().getLayoutInflater().inflate(R.layout.dialog_confirm_revocation, null);
ButterKnife.bind(this, view);
Bundle args = getArguments();
if (args != null) {
mAccountId = args.getString(AccountEditionActivity.ACCOUNT_ID_KEY);
}
final AlertDialog result = new AlertDialog.Builder(requireContext())
.setTitle(R.string.account_enter_password)
.setMessage(R.string.account_new_device_password)
.setView(view)
.setPositiveButton(android.R.string.ok, null) //Set to null. We override the onclick
.setNegativeButton(android.R.string.cancel,
(dialog, whichButton) -> dismiss()
)
.create();
result.setOnShowListener(dialog -> {
Button positiveButton = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(view1 -> {
if (validate()) {
dismiss();
}
});
});
result.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
return result;
}
private boolean validate() {
if (mListener != null) {
mListener.onUnlockAccount(mAccountId, mPasswordTxt.getText().toString());
return true;
}
return false;
}
@OnEditorAction({R.id.password_txt})
public boolean onEditorAction(TextView v, int actionId) {
if (v == mPasswordTxt) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
boolean validationResult = validate();
if (validationResult) {
getDialog().dismiss();
}
return validationResult;
}
}
return false;
}
public interface UnlockAccountListener {
void onUnlockAccount(String accountId, String password);
}
}
......@@ -42,9 +42,9 @@ import cx.ring.R;
public class ConfirmRevocationDialog extends DialogFragment {
public static final String DEVICEID_KEY = "deviceid_key";
static final String TAG = ConfirmRevocationDialog.class.getSimpleName();
@BindView(R.id.ring_password_txt_box)
@BindView(R.id.password_txt_box)
protected TextInputLayout mPasswordTxtBox;
@BindView(R.id.ring_password_txt)
@BindView(R.id.password_txt)
protected EditText mPasswordTxt;
@BindString(R.string.enter_password)
protected String mPromptPassword;
......@@ -107,7 +107,7 @@ public class ConfirmRevocationDialog extends DialogFragment {
return false;
}
@OnEditorAction({R.id.ring_password_txt})
@OnEditorAction({R.id.password_txt})
public boolean onEditorAction(TextView v, int actionId) {
if (v == mPasswordTxt) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
......
......@@ -55,9 +55,9 @@ public class RegisterNameDialog extends DialogFragment {
public TextInputLayout mUsernameTxtBox;
@BindView(R.id.ring_username)
public EditText mUsernameTxt;
@BindView(R.id.ring_password_txt_box)
@BindView(R.id.password_txt_box)
public TextInputLayout mPasswordTxtBox;
@BindView(R.id.ring_password_txt)
@BindView(R.id.password_txt)
public EditText mPasswordTxt;
@BindString(R.string.register_name)
public String mRegisterTitle;
......@@ -239,7 +239,7 @@ public class RegisterNameDialog extends DialogFragment {
return false;
}
@OnEditorAction({R.id.ring_username, R.id.ring_password_txt})
@OnEditorAction({R.id.ring_username, R.id.password_txt})
public boolean onEditorAction(TextView v, int actionId) {
if (v == mPasswordTxt) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
......
......@@ -63,7 +63,7 @@ public class RingAccountCreationFragment extends BaseSupportFragment<RingAccount
@BindView(R.id.ring_password_box)
protected ViewGroup mPasswordBox;
@BindView(R.id.ring_password_txt_box)
@BindView(R.id.password_txt_box)
protected TextInputLayout mPasswordTxtBox;
@BindView(R.id.ring_password_repeat_txt_box)
......
......@@ -20,7 +20,9 @@
*/
package cx.ring.account;
import android.app.DownloadManager;
import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Typeface;
import android.os.Bundle;
......@@ -31,6 +33,7 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SwitchCompat;
import android.os.Environment;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableString;
......@@ -47,8 +50,11 @@ import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import java.io.File;
import java.util.Map;
import javax.inject.Inject;
import butterknife.BindView;
import butterknife.OnClick;
import butterknife.OnEditorAction;
......@@ -57,6 +63,8 @@ import cx.ring.dependencyinjection.RingInjectionComponent;
import cx.ring.interfaces.BackHandlerInterface;
import cx.ring.model.Account;
import cx.ring.mvp.BaseSupportFragment;
import cx.ring.services.AccountService;
import cx.ring.utils.AndroidFileUtils;
import cx.ring.utils.KeyboardVisibilityManager;
import cx.ring.views.LinkNewDeviceLayout;
......@@ -65,12 +73,13 @@ public class RingAccountSummaryFragment extends BaseSupportFragment<RingAccountS
RenameDeviceDialog.RenameDeviceListener,
DeviceAdapter.DeviceRevocationListener,
ConfirmRevocationDialog.ConfirmRevocationListener,
RingAccountSummaryView, ChangePasswordDialog.PasswordChangedListener {
RingAccountSummaryView, ChangePasswordDialog.PasswordChangedListener, BackupAccountDialog.UnlockAccountListener {
public static final String TAG = RingAccountSummaryFragment.class.getSimpleName();
private static final String FRAGMENT_DIALOG_REVOCATION = TAG + ".dialog.deviceRevocation";
private static final String FRAGMENT_DIALOG_RENAME = TAG + ".dialog.deviceRename";
private static final String FRAGMENT_DIALOG_PASSWORD = TAG + ".dialog.changePassword";
private static final String FRAGMENT_DIALOG_BACKUP = TAG + ".dialog.backup";
/*
UI Bindings
......@@ -87,6 +96,9 @@ public class RingAccountSummaryFragment extends BaseSupportFragment<RingAccountS
@BindView(R.id.btn_start_export)
Button mStartBtn;
//@BindView(R.id.export_account_btn)
//Button mExportToFile;
@BindView(R.id.account_link_info)
TextView mExportInfos;
......@@ -132,6 +144,12 @@ public class RingAccountSummaryFragment extends BaseSupportFragment<RingAccountS
private DeviceAdapter mDeviceAdapter;
private ProgressDialog mWaitDialog;
private boolean mAccountHasPassword = true;
private String mBestName = "";
private String mAccountId = "";
@Inject
AccountService mAccountService;
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
......@@ -167,12 +185,21 @@ public class RingAccountSummaryFragment extends BaseSupportFragment<RingAccountS
par.height = totalHeight + (mDeviceList.getDividerHeight() * (mDeviceAdapter.getCount() - 1));
mDeviceList.setLayoutParams(par);
mDeviceList.requestLayout();
mAccountHasPassword = account.hasPassword();
mChangePasswordBtn.setText(account.hasPassword() ? R.string.account_password_change : R.string.account_password_set);
mChangePasswordBtn.setText(mAccountHasPassword ? R.string.account_password_change : R.string.account_password_set);
mAccountSwitch.setChecked(account.isEnabled());
mAccountNameTxt.setText(account.getAlias());
mAccountIdTxt.setText(account.getUsername());
mAccountId = account.getAccountID();
mBestName = account.getAlias();
if (mBestName.isEmpty()) {
mBestName = account.getRegisteredName();
if (mBestName.isEmpty()) {
mBestName = account.getUsername();
}
}
String username = account.getRegisteredName();
boolean currentRegisteredName = account.registeringUsername;
boolean hasRegisteredName = !currentRegisteredName && username != null && !username.isEmpty();
......@@ -208,7 +235,6 @@ public class RingAccountSummaryFragment extends BaseSupportFragment<RingAccountS
mAccountStatus.setText(status);
mAccountStatus.setChipBackgroundColorResource(color);
mAccountHasPassword = account.hasPassword();
mPasswordLayout.setVisibility(mAccountHasPassword ? View.VISIBLE : View.GONE);
}
......@@ -307,6 +333,15 @@ public class RingAccountSummaryFragment extends BaseSupportFragment<RingAccountS
presenter.startAccountExport(password);
}
@OnClick(R.id.export_account_btn)
void onClickExport() {
if (mAccountHasPassword) {
onBackupAccount();
} else {
onUnlockAccount(mAccountId, "");
}
}
@OnClick(R.id.account_switch)
void onToggleAccount() {
presenter.enableAccount(mAccountSwitch.isChecked());
......@@ -437,11 +472,35 @@ public class RingAccountSummaryFragment extends BaseSupportFragment<RingAccountS
.show();
}
@Override
public void displayCompleteArchive(File dest) {
DownloadManager downloadManager = (DownloadManager) getActivity().getSystemService(Context.DOWNLOAD_SERVICE);
if (downloadManager != null) {
downloadManager.addCompletedDownload(dest.getName(),
dest.getName(),
true,
AndroidFileUtils.getMimeType(dest.getAbsolutePath()),
dest.getAbsolutePath(),
dest.length(),
true);
}
}
@Override
public void onConfirmRevocation(String deviceId, String password) {
presenter.revokeDevice(deviceId, password);
}
public void onBackupAccount() {
BackupAccountDialog dialog = new BackupAccountDialog();
Bundle args = new Bundle();
args.putString(AccountEditionActivity.ACCOUNT_ID_KEY, getArguments().getString(AccountEditionActivity.ACCOUNT_ID_KEY));
dialog.setArguments(args);
dialog.setListener(this);
dialog.show(requireFragmentManager(), FRAGMENT_DIALOG_BACKUP);
}
@Override
public void onDeviceRevocationAsked(String deviceId) {
ConfirmRevocationDialog dialog = new ConfirmRevocationDialog();
......@@ -484,4 +543,14 @@ public class RingAccountSummaryFragment extends BaseSupportFragment<RingAccountS
public void onPasswordChanged(String oldPassword, String newPassword) {
presenter.changePassword(oldPassword, newPassword);
}
@Override
public void onUnlockAccount(String accountId, String password) {
File downloadDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "Ring");
downloadDir.mkdirs();
File dest = new File(downloadDir, mBestName + ".gz");
if (dest.exists())
dest.delete();
presenter.downloadAccountsArchive(dest, password);
}
}
......@@ -19,7 +19,9 @@
package cx.ring.tv.account;
import android.app.AlertDialog;
import android.app.DownloadManager;
import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
......@@ -36,6 +38,7 @@ import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import android.view.View;
import java.io.File;
import java.util.List;
import java.util.Map;
......@@ -44,6 +47,7 @@ import cx.ring.account.RingAccountSummaryPresenter;
import cx.ring.account.RingAccountSummaryView;
import cx.ring.application.RingApplication;
import cx.ring.model.Account;
import cx.ring.utils.AndroidFileUtils;
public class TVAccountExport
extends RingGuidedStepFragment<RingAccountSummaryPresenter>
......@@ -175,4 +179,17 @@ public class TVAccountExport
public void passwordChangeEnded(boolean ok) {
}
public void displayCompleteArchive(File dest) {
DownloadManager downloadManager = (DownloadManager) getActivity().getSystemService(Context.DOWNLOAD_SERVICE);
if (downloadManager != null) {
downloadManager.addCompletedDownload(dest.getName(),
dest.getName(),
true,
AndroidFileUtils.getMimeType(dest.getAbsolutePath()),
dest.getAbsolutePath(),
dest.length(),
true);
}
}
}
......@@ -6,7 +6,7 @@
android:paddingTop="16dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/ring_password_txt_box"
android:id="@+id/password_txt_box"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
......@@ -14,7 +14,7 @@
android:layout_marginRight="12dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/ring_password_txt"
android:id="@+id/password_txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/account_revoke_device_hint"
......
......@@ -102,7 +102,7 @@
android:textColor="@color/text_color_primary" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/ring_password_txt_box"
android:id="@+id/password_txt_box"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:passwordToggleEnabled="true">
......
......@@ -46,7 +46,7 @@
android:textSize="14sp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/ring_password_txt_box"
android:id="@+id/password_txt_box"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:passwordToggleEnabled="true">
......
......@@ -35,10 +35,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:id="@+id/ring_account_status_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:paddingTop="24dp">
android:paddingTop="24dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/account_alias_txt"
......@@ -78,7 +78,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginVertical="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:background="?android:attr/listDivider" />
<TextView
......@@ -93,25 +94,42 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:layout_height="wrap_content"
android:ellipsize="middle"
android:singleLine="true"
android:textIsSelectable="true"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle2"
android:textIsSelectable="true"
tools:text="ring:8F29045378ACA68F2ACA2346078ACA68F2ACA290" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/export_account_btn"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/account_export_file" />
<com.google.android.material.button.MaterialButton
android:id="@+id/change_password_btn"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/account_password_change" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginVertical="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:background="?android:attr/listDivider" />
<TextView
......@@ -171,11 +189,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<TextView
android:id="@+id/registered_name_txt"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle2"
android:textIsSelectable="true"
tools:text="blockchain_name" />
......@@ -185,7 +203,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginVertical="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:background="?android:attr/listDivider" />
<TextView
......@@ -206,7 +225,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:divider="#DDDDDD"
android:dividerHeight="1dp"
android:footerDividersEnabled="true"
android:headerDividersEnabled="true" />
android:headerDividersEnabled="true"
android:nestedScrollingEnabled="true" />
<TextView
android:id="@+id/empty_account_list"
......
......@@ -25,7 +25,7 @@
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/ring_password_txt_box"
android:id="@+id/password_txt_box"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
......@@ -33,7 +33,7 @@
android:layout_marginRight="12dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/ring_password_txt"
android:id="@+id/password_txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/account_new_device_password"
......
......@@ -154,11 +154,11 @@ along with this program; if not, write to the Free Software
<!-- restore/backup -->
<string name="account_new_device_password">Enter main password to unlock account</string>
<string name="error_password_char_count">6 characters minimum</string>
<string name="error_passwords_not_equals">Passwords do not match</string>
<string name="export_account_wait_title">Please wait…</string>
<string name="export_account_wait_message">Publishing new account information</string>
<string name="account_export_file">Backup account</string>
<string name="account_export_end_decryption_message">Couldn\'t unlock your account using the provided password.</string>
<string name="account_export_end_network_title">Network error</string>
<string name="account_export_end_network_message">Couldn\'t export account on the network. Check your connectivity.</string>
......
......@@ -20,6 +20,8 @@
package cx.ring.account;
import java.io.File;
import java.io.IOException;
import java.net.SocketException;
import javax.inject.Inject;
......@@ -28,8 +30,11 @@ import javax.inject.Named;
import cx.ring.model.Account;
import cx.ring.mvp.RootPresenter;
import cx.ring.services.AccountService;
import cx.ring.utils.FileUtils;
import cx.ring.utils.Log;
import io.reactivex.Scheduler;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
public class RingAccountSummaryPresenter extends RootPresenter<RingAccountSummaryView> {
......@@ -137,4 +142,12 @@ public class RingAccountSummaryPresenter extends RootPresenter<RingAccountSummar
}
return account.getDeviceName();
}
public void downloadAccountsArchive(File dest, String password) {
mCompositeDisposable.add(
mAccountService.exportToFile(mAccountID, dest.getAbsolutePath(), password)
.observeOn(mUiScheduler)
.subscribe(() -> getView().displayCompleteArchive(dest),
error -> getView().passwordChangeEnded(false)));
}
}
......@@ -19,6 +19,7 @@
package cx.ring.account;
import java.io.File;
import java.util.Map;
import cx.ring.model.Account;
......@@ -46,4 +47,5 @@ public interface RingAccountSummaryView {
void deviceRevocationEnded(String device, int status);
void passwordChangeEnded(boolean ok);
void displayCompleteArchive(File dest);
}
......@@ -672,6 +672,13 @@ public class AccountService {
});
}
public Completable exportToFile(String accountId, String absolutePath, String password) {
return Completable.fromAction(() -> {
if (!Ringservice.exportToFile(accountId, absolutePath, password))
throw new IllegalArgumentException("Can't export archive");
}).subscribeOn(Schedulers.from(mExecutor));
}
/**
* @param accountId id of the account
* @param oldPassword old account password
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment