diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 090667d24..dfc7007f7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -570,6 +570,10 @@ android:name=".etesync.EteSyncCalendarSettingsActivity" android:theme="@style/Tasks" /> + + > { +public class AddEteSyncAccountViewModel extends CompletableViewModel> { void addAccount( PlayServices playServices, Context context, EteSyncClient client, String url, String username, - String password, - String encryptionPassword) { + String password) { run( () -> { playServices.updateSecurityProvider(context); - return client - .setForeground() - .forUrl(url, username, encryptionPassword, null) - .getKeyAndToken(password); + return client.setForeground().forUrl(url, username, null, null).getInfoAndToken(password); }); } } diff --git a/app/src/main/java/org/tasks/etesync/CreateUserInfoViewModel.java b/app/src/main/java/org/tasks/etesync/CreateUserInfoViewModel.java new file mode 100644 index 000000000..ea9dd89b3 --- /dev/null +++ b/app/src/main/java/org/tasks/etesync/CreateUserInfoViewModel.java @@ -0,0 +1,14 @@ +package org.tasks.etesync; + +import org.tasks.data.CaldavAccount; +import org.tasks.ui.CompletableViewModel; + +public class CreateUserInfoViewModel extends CompletableViewModel { + + void createUserInfo(EteSyncClient client, CaldavAccount caldavAccount, String derivedKey) { + run(() -> { + client.forAccount(caldavAccount).createUserInfo(derivedKey); + return derivedKey; + }); + } +} diff --git a/app/src/main/java/org/tasks/etesync/EncryptionSettingsActivity.java b/app/src/main/java/org/tasks/etesync/EncryptionSettingsActivity.java new file mode 100644 index 000000000..620924ff2 --- /dev/null +++ b/app/src/main/java/org/tasks/etesync/EncryptionSettingsActivity.java @@ -0,0 +1,224 @@ +package org.tasks.etesync; + +import static android.text.TextUtils.isEmpty; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import androidx.appcompat.widget.Toolbar; +import androidx.appcompat.widget.Toolbar.OnMenuItemClickListener; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProviders; +import at.bitfire.dav4jvm.exception.HttpException; +import butterknife.ButterKnife; +import butterknife.OnTextChanged; +import com.etesync.journalmanager.Constants; +import com.etesync.journalmanager.Crypto; +import com.etesync.journalmanager.Crypto.CryptoManager; +import com.etesync.journalmanager.Exceptions.IntegrityException; +import com.etesync.journalmanager.Exceptions.VersionTooNewException; +import com.etesync.journalmanager.GsonHelper; +import com.etesync.journalmanager.UserInfoManager.UserInfo; +import com.google.android.material.snackbar.Snackbar; +import java.net.ConnectException; +import javax.inject.Inject; +import org.tasks.R; +import org.tasks.data.CaldavAccount; +import org.tasks.databinding.ActivityEtesyncEncryptionSettingsBinding; +import org.tasks.injection.ActivityComponent; +import org.tasks.injection.ThemedInjectingAppCompatActivity; +import org.tasks.security.Encryption; +import org.tasks.ui.DisplayableException; +import org.tasks.ui.MenuColorizer; +import timber.log.Timber; + +public class EncryptionSettingsActivity extends ThemedInjectingAppCompatActivity + implements OnMenuItemClickListener { + + public static final String EXTRA_USER_INFO = "extra_user_info"; + public static final String EXTRA_ACCOUNT = "extra_account"; + public static final String EXTRA_DERIVED_KEY = "extra_derived_key"; + + @Inject EteSyncClient client; + @Inject Encryption encryption; + + ActivityEtesyncEncryptionSettingsBinding binding; + private UserInfo userInfo; + private CaldavAccount caldavAccount; + CreateUserInfoViewModel createUserInfoViewModel; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + createUserInfoViewModel = ViewModelProviders.of(this).get(CreateUserInfoViewModel.class); + + binding = ActivityEtesyncEncryptionSettingsBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + ButterKnife.bind(this); + + Intent intent = getIntent(); + caldavAccount = intent.getParcelableExtra(EXTRA_ACCOUNT); + userInfo = GsonHelper.gson.fromJson(intent.getStringExtra(EXTRA_USER_INFO), UserInfo.class); + + if (userInfo == null) { + binding.repeatEncryptionPasswordLayout.setVisibility(View.VISIBLE); + } + + Toolbar toolbar = binding.toolbar.toolbar; + toolbar.setTitle( + caldavAccount == null ? getString(R.string.add_account) : caldavAccount.getName()); + toolbar.setNavigationIcon(ContextCompat.getDrawable(this, R.drawable.ic_outline_save_24px)); + toolbar.setNavigationOnClickListener(v -> save()); + toolbar.inflateMenu(R.menu.menu_etesync_encryption_settings); + toolbar.setOnMenuItemClickListener(this); + MenuColorizer.colorToolbar(this, toolbar); + + createUserInfoViewModel.observe(this, this::returnDerivedKey, this::requestFailed); + + if (createUserInfoViewModel.inProgress()) { + showProgressIndicator(); + } + } + + private void showProgressIndicator() { + binding.progressBar.progressBar.setVisibility(View.VISIBLE); + } + + private void hideProgressIndicator() { + binding.progressBar.progressBar.setVisibility(View.GONE); + } + + private boolean requestInProgress() { + return binding.progressBar.progressBar.getVisibility() == View.VISIBLE; + } + + private void returnDerivedKey(String derivedKey) { + hideProgressIndicator(); + + Intent result = new Intent(); + result.putExtra(EXTRA_DERIVED_KEY, derivedKey); + setResult(RESULT_OK, result); + finish(); + } + + private void save() { + if (requestInProgress()) { + return; + } + + String encryptionPassword = getNewEncryptionPassword(); + String derivedKey = caldavAccount.getEncryptionPassword(encryption); + + if (isEmpty(encryptionPassword) && isEmpty(derivedKey)) { + binding.encryptionPasswordLayout.setError(getString(R.string.encryption_password_required)); + return; + } + + if (userInfo == null) { + String repeatEncryptionPassword = binding.repeatEncryptionPassword.getText().toString().trim(); + if (!encryptionPassword.equals(repeatEncryptionPassword)) { + binding.repeatEncryptionPasswordLayout.setError(getString(R.string.passwords_do_not_match)); + return; + } + } + + String key = + isEmpty(encryptionPassword) + ? derivedKey + : Crypto.deriveKey(caldavAccount.getUsername(), encryptionPassword); + CryptoManager cryptoManager; + try { + int version = userInfo == null ? Constants.CURRENT_VERSION : userInfo.getVersion(); + cryptoManager = new CryptoManager(version, key, "userInfo"); + } catch (VersionTooNewException | IntegrityException e) { + requestFailed(e); + return; + } + + if (userInfo == null) { + showProgressIndicator(); + createUserInfoViewModel.createUserInfo(client, caldavAccount, key); + } else { + try { + userInfo.verify(cryptoManager); + returnDerivedKey(key); + } catch (IntegrityException e) { + requestFailed(e); + } + } + } + + protected void requestFailed(Throwable t) { + hideProgressIndicator(); + + if (t instanceof HttpException) { + showSnackbar(t.getMessage()); + } else if (t instanceof DisplayableException) { + showSnackbar(((DisplayableException) t).getResId()); + } else if (t instanceof ConnectException) { + showSnackbar(R.string.network_error); + } else { + Timber.e(t); + showSnackbar(R.string.error_adding_account, t.getMessage()); + } + } + + private void showSnackbar(int resId, Object... formatArgs) { + showSnackbar(getString(resId, formatArgs)); + } + + private void showSnackbar(String message) { + newSnackbar(message).show(); + } + + private Snackbar newSnackbar(String message) { + Snackbar snackbar = + Snackbar.make(binding.rootLayout, message, 8000) + .setTextColor(ContextCompat.getColor(this, R.color.snackbar_text_color)) + .setActionTextColor(ContextCompat.getColor(this, R.color.snackbar_action_color)); + snackbar + .getView() + .setBackgroundColor(ContextCompat.getColor(this, R.color.snackbar_background)); + return snackbar; + } + + @OnTextChanged(R.id.repeat_encryption_password) + void onRpeatEncryptionPasswordChanged() { + binding.repeatEncryptionPasswordLayout.setError(null); + } + + @OnTextChanged(R.id.encryption_password) + void onEncryptionPasswordChanged() { + binding.encryptionPasswordLayout.setError(null); + } + + private String getNewEncryptionPassword() { + return binding.encryptionPassword.getText().toString().trim(); + } + + @Override + public void finish() { + if (!requestInProgress()) { + super.finish(); + } + } + + @Override + public void inject(ActivityComponent component) { + component.inject(this); + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + if (item.getItemId() == R.id.help) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://tasks.org/etesync"))); + return true; + } else { + return onOptionsItemSelected(item); + } + } +} diff --git a/app/src/main/java/org/tasks/etesync/EteSyncAccountSettingsActivity.java b/app/src/main/java/org/tasks/etesync/EteSyncAccountSettingsActivity.java index df1b03eeb..8fc444d75 100644 --- a/app/src/main/java/org/tasks/etesync/EteSyncAccountSettingsActivity.java +++ b/app/src/main/java/org/tasks/etesync/EteSyncAccountSettingsActivity.java @@ -1,29 +1,39 @@ package org.tasks.etesync; +import static com.todoroo.astrid.data.Task.NO_ID; + import android.content.Context; +import android.content.Intent; import android.os.Bundle; import android.view.View; import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; import androidx.core.util.Pair; import androidx.lifecycle.ViewModelProviders; -import butterknife.OnFocusChange; -import butterknife.OnTextChanged; +import butterknife.OnCheckedChanged; +import com.etesync.journalmanager.Crypto.CryptoManager; +import com.etesync.journalmanager.Exceptions.IntegrityException; +import com.etesync.journalmanager.Exceptions.VersionTooNewException; +import com.etesync.journalmanager.GsonHelper; +import com.etesync.journalmanager.UserInfoManager.UserInfo; +import com.google.common.base.Strings; import com.todoroo.astrid.helper.UUIDHelper; import io.reactivex.Completable; import io.reactivex.schedulers.Schedulers; import javax.inject.Inject; import org.tasks.R; -import org.tasks.analytics.Tracking.Events; import org.tasks.caldav.BaseCaldavAccountSettingsActivity; import org.tasks.data.CaldavAccount; import org.tasks.gtasks.PlayServices; import org.tasks.injection.ActivityComponent; import org.tasks.injection.ForApplication; +import timber.log.Timber; public class EteSyncAccountSettingsActivity extends BaseCaldavAccountSettingsActivity implements Toolbar.OnMenuItemClickListener { + private static final int REQUEST_ENCRYPTION_PASSWORD = 10101; + @Inject @ForApplication Context context; @Inject PlayServices playServices; @Inject EteSyncClient eteSyncClient; @@ -36,77 +46,113 @@ public class EteSyncAccountSettingsActivity extends BaseCaldavAccountSettingsAct super.onCreate(savedInstanceState); binding.repeat.setVisibility(View.GONE); - binding.encryptionPasswordLayout.setVisibility(View.VISIBLE); + binding.showAdvanced.setVisibility(View.VISIBLE); + updateUrlVisibility(); addAccountViewModel = ViewModelProviders.of(this).get(AddEteSyncAccountViewModel.class); updateAccountViewModel = ViewModelProviders.of(this).get(UpdateEteSyncAccountViewModel.class); - if (savedInstanceState == null) { - if (caldavAccount == null) { - binding.url.setText(R.string.etesync_url); - } - } - addAccountViewModel.observe(this, this::addAccount, this::requestFailed); updateAccountViewModel.observe(this, this::updateAccount, this::requestFailed); } - private void addAccount(Pair authentication) { - CaldavAccount newAccount = new CaldavAccount(); - newAccount.setAccountType(CaldavAccount.TYPE_ETESYNC); - newAccount.setUuid(UUIDHelper.newUUID()); - applyTo(newAccount, authentication); - newAccount.setId(caldavDao.insert(newAccount)); - - tracker.reportEvent(Events.CALDAV_ACCOUNT_ADDED); - - setResult(RESULT_OK); - finish(); + private void addAccount(Pair userInfoAndToken) { + caldavAccount = new CaldavAccount(); + caldavAccount.setAccountType(CaldavAccount.TYPE_ETESYNC); + caldavAccount.setUuid(UUIDHelper.newUUID()); + applyTo(caldavAccount, userInfoAndToken); } - private void updateAccount(Pair authentication) { - applyTo(caldavAccount, authentication); + private void updateAccount(Pair userInfoAndToken) { caldavAccount.setError(""); - caldavDao.update(caldavAccount); - setResult(RESULT_OK); - finish(); + applyTo(caldavAccount, userInfoAndToken); } - private void applyTo(CaldavAccount account, @Nullable Pair authentication) { + private void applyTo(CaldavAccount account, Pair userInfoAndToken) { + hideProgressIndicator(); + account.setName(getNewName()); account.setUrl(getNewURL()); account.setUsername(getNewUsername()); - if (authentication != null) { - account.setPassword(encryption.encrypt(authentication.first)); - account.setEncryptionKey(encryption.encrypt(authentication.second)); + String token = userInfoAndToken.second; + if (!token.equals(account.getPassword(encryption))) { + account.setPassword(encryption.encrypt(token)); + } + + UserInfo userInfo = userInfoAndToken.first; + if (testUserInfo(userInfo)) { + saveAccountAndFinish(); + } else { + Intent intent = new Intent(this, EncryptionSettingsActivity.class); + intent.putExtra(EncryptionSettingsActivity.EXTRA_USER_INFO, toJson(userInfo)); + intent.putExtra(EncryptionSettingsActivity.EXTRA_ACCOUNT, account); + startActivityForResult(intent, REQUEST_ENCRYPTION_PASSWORD); } } - @Override - protected boolean needsValidation() { - return super.needsValidation() || encryptionPasswordChanged(); + private boolean testUserInfo(UserInfo userInfo) { + String encryptionKey = caldavAccount.getEncryptionPassword(encryption); + if (userInfo != null && !Strings.isNullOrEmpty(encryptionKey)) { + try { + CryptoManager cryptoManager = + new CryptoManager(userInfo.getVersion(), encryptionKey, "userInfo"); + userInfo.verify(cryptoManager); + return true; + } catch (IntegrityException | VersionTooNewException e) { + Timber.e(e); + } + } + return false; + } + + private String toJson(UserInfo userInfo) { + return GsonHelper.gson.toJson(userInfo); + } + + @OnCheckedChanged(R.id.show_advanced) + void toggleUrl() { + updateUrlVisibility(); + } + + private void updateUrlVisibility() { + binding.urlLayout.setVisibility(binding.showAdvanced.isChecked() ? View.VISIBLE : View.GONE); } - protected boolean encryptionPasswordChanged() { - return caldavAccount == null - || !PASSWORD_MASK.equals(binding.encryptionPassword.getText().toString().trim()); + @Override + protected boolean needsValidation() { + return super.needsValidation() || Strings.isNullOrEmpty(caldavAccount.getEncryptionKey()); } @Override protected void addAccount(String url, String username, String password) { - addAccountViewModel.addAccount( - playServices, context, eteSyncClient, url, username, password, getNewEncryptionPassword()); + addAccountViewModel.addAccount(playServices, context, eteSyncClient, url, username, password); } @Override protected void updateAccount(String url, String username, String password) { updateAccountViewModel.updateAccount( - eteSyncClient, url, username, password, getNewEncryptionPassword()); + eteSyncClient, + url, + username, + PASSWORD_MASK.equals(password) ? null : password, + caldavAccount.getPassword(encryption)); } @Override protected void updateAccount() { - updateAccount(null); + caldavAccount.setName(getNewName()); + saveAccountAndFinish(); + } + + @Override + protected String getNewURL() { + String url = super.getNewURL(); + return Strings.isNullOrEmpty(url) ? getString(R.string.etesync_url) : url; + } + + @Override + protected String getNewPassword() { + return binding.password.getText().toString().trim(); } @Override @@ -119,14 +165,27 @@ public class EteSyncAccountSettingsActivity extends BaseCaldavAccountSettingsAct component.inject(this); } - @OnTextChanged(R.id.encryption_password) - void onEncryptionPasswordChanged(CharSequence text) { - binding.encryptionPasswordLayout.setError(null); + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + if (requestCode == REQUEST_ENCRYPTION_PASSWORD) { + if (resultCode == RESULT_OK) { + String key = data.getStringExtra(EncryptionSettingsActivity.EXTRA_DERIVED_KEY); + caldavAccount.setEncryptionKey(encryption.encrypt(key)); + saveAccountAndFinish(); + } + } else { + super.onActivityResult(requestCode, resultCode, data); + } } - @OnFocusChange(R.id.encryption_password) - void onEncryptionPasswordFocused(boolean hasFocus) { - changePasswordFocus(binding.encryptionPassword, hasFocus); + private void saveAccountAndFinish() { + if (caldavAccount.getId() == NO_ID) { + caldavDao.insert(caldavAccount); + } else { + caldavDao.update(caldavAccount); + } + setResult(RESULT_OK); + finish(); } @Override diff --git a/app/src/main/java/org/tasks/etesync/EteSyncClient.java b/app/src/main/java/org/tasks/etesync/EteSyncClient.java index 41731c513..ef6b20829 100644 --- a/app/src/main/java/org/tasks/etesync/EteSyncClient.java +++ b/app/src/main/java/org/tasks/etesync/EteSyncClient.java @@ -8,7 +8,7 @@ import androidx.annotation.Nullable; import androidx.core.util.Pair; import at.bitfire.cert4android.CustomCertManager; import at.bitfire.cert4android.CustomCertManager.CustomHostnameVerifier; -import com.etesync.journalmanager.Crypto; +import com.etesync.journalmanager.Constants; import com.etesync.journalmanager.Crypto.CryptoManager; import com.etesync.journalmanager.Exceptions; import com.etesync.journalmanager.Exceptions.HttpException; @@ -151,16 +151,15 @@ public class EteSyncClient { foreground); } - Pair getKeyAndToken(String password) - throws IOException, Exceptions.HttpException, VersionTooNewException, IntegrityException { + Pair getInfoAndToken(String password) throws IOException, HttpException { JournalAuthenticator journalAuthenticator = new JournalAuthenticator(httpClient, httpUrl); String token = journalAuthenticator.getAuthToken(username, password); + return Pair.create(getUserInfo(), token); + } + + UserInfo getUserInfo() throws HttpException { UserInfoManager userInfoManager = new UserInfoManager(httpClient, httpUrl); - UserInfo userInfo = userInfoManager.fetch(username); - String key = Crypto.deriveKey(username, encryptionPassword); - CryptoManager cryptoManager = new CryptoManager(userInfo.getVersion(), key, "userInfo"); - userInfo.verify(cryptoManager); - return Pair.create(token, key); + return userInfoManager.fetch(username); } CryptoManager getCrypto(Journal journal) throws VersionTooNewException, IntegrityException { @@ -185,8 +184,13 @@ public class EteSyncClient { Map result = new HashMap<>(); for (Journal journal : journalManager.list()) { CollectionInfo collection = convertJournalToCollection(journal); - if (collection != null && TYPE_TASKS.equals(collection.getType())) { - result.put(journal, collection); + if (collection != null) { + if (TYPE_TASKS.equals(collection.getType())) { + Timber.v("Found %s", collection); + result.put(journal, collection); + } else { + Timber.v("Ignoring %s", collection); + } } } return result; @@ -222,7 +226,7 @@ public class EteSyncClient { void invalidateToken() { try { - new JournalAuthenticator(httpClient, httpUrl).invalidateAuthToken(token); + new JournalAuthenticator(httpClient, httpUrl).invalidateAuthToken(token); } catch (Exception e) { Timber.e(e); } @@ -244,4 +248,12 @@ public class EteSyncClient { void deleteCollection(CaldavCalendar calendar) throws HttpException { journalManager.delete(Journal.fakeWithUid(calendar.getUrl())); } + + void createUserInfo(String derivedKey) + throws HttpException, VersionTooNewException, IntegrityException, IOException { + CryptoManager cryptoManager = + new CryptoManager(Constants.CURRENT_VERSION, derivedKey, "userInfo"); + UserInfo userInfo = UserInfo.generate(cryptoManager, username); + new UserInfoManager(httpClient, httpUrl).create(userInfo); + } } diff --git a/app/src/main/java/org/tasks/etesync/UpdateEteSyncAccountViewModel.java b/app/src/main/java/org/tasks/etesync/UpdateEteSyncAccountViewModel.java index e7a9dee8d..ef0fb6168 100644 --- a/app/src/main/java/org/tasks/etesync/UpdateEteSyncAccountViewModel.java +++ b/app/src/main/java/org/tasks/etesync/UpdateEteSyncAccountViewModel.java @@ -1,21 +1,25 @@ package org.tasks.etesync; +import androidx.annotation.Nullable; import androidx.core.util.Pair; +import com.etesync.journalmanager.UserInfoManager.UserInfo; +import com.google.common.base.Strings; import org.tasks.ui.CompletableViewModel; @SuppressWarnings("WeakerAccess") -public class UpdateEteSyncAccountViewModel extends CompletableViewModel> { +public class UpdateEteSyncAccountViewModel extends CompletableViewModel> { void updateAccount( EteSyncClient client, String url, String username, - String password, - String encryptionPassword) { + @Nullable String password, + @Nullable String token) { run( - () -> - client - .setForeground() - .forUrl(url, username, encryptionPassword, null) - .getKeyAndToken(password)); + () -> { + EteSyncClient newClient = client.setForeground().forUrl(url, username, null, token); + return Strings.isNullOrEmpty(password) + ? Pair.create(newClient.getUserInfo(), token) + : newClient.getInfoAndToken(password); + }); } } diff --git a/app/src/main/java/org/tasks/injection/ActivityComponent.java b/app/src/main/java/org/tasks/injection/ActivityComponent.java index e080218d3..2ac04ef84 100644 --- a/app/src/main/java/org/tasks/injection/ActivityComponent.java +++ b/app/src/main/java/org/tasks/injection/ActivityComponent.java @@ -19,6 +19,7 @@ import org.tasks.activities.DatePickerActivity; import org.tasks.activities.FilterSelectionActivity; import org.tasks.activities.FilterSettingsActivity; import org.tasks.activities.GoogleTaskListSettingsActivity; +import org.tasks.etesync.EncryptionSettingsActivity; import org.tasks.etesync.EteSyncAccountSettingsActivity; import org.tasks.etesync.EteSyncCalendarSettingsActivity; import org.tasks.tags.TagPickerActivity; @@ -152,4 +153,6 @@ public interface ActivityComponent { void inject(TagPickerViewModel viewModel); void inject(EteSyncCalendarSettingsActivity eteSyncCalendarSettingsActivity); + + void inject(EncryptionSettingsActivity encryptionSettingsActivity); } diff --git a/app/src/main/res/layout/activity_caldav_account_settings.xml b/app/src/main/res/layout/activity_caldav_account_settings.xml index d7687d823..d1ee7947f 100644 --- a/app/src/main/res/layout/activity_caldav_account_settings.xml +++ b/app/src/main/res/layout/activity_caldav_account_settings.xml @@ -29,6 +29,7 @@ @@ -47,19 +48,6 @@ - - - - - - @@ -76,7 +64,7 @@ + app:endIconMode="password_toggle"> - + android:text="@string/show_advanced_settings" + android:visibility="gone" /> + + + android:hint="@string/url" + android:imeOptions="flagNoExtractUi" /> diff --git a/app/src/main/res/layout/activity_etesync_encryption_settings.xml b/app/src/main/res/layout/activity_etesync_encryption_settings.xml new file mode 100644 index 000000000..b11ce40c2 --- /dev/null +++ b/app/src/main/res/layout/activity_etesync_encryption_settings.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_etesync_encryption_settings.xml b/app/src/main/res/menu/menu_etesync_encryption_settings.xml new file mode 100644 index 000000000..4c186a162 --- /dev/null +++ b/app/src/main/res/menu/menu_etesync_encryption_settings.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 05dbff7a2..b6dbf3ad3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -379,6 +379,7 @@ File %1$s contained %2$s.\n\n Username required Password required Encryption password required + Passwords do not match URL required Host name required Must begin with http(s):// @@ -448,6 +449,7 @@ File %1$s contained %2$s.\n\n User Password Encryption password + Confirm encryption password URL Error: %s Calendar settings @@ -571,4 +573,5 @@ File %1$s contained %2$s.\n\n Basic service that synchronizes with your Google account Synchronization based on open internet standards Open source, end-to-end encrypted synchronization + Show advanced settings