Merge tag '2.5.5' into develop
|
@ -0,0 +1,2 @@
|
||||||
|
liberapay: inputmice
|
||||||
|
custom: https://paypal.me/ConversationsIM
|
|
@ -1,12 +1,12 @@
|
||||||
#### General information
|
#### General information
|
||||||
|
|
||||||
* **Version:** eg 1.21.0
|
* **Version:** 2.5.3
|
||||||
* **Device:** eg Google Nexus 5
|
* **Device:** Xiaomi Mi A1
|
||||||
* **Android Version:** eg Android 6.0 Stock or Android 5.1 Cyanogenmod
|
* **Android Version:** Android 9 (stock)
|
||||||
* **Server name:** eg conversations.im, jabber.at or self hosted
|
* **Server name:** conversations.im, jabber.at or self hosted
|
||||||
* **Server software:** ejabberd 16.04 or prosody 0.10 (if known)
|
* **Server software:** ejabberd 16.04 or prosody 0.10 (if known)
|
||||||
* **Installed server modules:** eg Stream Managment, CSI, MAM
|
* **Installed server modules:** Stream Managment, CSI, MAM
|
||||||
* **Conversations source:** eg PlayStore, PlayStore Beta Channel, F-Droid, self build (latest HEAD)
|
* **Conversations source:** PlayStore, PlayStore Beta Channel, F-Droid, self build (latest HEAD)
|
||||||
|
|
||||||
|
|
||||||
#### Steps to reproduce
|
#### Steps to reproduce
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
### Version 2.5.5
|
||||||
|
* allow backups to be restored from anywhere
|
||||||
|
* bug fixes
|
||||||
|
|
||||||
### Version 2.5.4
|
### Version 2.5.4
|
||||||
* stability improvements for group chats and channels
|
* stability improvements for group chats and channels
|
||||||
|
|
||||||
|
|
|
@ -81,8 +81,8 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 25
|
targetSdkVersion 25
|
||||||
versionCode 333
|
versionCode 334
|
||||||
versionName "2.5.4"
|
versionName "2.5.5"
|
||||||
archivesBaseName += "-$versionName"
|
archivesBaseName += "-$versionName"
|
||||||
applicationId "eu.sum7.conversations"
|
applicationId "eu.sum7.conversations"
|
||||||
resValue "string", "applicationId", applicationId
|
resValue "string", "applicationId", applicationId
|
||||||
|
|
|
@ -23,7 +23,20 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.ImportBackupActivity"
|
android:name=".ui.ImportBackupActivity"
|
||||||
android:label="@string/restore_backup"
|
android:label="@string/restore_backup"
|
||||||
android:launchMode="singleTask" />
|
android:launchMode="singleTask">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="application/vnd.conversations.backup" />
|
||||||
|
<data android:scheme="content" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="application/vnd.conversations.backup" />
|
||||||
|
<data android:scheme="file" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
@ -16,18 +17,20 @@ import java.io.BufferedReader;
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
import java.util.zip.ZipException;
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
|
@ -81,14 +84,22 @@ public class ImportBackupService extends Service {
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
final String password = intent.getStringExtra("password");
|
final String password = intent.getStringExtra("password");
|
||||||
|
final Uri data = intent.getData();
|
||||||
|
final Uri uri;
|
||||||
|
if (data == null) {
|
||||||
final String file = intent.getStringExtra("file");
|
final String file = intent.getStringExtra("file");
|
||||||
if (password == null || file == null) {
|
uri = file == null ? null : Uri.fromFile(new File(file));
|
||||||
|
} else {
|
||||||
|
uri = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password == null || password.isEmpty() || uri == null) {
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
if (running.compareAndSet(false, true)) {
|
if (running.compareAndSet(false, true)) {
|
||||||
executor.execute(() -> {
|
executor.execute(() -> {
|
||||||
startForegroundService();
|
startForegroundService();
|
||||||
final boolean success = importBackup(new File(file), password);
|
final boolean success = importBackup(uri, password);
|
||||||
stopForeground(true);
|
stopForeground(true);
|
||||||
running.set(false);
|
running.set(false);
|
||||||
if (success) {
|
if (success) {
|
||||||
|
@ -122,7 +133,7 @@ public class ImportBackupService extends Service {
|
||||||
try {
|
try {
|
||||||
final BackupFile backupFile = BackupFile.read(file);
|
final BackupFile backupFile = BackupFile.read(file);
|
||||||
if (accounts.contains(backupFile.getHeader().getJid())) {
|
if (accounts.contains(backupFile.getHeader().getJid())) {
|
||||||
Log.d(Config.LOGTAG,"skipping backup for "+backupFile.getHeader().getJid());
|
Log.d(Config.LOGTAG, "skipping backup for " + backupFile.getHeader().getJid());
|
||||||
} else {
|
} else {
|
||||||
backupFiles.add(backupFile);
|
backupFiles.add(backupFile);
|
||||||
}
|
}
|
||||||
|
@ -145,21 +156,35 @@ public class ImportBackupService extends Service {
|
||||||
startForeground(NOTIFICATION_ID, mBuilder.build());
|
startForeground(NOTIFICATION_ID, mBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean importBackup(File file, String password) {
|
private boolean importBackup(Uri uri, String password) {
|
||||||
Log.d(Config.LOGTAG, "importing backup from file " + file.getAbsolutePath());
|
Log.d(Config.LOGTAG, "importing backup from " + uri);
|
||||||
try {
|
try {
|
||||||
SQLiteDatabase db = mDatabaseBackend.getWritableDatabase();
|
SQLiteDatabase db = mDatabaseBackend.getWritableDatabase();
|
||||||
final FileInputStream fileInputStream = new FileInputStream(file);
|
final InputStream inputStream;
|
||||||
final DataInputStream dataInputStream = new DataInputStream(fileInputStream);
|
if ("file".equals(uri.getScheme())) {
|
||||||
BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
|
inputStream = new FileInputStream(new File(uri.getPath()));
|
||||||
|
} else {
|
||||||
|
inputStream = getContentResolver().openInputStream(uri);
|
||||||
|
}
|
||||||
|
final DataInputStream dataInputStream = new DataInputStream(inputStream);
|
||||||
|
final BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
|
||||||
Log.d(Config.LOGTAG, backupFileHeader.toString());
|
Log.d(Config.LOGTAG, backupFileHeader.toString());
|
||||||
|
|
||||||
|
if (mDatabaseBackend.getAccountJids(false).contains(backupFileHeader.getJid())) {
|
||||||
|
synchronized (mOnBackupProcessedListeners) {
|
||||||
|
for (OnBackupProcessed l : mOnBackupProcessedListeners) {
|
||||||
|
l.onAccountAlreadySetup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
|
final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
|
||||||
byte[] key = ExportBackupService.getKey(password, backupFileHeader.getSalt());
|
byte[] key = ExportBackupService.getKey(password, backupFileHeader.getSalt());
|
||||||
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
|
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
|
||||||
IvParameterSpec ivSpec = new IvParameterSpec(backupFileHeader.getIv());
|
IvParameterSpec ivSpec = new IvParameterSpec(backupFileHeader.getIv());
|
||||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
|
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
|
||||||
CipherInputStream cipherInputStream = new CipherInputStream(fileInputStream, cipher);
|
CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
|
||||||
|
|
||||||
GZIPInputStream gzipInputStream = new GZIPInputStream(cipherInputStream);
|
GZIPInputStream gzipInputStream = new GZIPInputStream(cipherInputStream);
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, "UTF-8"));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, "UTF-8"));
|
||||||
|
@ -197,12 +222,7 @@ public class ImportBackupService extends Service {
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Throwable throwable = e.getCause();
|
Throwable throwable = e.getCause();
|
||||||
final boolean reasonWasCrypto;
|
final boolean reasonWasCrypto = throwable instanceof BadPaddingException || e instanceof ZipException;
|
||||||
if (throwable instanceof BadPaddingException) {
|
|
||||||
reasonWasCrypto = true;
|
|
||||||
} else {
|
|
||||||
reasonWasCrypto = false;
|
|
||||||
}
|
|
||||||
synchronized (mOnBackupProcessedListeners) {
|
synchronized (mOnBackupProcessedListeners) {
|
||||||
for (OnBackupProcessed l : mOnBackupProcessedListeners) {
|
for (OnBackupProcessed l : mOnBackupProcessedListeners) {
|
||||||
if (reasonWasCrypto) {
|
if (reasonWasCrypto) {
|
||||||
|
@ -212,7 +232,7 @@ public class ImportBackupService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.d(Config.LOGTAG, "error restoring backup " + file.getAbsolutePath(), e);
|
Log.d(Config.LOGTAG, "error restoring backup " + uri, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,14 +279,16 @@ public class ImportBackupService extends Service {
|
||||||
void onBackupDecryptionFailed();
|
void onBackupDecryptionFailed();
|
||||||
|
|
||||||
void onBackupRestoreFailed();
|
void onBackupRestoreFailed();
|
||||||
|
|
||||||
|
void onAccountAlreadySetup();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class BackupFile {
|
public static class BackupFile {
|
||||||
private final File file;
|
private final Uri uri;
|
||||||
private final BackupFileHeader header;
|
private final BackupFileHeader header;
|
||||||
|
|
||||||
private BackupFile(File file, BackupFileHeader header) {
|
private BackupFile(Uri uri, BackupFileHeader header) {
|
||||||
this.file = file;
|
this.uri = uri;
|
||||||
this.header = header;
|
this.header = header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,15 +297,26 @@ public class ImportBackupService extends Service {
|
||||||
final DataInputStream dataInputStream = new DataInputStream(fileInputStream);
|
final DataInputStream dataInputStream = new DataInputStream(fileInputStream);
|
||||||
BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
|
BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
|
||||||
fileInputStream.close();
|
fileInputStream.close();
|
||||||
return new BackupFile(file, backupFileHeader);
|
return new BackupFile(Uri.fromFile(file), backupFileHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BackupFile read(final Context context, final Uri uri) throws IOException {
|
||||||
|
final InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
||||||
|
if (inputStream == null) {
|
||||||
|
throw new FileNotFoundException();
|
||||||
|
}
|
||||||
|
final DataInputStream dataInputStream = new DataInputStream(inputStream);
|
||||||
|
BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
|
||||||
|
inputStream.close();
|
||||||
|
return new BackupFile(uri, backupFileHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BackupFileHeader getHeader() {
|
public BackupFileHeader getHeader() {
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getFile() {
|
public Uri getUri() {
|
||||||
return file;
|
return uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,12 @@ package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.databinding.DataBindingUtil;
|
import android.databinding.DataBindingUtil;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
|
@ -13,8 +16,11 @@ import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
|
@ -32,6 +38,8 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo
|
||||||
private BackupFileAdapter backupFileAdapter;
|
private BackupFileAdapter backupFileAdapter;
|
||||||
private ImportBackupService service;
|
private ImportBackupService service;
|
||||||
|
|
||||||
|
private boolean mLoadingState = false;
|
||||||
|
|
||||||
private int mTheme;
|
private int mTheme;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -41,12 +49,26 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
binding = DataBindingUtil.setContentView(this, R.layout.activity_import_backup);
|
binding = DataBindingUtil.setContentView(this, R.layout.activity_import_backup);
|
||||||
setSupportActionBar((Toolbar) binding.toolbar);
|
setSupportActionBar((Toolbar) binding.toolbar);
|
||||||
configureActionBar(getSupportActionBar());
|
setLoadingState(savedInstanceState != null && savedInstanceState.getBoolean("loading_state", false));
|
||||||
this.backupFileAdapter = new BackupFileAdapter();
|
this.backupFileAdapter = new BackupFileAdapter();
|
||||||
this.binding.list.setAdapter(this.backupFileAdapter);
|
this.binding.list.setAdapter(this.backupFileAdapter);
|
||||||
this.backupFileAdapter.setOnItemClickedListener(this);
|
this.backupFileAdapter.setOnItemClickedListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.import_backup, menu);
|
||||||
|
final MenuItem openBackup = menu.findItem(R.id.action_open_backup_file);
|
||||||
|
openBackup.setVisible(!this.mLoadingState);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle bundle) {
|
||||||
|
bundle.putBoolean("loading_state", this.mLoadingState);
|
||||||
|
super.onSaveInstanceState(bundle);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
@ -56,6 +78,13 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo
|
||||||
} else {
|
} else {
|
||||||
bindService(new Intent(this, ImportBackupService.class), this, Context.BIND_AUTO_CREATE);
|
bindService(new Intent(this, ImportBackupService.class), this, Context.BIND_AUTO_CREATE);
|
||||||
}
|
}
|
||||||
|
final Intent intent = getIntent();
|
||||||
|
if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction()) && !this.mLoadingState) {
|
||||||
|
Uri uri = intent.getData();
|
||||||
|
if (uri != null) {
|
||||||
|
openBackupFileFromUri(uri, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -87,31 +116,83 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(ImportBackupService.BackupFile backupFile) {
|
public void onClick(final ImportBackupService.BackupFile backupFile) {
|
||||||
|
showEnterPasswordDialog(backupFile, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openBackupFileFromUri(final Uri uri, final boolean finishOnCancel) {
|
||||||
|
try {
|
||||||
|
final ImportBackupService.BackupFile backupFile = ImportBackupService.BackupFile.read(this, uri);
|
||||||
|
showEnterPasswordDialog(backupFile, finishOnCancel);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Snackbar.make(binding.coordinator, R.string.not_a_backup_file, Snackbar.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showEnterPasswordDialog(final ImportBackupService.BackupFile backupFile, final boolean finishOnCancel) {
|
||||||
final DialogEnterPasswordBinding enterPasswordBinding = DataBindingUtil.inflate(LayoutInflater.from(this), R.layout.dialog_enter_password, null, false);
|
final DialogEnterPasswordBinding enterPasswordBinding = DataBindingUtil.inflate(LayoutInflater.from(this), R.layout.dialog_enter_password, null, false);
|
||||||
Log.d(Config.LOGTAG, "attempting to import " + backupFile.getFile().getAbsolutePath());
|
Log.d(Config.LOGTAG, "attempting to import " + backupFile.getUri());
|
||||||
enterPasswordBinding.explain.setText(getString(R.string.enter_password_to_restore, backupFile.getHeader().getJid().toString()));
|
enterPasswordBinding.explain.setText(getString(R.string.enter_password_to_restore, backupFile.getHeader().getJid().toString()));
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
builder.setView(enterPasswordBinding.getRoot());
|
builder.setView(enterPasswordBinding.getRoot());
|
||||||
builder.setTitle(R.string.enter_password);
|
builder.setTitle(R.string.enter_password);
|
||||||
builder.setNegativeButton(R.string.cancel, null);
|
builder.setNegativeButton(R.string.cancel, (dialog, which) -> {
|
||||||
builder.setPositiveButton(R.string.restore, (dialog, which) -> {
|
if (finishOnCancel) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.setPositiveButton(R.string.restore, null);
|
||||||
|
builder.setCancelable(false);
|
||||||
|
final AlertDialog dialog = builder.create();
|
||||||
|
dialog.setOnShowListener((d) -> {
|
||||||
|
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||||
final String password = enterPasswordBinding.accountPassword.getEditableText().toString();
|
final String password = enterPasswordBinding.accountPassword.getEditableText().toString();
|
||||||
|
if (password.isEmpty()) {
|
||||||
|
enterPasswordBinding.accountPasswordLayout.setError(getString(R.string.please_enter_password));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final Uri uri = backupFile.getUri();
|
||||||
Intent intent = new Intent(this, ImportBackupService.class);
|
Intent intent = new Intent(this, ImportBackupService.class);
|
||||||
|
intent.setAction(Intent.ACTION_SEND);
|
||||||
intent.putExtra("password", password);
|
intent.putExtra("password", password);
|
||||||
intent.putExtra("file", backupFile.getFile().getAbsolutePath());
|
if ("file".equals(uri.getScheme())) {
|
||||||
|
intent.putExtra("file", uri.getPath());
|
||||||
|
} else {
|
||||||
|
intent.setData(uri);
|
||||||
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
}
|
||||||
setLoadingState(true);
|
setLoadingState(true);
|
||||||
ContextCompat.startForegroundService(this, intent);
|
ContextCompat.startForegroundService(this, intent);
|
||||||
|
d.dismiss();
|
||||||
});
|
});
|
||||||
builder.setCancelable(false);
|
});
|
||||||
builder.create().show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setLoadingState(final boolean loadingState) {
|
private void setLoadingState(final boolean loadingState) {
|
||||||
binding.coordinator.setVisibility(loadingState ? View.GONE :View.VISIBLE);
|
binding.coordinator.setVisibility(loadingState ? View.GONE : View.VISIBLE);
|
||||||
binding.inProgress.setVisibility(loadingState ? View.VISIBLE : View.GONE);
|
binding.inProgress.setVisibility(loadingState ? View.VISIBLE : View.GONE);
|
||||||
setTitle(loadingState ? R.string.restoring_backup : R.string.restore_backup);
|
setTitle(loadingState ? R.string.restoring_backup : R.string.restore_backup);
|
||||||
configureActionBar(getSupportActionBar(),!loadingState);
|
configureActionBar(getSupportActionBar(), !loadingState);
|
||||||
|
this.mLoadingState = loadingState;
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
if (requestCode == 0xbac) {
|
||||||
|
openBackupFileFromUri(intent.getData(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAccountAlreadySetup() {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
setLoadingState(false);
|
||||||
|
Snackbar.make(binding.coordinator, R.string.account_already_setup, Snackbar.LENGTH_LONG).show();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -126,17 +207,32 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackupDecryptionFailed() {
|
public void onBackupDecryptionFailed() {
|
||||||
runOnUiThread(()-> {
|
runOnUiThread(() -> {
|
||||||
setLoadingState(false);
|
setLoadingState(false);
|
||||||
Snackbar.make(binding.coordinator,R.string.unable_to_decrypt_backup,Snackbar.LENGTH_LONG).show();
|
Snackbar.make(binding.coordinator, R.string.unable_to_decrypt_backup, Snackbar.LENGTH_LONG).show();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackupRestoreFailed() {
|
public void onBackupRestoreFailed() {
|
||||||
runOnUiThread(()-> {
|
runOnUiThread(() -> {
|
||||||
setLoadingState(false);
|
setLoadingState(false);
|
||||||
Snackbar.make(binding.coordinator,R.string.unable_to_restore_backup,Snackbar.LENGTH_LONG).show();
|
Snackbar.make(binding.coordinator, R.string.unable_to_restore_backup, Snackbar.LENGTH_LONG).show();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.action_open_backup_file) {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
intent.setType("*/*");
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||||
|
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
|
||||||
|
}
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
startActivityForResult(Intent.createChooser(intent, getString(R.string.open_backup)), 0xbac);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,13 +20,12 @@ public class SignupUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Intent getSignUpIntent(final Activity activity, final boolean toServerChooser) {
|
public static Intent getSignUpIntent(final Activity activity, final boolean toServerChooser) {
|
||||||
Intent intent;
|
final Intent intent;
|
||||||
if (toServerChooser) {
|
if (toServerChooser) {
|
||||||
intent = new Intent(activity, PickServerActivity.class);
|
intent = new Intent(activity, PickServerActivity.class);
|
||||||
} else {
|
} else {
|
||||||
intent = new Intent(activity, WelcomeActivity.class);
|
intent = new Intent(activity, WelcomeActivity.class);
|
||||||
}
|
}
|
||||||
StartConversationActivity.addInviteUri(intent, activity.getIntent());
|
|
||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,6 @@
|
||||||
android:theme="@style/SplashTheme">
|
android:theme="@style/SplashTheme">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.security.cert.X509Certificate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.net.ssl.SSLSession;
|
import javax.net.ssl.SSLSession;
|
||||||
|
|
||||||
|
@ -80,14 +81,14 @@ public class XmppDomainVerifier implements DomainHostnameVerifier {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Log.d(LOGTAG, "comparing " + needle.substring(i) + " and " + entry.substring(1));
|
Log.d(LOGTAG, "comparing " + needle.substring(i) + " and " + entry.substring(1));
|
||||||
if (needle.substring(i).equals(entry.substring(1))) {
|
if (needle.substring(i).equalsIgnoreCase(entry.substring(1))) {
|
||||||
Log.d(LOGTAG, "domain " + needle + " matched " + entry);
|
Log.d(LOGTAG, "domain " + needle + " matched " + entry);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
offset = i + 1;
|
offset = i + 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (entry.equals(needle)) {
|
if (entry.equalsIgnoreCase(needle)) {
|
||||||
Log.d(LOGTAG, "domain " + needle + " matched " + entry);
|
Log.d(LOGTAG, "domain " + needle + " matched " + entry);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -117,25 +118,25 @@ public class XmppDomainVerifier implements DomainHostnameVerifier {
|
||||||
List<String> domains = new ArrayList<>();
|
List<String> domains = new ArrayList<>();
|
||||||
if (alternativeNames != null) {
|
if (alternativeNames != null) {
|
||||||
for (List<?> san : alternativeNames) {
|
for (List<?> san : alternativeNames) {
|
||||||
Integer type = (Integer) san.get(0);
|
final Integer type = (Integer) san.get(0);
|
||||||
if (type == 0) {
|
if (type == 0) {
|
||||||
Pair<String, String> otherName = parseOtherName((byte[]) san.get(1));
|
final Pair<String, String> otherName = parseOtherName((byte[]) san.get(1));
|
||||||
if (otherName != null) {
|
if (otherName != null && otherName.first != null && otherName.second != null) {
|
||||||
switch (otherName.first) {
|
switch (otherName.first) {
|
||||||
case SRV_NAME:
|
case SRV_NAME:
|
||||||
srvNames.add(otherName.second);
|
srvNames.add(otherName.second.toLowerCase(Locale.US));
|
||||||
break;
|
break;
|
||||||
case XMPP_ADDR:
|
case XMPP_ADDR:
|
||||||
xmppAddrs.add(otherName.second);
|
xmppAddrs.add(otherName.second.toLowerCase(Locale.US));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Log.d(LOGTAG, "oid: " + otherName.first + " value: " + otherName.second);
|
Log.d(LOGTAG, "oid: " + otherName.first + " value: " + otherName.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (type == 2) {
|
} else if (type == 2) {
|
||||||
Object value = san.get(1);
|
final Object value = san.get(1);
|
||||||
if (value instanceof String) {
|
if (value instanceof String) {
|
||||||
domains.add((String) value);
|
domains.add(((String) value).toLowerCase(Locale.US));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -285,7 +285,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
final ArrayList<Message> results = new ArrayList<>();
|
final ArrayList<Message> results = new ArrayList<>();
|
||||||
synchronized (this.messages) {
|
synchronized (this.messages) {
|
||||||
for (Message message : this.messages) {
|
for (Message message : this.messages) {
|
||||||
if (message.getType() != Message.TYPE_IMAGE && message.getStatus() == Message.STATUS_UNSEND) {
|
if ((message.getType() == Message.TYPE_TEXT || message.hasFileOnRemoteHost()) && message.getStatus() == Message.STATUS_UNSEND) {
|
||||||
results.add(message);
|
results.add(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -543,7 +543,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null
|
final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null
|
||||||
|| replacedMessage.getFingerprint().equals(message.getFingerprint());
|
|| replacedMessage.getFingerprint().equals(message.getFingerprint());
|
||||||
final boolean trueCountersMatch = replacedMessage.getTrueCounterpart() != null
|
final boolean trueCountersMatch = replacedMessage.getTrueCounterpart() != null
|
||||||
&& replacedMessage.getTrueCounterpart().equals(message.getTrueCounterpart());
|
&& message.getTrueCounterpart() != null
|
||||||
|
&& replacedMessage.getTrueCounterpart().asBareJid().equals(message.getTrueCounterpart().asBareJid());
|
||||||
final boolean mucUserMatches = query == null && replacedMessage.sameMucUser(message); //can not be checked when using mam
|
final boolean mucUserMatches = query == null && replacedMessage.sameMucUser(message); //can not be checked when using mam
|
||||||
final boolean duplicate = conversation.hasDuplicateMessage(message);
|
final boolean duplicate = conversation.hasDuplicateMessage(message);
|
||||||
if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode || mucUserMatches) && !duplicate) {
|
if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode || mucUserMatches) && !duplicate) {
|
||||||
|
|
|
@ -21,7 +21,9 @@ import java.io.PrintWriter;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.zip.GZIPOutputStream;
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
@ -50,6 +52,8 @@ public class ExportBackupService extends Service {
|
||||||
public static final String CIPHERMODE = "AES/GCM/NoPadding";
|
public static final String CIPHERMODE = "AES/GCM/NoPadding";
|
||||||
public static final String PROVIDER = "BC";
|
public static final String PROVIDER = "BC";
|
||||||
|
|
||||||
|
public static final String MIME_TYPE = "application/vnd.conversations.backup";
|
||||||
|
|
||||||
private static final int NOTIFICATION_ID = 19;
|
private static final int NOTIFICATION_ID = 19;
|
||||||
private static final int PAGE_SIZE = 20;
|
private static final int PAGE_SIZE = 20;
|
||||||
private static AtomicBoolean running = new AtomicBoolean(false);
|
private static AtomicBoolean running = new AtomicBoolean(false);
|
||||||
|
@ -213,11 +217,19 @@ public class ExportBackupService extends Service {
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
if (running.compareAndSet(false, true)) {
|
if (running.compareAndSet(false, true)) {
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
final boolean success = export();
|
boolean success;
|
||||||
|
List<File> files;
|
||||||
|
try {
|
||||||
|
files = export();
|
||||||
|
success = true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
success = false;
|
||||||
|
files = Collections.emptyList();
|
||||||
|
}
|
||||||
stopForeground(true);
|
stopForeground(true);
|
||||||
running.set(false);
|
running.set(false);
|
||||||
if (success) {
|
if (success) {
|
||||||
notifySuccess();
|
notifySuccess(files);
|
||||||
}
|
}
|
||||||
stopSelf();
|
stopSelf();
|
||||||
}).start();
|
}).start();
|
||||||
|
@ -250,16 +262,16 @@ public class ExportBackupService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean export() {
|
private List<File> export() throws Exception {
|
||||||
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
|
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
|
||||||
mBuilder.setContentTitle(getString(R.string.notification_create_backup_title))
|
mBuilder.setContentTitle(getString(R.string.notification_create_backup_title))
|
||||||
.setSmallIcon(R.drawable.ic_archive_white_24dp)
|
.setSmallIcon(R.drawable.ic_archive_white_24dp)
|
||||||
.setProgress(1, 0, false);
|
.setProgress(1, 0, false);
|
||||||
startForeground(NOTIFICATION_ID, mBuilder.build());
|
startForeground(NOTIFICATION_ID, mBuilder.build());
|
||||||
try {
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
final int max = this.mAccounts.size();
|
final int max = this.mAccounts.size();
|
||||||
final SecureRandom secureRandom = new SecureRandom();
|
final SecureRandom secureRandom = new SecureRandom();
|
||||||
|
final List<File> files = new ArrayList<>();
|
||||||
for (Account account : this.mAccounts) {
|
for (Account account : this.mAccounts) {
|
||||||
final byte[] IV = new byte[12];
|
final byte[] IV = new byte[12];
|
||||||
final byte[] salt = new byte[16];
|
final byte[] salt = new byte[16];
|
||||||
|
@ -268,6 +280,7 @@ public class ExportBackupService extends Service {
|
||||||
final BackupFileHeader backupFileHeader = new BackupFileHeader(getString(R.string.app_name), account.getJid(), System.currentTimeMillis(), IV, salt);
|
final BackupFileHeader backupFileHeader = new BackupFileHeader(getString(R.string.app_name), account.getJid(), System.currentTimeMillis(), IV, salt);
|
||||||
final Progress progress = new Progress(mBuilder, max, count);
|
final Progress progress = new Progress(mBuilder, max, count);
|
||||||
final File file = new File(FileBackend.getBackupDirectory(this) + account.getJid().asBareJid().toEscapedString() + ".ceb");
|
final File file = new File(FileBackend.getBackupDirectory(this) + account.getJid().asBareJid().toEscapedString() + ".ceb");
|
||||||
|
files.add(file);
|
||||||
if (file.getParentFile().mkdirs()) {
|
if (file.getParentFile().mkdirs()) {
|
||||||
Log.d(Config.LOGTAG, "created backup directory " + file.getParentFile().getAbsolutePath());
|
Log.d(Config.LOGTAG, "created backup directory " + file.getParentFile().getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
@ -299,32 +312,47 @@ public class ExportBackupService extends Service {
|
||||||
Log.d(Config.LOGTAG, "written backup to " + file.getAbsoluteFile());
|
Log.d(Config.LOGTAG, "written backup to " + file.getAbsoluteFile());
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
return true;
|
return files;
|
||||||
} catch (Exception e) {
|
|
||||||
Log.d(Config.LOGTAG, "unable to create backup ", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifySuccess() {
|
private void notifySuccess(List<File> files) {
|
||||||
final String path = FileBackend.getBackupDirectory(this);
|
final String path = FileBackend.getBackupDirectory(this);
|
||||||
|
|
||||||
PendingIntent pendingIntent = null;
|
PendingIntent openFolderIntent = null;
|
||||||
|
|
||||||
for (Intent intent : getPossibleFileOpenIntents(this, path)) {
|
for (Intent intent : getPossibleFileOpenIntents(this, path)) {
|
||||||
if (intent.resolveActivityInfo(getPackageManager(), 0) != null) {
|
if (intent.resolveActivityInfo(getPackageManager(), 0) != null) {
|
||||||
pendingIntent = PendingIntent.getActivity(this, 189, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
openFolderIntent = PendingIntent.getActivity(this, 189, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PendingIntent shareFilesIntent = null;
|
||||||
|
if (files.size() > 0) {
|
||||||
|
final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
|
||||||
|
ArrayList<Uri> uris = new ArrayList<>();
|
||||||
|
for(File file : files) {
|
||||||
|
uris.add(FileBackend.getUriForFile(this, file));
|
||||||
|
}
|
||||||
|
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
|
||||||
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
intent.setType(MIME_TYPE);
|
||||||
|
final Intent chooser = Intent.createChooser(intent, getString(R.string.share_backup_files));
|
||||||
|
shareFilesIntent = PendingIntent.getActivity(this,190, chooser, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
}
|
||||||
|
|
||||||
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
|
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
|
||||||
mBuilder.setContentTitle(getString(R.string.notification_backup_created_title))
|
mBuilder.setContentTitle(getString(R.string.notification_backup_created_title))
|
||||||
.setContentText(getString(R.string.notification_backup_created_subtitle, path))
|
.setContentText(getString(R.string.notification_backup_created_subtitle, path))
|
||||||
.setStyle(new NotificationCompat.BigTextStyle().bigText(getString(R.string.notification_backup_created_subtitle, FileBackend.getBackupDirectory(this))))
|
.setStyle(new NotificationCompat.BigTextStyle().bigText(getString(R.string.notification_backup_created_subtitle, FileBackend.getBackupDirectory(this))))
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(openFolderIntent)
|
||||||
.setSmallIcon(R.drawable.ic_archive_white_24dp);
|
.setSmallIcon(R.drawable.ic_archive_white_24dp);
|
||||||
|
|
||||||
|
if (shareFilesIntent != null) {
|
||||||
|
mBuilder.addAction(R.drawable.ic_share_white_24dp, getString(R.string.share_backup_files), shareFilesIntent);
|
||||||
|
}
|
||||||
|
|
||||||
notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
|
notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -131,10 +131,11 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
|
||||||
if (Intent.ACTION_SEND.equals(action)) {
|
if (Intent.ACTION_SEND.equals(action)) {
|
||||||
final String text = intent.getStringExtra(Intent.EXTRA_TEXT);
|
final String text = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||||
final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||||
|
|
||||||
if (data != null && "geo".equals(data.getScheme())) {
|
if (data != null && "geo".equals(data.getScheme())) {
|
||||||
this.share.uris.clear();
|
this.share.uris.clear();
|
||||||
this.share.uris.add(data);
|
this.share.uris.add(data);
|
||||||
} else if (type != null && uri != null && (text == null || !type.equals("text/plain"))) {
|
} else if (type != null && uri != null) {
|
||||||
this.share.uris.clear();
|
this.share.uris.clear();
|
||||||
this.share.uris.add(uri);
|
this.share.uris.add(uri);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -90,6 +90,7 @@ public class UriHandlerActivity extends AppCompatActivity {
|
||||||
if (accounts.size() == 0) {
|
if (accounts.size() == 0) {
|
||||||
if (xmppUri.isJidValid()) {
|
if (xmppUri.isJidValid()) {
|
||||||
intent = SignupUtils.getSignUpIntent(this);
|
intent = SignupUtils.getSignUpIntent(this);
|
||||||
|
intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString());
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, R.string.invalid_jid, Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, R.string.invalid_jid, Toast.LENGTH_SHORT).show();
|
||||||
|
|
|
@ -94,6 +94,7 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
|
||||||
imageResource = activity.getThemeResource(R.attr.ic_attach_location, R.drawable.ic_attach_location);
|
imageResource = activity.getThemeResource(R.attr.ic_attach_location, R.drawable.ic_attach_location);
|
||||||
showPreviewText = false;
|
showPreviewText = false;
|
||||||
} else {
|
} else {
|
||||||
|
//TODO move this into static MediaPreview method and use same icons as in MediaAdapter
|
||||||
final String mime = message.getMimeType();
|
final String mime = message.getMimeType();
|
||||||
switch (mime == null ? "" : mime.split("/")[0]) {
|
switch (mime == null ? "" : mime.split("/")[0]) {
|
||||||
case "image":
|
case "image":
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.util.concurrent.RejectedExecutionException;
|
||||||
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.databinding.MediaBinding;
|
import eu.siacs.conversations.databinding.MediaBinding;
|
||||||
|
import eu.siacs.conversations.services.ExportBackupService;
|
||||||
import eu.siacs.conversations.ui.XmppActivity;
|
import eu.siacs.conversations.ui.XmppActivity;
|
||||||
import eu.siacs.conversations.ui.util.Attachment;
|
import eu.siacs.conversations.ui.util.Attachment;
|
||||||
import eu.siacs.conversations.ui.util.StyledAttributes;
|
import eu.siacs.conversations.ui.util.StyledAttributes;
|
||||||
|
@ -79,6 +80,8 @@ public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHol
|
||||||
attr = R.attr.media_preview_archive;
|
attr = R.attr.media_preview_archive;
|
||||||
} else if (mime.equals("application/epub+zip") || mime.equals("application/vnd.amazon.mobi8-ebook")) {
|
} else if (mime.equals("application/epub+zip") || mime.equals("application/vnd.amazon.mobi8-ebook")) {
|
||||||
attr = R.attr.media_preview_ebook;
|
attr = R.attr.media_preview_ebook;
|
||||||
|
} else if (mime.equals(ExportBackupService.MIME_TYPE)) {
|
||||||
|
attr = R.attr.media_preview_backup;
|
||||||
} else if (DOCUMENT_MIMES.contains(mime)) {
|
} else if (DOCUMENT_MIMES.contains(mime)) {
|
||||||
attr = R.attr.media_preview_document;
|
attr = R.attr.media_preview_document;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -39,13 +39,13 @@ import rocks.xmpp.addr.Jid;
|
||||||
|
|
||||||
public class JidHelper {
|
public class JidHelper {
|
||||||
|
|
||||||
private static List<String> LOCALPART_BLACKLIST = Arrays.asList("xmpp","jabber","me");
|
private static List<String> LOCAL_PART_BLACKLIST = Arrays.asList("xmpp", "jabber", "me");
|
||||||
|
|
||||||
public static String localPartOrFallback(Jid jid) {
|
public static String localPartOrFallback(Jid jid) {
|
||||||
if (LOCALPART_BLACKLIST.contains(jid.getLocal().toLowerCase(Locale.ENGLISH))) {
|
if (LOCAL_PART_BLACKLIST.contains(jid.getLocal().toLowerCase(Locale.ENGLISH))) {
|
||||||
final String domain = jid.getDomain();
|
final String domain = jid.getDomain();
|
||||||
final int index = domain.lastIndexOf('.');
|
final int index = domain.indexOf('.');
|
||||||
return index > 1 ? domain.substring(0,index) : domain;
|
return index > 1 ? domain.substring(0, index) : domain;
|
||||||
} else {
|
} else {
|
||||||
return jid.getLocal();
|
return jid.getLocal();
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import java.util.Properties;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.entities.Transferable;
|
import eu.siacs.conversations.entities.Transferable;
|
||||||
|
import eu.siacs.conversations.services.ExportBackupService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for dealing with MIME types.
|
* Utilities for dealing with MIME types.
|
||||||
|
@ -72,6 +73,7 @@ public final class MimeUtils {
|
||||||
add("application/vnd.amazon.mobi8-ebook","kfx");
|
add("application/vnd.amazon.mobi8-ebook","kfx");
|
||||||
add("application/vnd.android.package-archive", "apk");
|
add("application/vnd.android.package-archive", "apk");
|
||||||
add("application/vnd.cinderella", "cdy");
|
add("application/vnd.cinderella", "cdy");
|
||||||
|
add(ExportBackupService.MIME_TYPE, "ceb");
|
||||||
add("application/vnd.ms-pki.stl", "stl");
|
add("application/vnd.ms-pki.stl", "stl");
|
||||||
add("application/vnd.oasis.opendocument.database", "odb");
|
add("application/vnd.oasis.opendocument.database", "odb");
|
||||||
add("application/vnd.oasis.opendocument.formula", "odf");
|
add("application/vnd.oasis.opendocument.formula", "odf");
|
||||||
|
|
|
@ -32,6 +32,7 @@ import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.entities.MucOptions;
|
import eu.siacs.conversations.entities.MucOptions;
|
||||||
import eu.siacs.conversations.entities.Presence;
|
import eu.siacs.conversations.entities.Presence;
|
||||||
import eu.siacs.conversations.entities.Transferable;
|
import eu.siacs.conversations.entities.Transferable;
|
||||||
|
import eu.siacs.conversations.services.ExportBackupService;
|
||||||
import rocks.xmpp.addr.Jid;
|
import rocks.xmpp.addr.Jid;
|
||||||
|
|
||||||
public class UIHelper {
|
public class UIHelper {
|
||||||
|
@ -483,8 +484,12 @@ public class UIHelper {
|
||||||
return context.getString(R.string.pdf_document);
|
return context.getString(R.string.pdf_document);
|
||||||
} else if (mime.equals("application/vnd.android.package-archive")) {
|
} else if (mime.equals("application/vnd.android.package-archive")) {
|
||||||
return context.getString(R.string.apk);
|
return context.getString(R.string.apk);
|
||||||
|
} else if (mime.equals(ExportBackupService.MIME_TYPE)) {
|
||||||
|
return context.getString(R.string.conversations_backup);
|
||||||
} else if (mime.contains("vcard")) {
|
} else if (mime.contains("vcard")) {
|
||||||
return context.getString(R.string.vcard);
|
return context.getString(R.string.vcard);
|
||||||
|
} else if (mime.equals("text/x-vcalendar") || mime.equals("text/calendar")) {
|
||||||
|
return context.getString(R.string.event);
|
||||||
} else if (mime.equals("application/epub+zip") || mime.equals("application/vnd.amazon.mobi8-ebook")) {
|
} else if (mime.equals("application/epub+zip") || mime.equals("application/vnd.amazon.mobi8-ebook")) {
|
||||||
return context.getString(R.string.ebook);
|
return context.getString(R.string.ebook);
|
||||||
} else {
|
} else {
|
||||||
|
|
After Width: | Height: | Size: 561 B |
After Width: | Height: | Size: 589 B |
After Width: | Height: | Size: 353 B |
After Width: | Height: | Size: 386 B |
After Width: | Height: | Size: 405 B |
After Width: | Height: | Size: 242 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 770 B |
After Width: | Height: | Size: 417 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 610 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 789 B |
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_open_backup_file"
|
||||||
|
android:icon="?attr/ic_cloud_download"
|
||||||
|
app:showAsAction="always"
|
||||||
|
android:title="@string/open_backup"/>
|
||||||
|
|
||||||
|
</menu>
|
|
@ -17,6 +17,8 @@
|
||||||
<string name="action_unblock_contact">Desbloquear contacto</string>
|
<string name="action_unblock_contact">Desbloquear contacto</string>
|
||||||
<string name="action_block_domain">Bloquear dominio</string>
|
<string name="action_block_domain">Bloquear dominio</string>
|
||||||
<string name="action_unblock_domain">Desbloquear dominio</string>
|
<string name="action_unblock_domain">Desbloquear dominio</string>
|
||||||
|
<string name="action_block_participant">Bloquear participante</string>
|
||||||
|
<string name="action_unblock_participant">Desbloquear participante</string>
|
||||||
<string name="title_activity_manage_accounts">Gestionar Cuentas</string>
|
<string name="title_activity_manage_accounts">Gestionar Cuentas</string>
|
||||||
<string name="title_activity_settings">Ajustes</string>
|
<string name="title_activity_settings">Ajustes</string>
|
||||||
<string name="title_activity_sharewith">Compartir con Conversación</string>
|
<string name="title_activity_sharewith">Compartir con Conversación</string>
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
<string name="action_unblock_contact">Odblokuj kontakt</string>
|
<string name="action_unblock_contact">Odblokuj kontakt</string>
|
||||||
<string name="action_block_domain">Zablokuj domenę</string>
|
<string name="action_block_domain">Zablokuj domenę</string>
|
||||||
<string name="action_unblock_domain">Odblokuj domenę</string>
|
<string name="action_unblock_domain">Odblokuj domenę</string>
|
||||||
|
<string name="action_block_participant">Zablokuj użytkownika</string>
|
||||||
|
<string name="action_unblock_participant">Odblokuj użytkownika</string>
|
||||||
<string name="title_activity_manage_accounts">Zarządzaj kontami</string>
|
<string name="title_activity_manage_accounts">Zarządzaj kontami</string>
|
||||||
<string name="title_activity_settings">Ustawienia</string>
|
<string name="title_activity_settings">Ustawienia</string>
|
||||||
<string name="title_activity_sharewith">Udostępnij w konwersacji</string>
|
<string name="title_activity_sharewith">Udostępnij w konwersacji</string>
|
||||||
|
@ -205,8 +207,8 @@
|
||||||
<string name="openpgp_key_id">ID klucza OpenPGP</string>
|
<string name="openpgp_key_id">ID klucza OpenPGP</string>
|
||||||
<string name="omemo_fingerprint">Odcisk OMEMO</string>
|
<string name="omemo_fingerprint">Odcisk OMEMO</string>
|
||||||
<string name="omemo_fingerprint_x509">Odcisk v\\OMEMO</string>
|
<string name="omemo_fingerprint_x509">Odcisk v\\OMEMO</string>
|
||||||
<string name="omemo_fingerprint_selected_message">Odcisk OMEMO wiadomości</string>
|
<string name="omemo_fingerprint_selected_message">Odcisk OMEMO tej wiadomości</string>
|
||||||
<string name="omemo_fingerprint_x509_selected_message">Odcisk v\\OMEMO wiadomości</string>
|
<string name="omemo_fingerprint_x509_selected_message">Odcisk v\\OMEMO tej wiadomości</string>
|
||||||
<string name="other_devices">Pozostałe urządzenia</string>
|
<string name="other_devices">Pozostałe urządzenia</string>
|
||||||
<string name="trust_omemo_fingerprints">Zaufane odciski OMEMO</string>
|
<string name="trust_omemo_fingerprints">Zaufane odciski OMEMO</string>
|
||||||
<string name="fetching_keys">Pobieranie kluczy...</string>
|
<string name="fetching_keys">Pobieranie kluczy...</string>
|
||||||
|
|
|
@ -43,6 +43,9 @@
|
||||||
<attr name="ic_attach_photo" format="reference"/>
|
<attr name="ic_attach_photo" format="reference"/>
|
||||||
<attr name="ic_attach_record" format="reference"/>
|
<attr name="ic_attach_record" format="reference"/>
|
||||||
|
|
||||||
|
|
||||||
|
<attr name="ic_cloud_download" format="reference"/>
|
||||||
|
|
||||||
<attr name="message_bubble_received_monochrome" format="reference"/>
|
<attr name="message_bubble_received_monochrome" format="reference"/>
|
||||||
<attr name="message_bubble_sent" format="reference"/>
|
<attr name="message_bubble_sent" format="reference"/>
|
||||||
<attr name="message_bubble_received_green" format="reference"/>
|
<attr name="message_bubble_received_green" format="reference"/>
|
||||||
|
@ -63,6 +66,7 @@
|
||||||
<attr name="media_preview_calendar" format="reference"/>
|
<attr name="media_preview_calendar" format="reference"/>
|
||||||
<attr name="media_preview_archive" format="reference" />
|
<attr name="media_preview_archive" format="reference" />
|
||||||
<attr name="media_preview_ebook" format="reference"/>
|
<attr name="media_preview_ebook" format="reference"/>
|
||||||
|
<attr name="media_preview_backup" format="reference"/>
|
||||||
<attr name="media_preview_unknown" format="reference" />
|
<attr name="media_preview_unknown" format="reference" />
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -866,4 +866,11 @@
|
||||||
<string name="this_looks_like_a_domain">This looks like a domain address</string>
|
<string name="this_looks_like_a_domain">This looks like a domain address</string>
|
||||||
<string name="add_anway">Add anyway</string>
|
<string name="add_anway">Add anyway</string>
|
||||||
<string name="this_looks_like_channel">This looks like a channel address</string>
|
<string name="this_looks_like_channel">This looks like a channel address</string>
|
||||||
|
<string name="share_backup_files">Share backup files</string>
|
||||||
|
<string name="conversations_backup">Conversations backup</string>
|
||||||
|
<string name="event">Event</string>
|
||||||
|
<string name="open_backup">Open backup</string>
|
||||||
|
<string name="not_a_backup_file">The file you selected is not a Conversations backup file</string>
|
||||||
|
<string name="account_already_setup">This account has already been setup</string>
|
||||||
|
<string name="please_enter_password">Please enter the password for this account</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -74,6 +74,7 @@
|
||||||
<item type="reference" name="media_preview_calendar">@drawable/ic_event_black_48dp</item>
|
<item type="reference" name="media_preview_calendar">@drawable/ic_event_black_48dp</item>
|
||||||
<item type="reference" name="media_preview_archive">@drawable/ic_archive_black_48dp</item>
|
<item type="reference" name="media_preview_archive">@drawable/ic_archive_black_48dp</item>
|
||||||
<item type="reference" name="media_preview_ebook">@drawable/ic_book_black_48dp</item>
|
<item type="reference" name="media_preview_ebook">@drawable/ic_book_black_48dp</item>
|
||||||
|
<item type="reference" name="media_preview_backup">@drawable/ic_backup_black_48dp</item>
|
||||||
<item type="reference" name="media_preview_unknown">@drawable/ic_help_black_48dp</item>
|
<item type="reference" name="media_preview_unknown">@drawable/ic_help_black_48dp</item>
|
||||||
|
|
||||||
<item type="reference" name="icon_add_group">@drawable/ic_group_add_white_24dp</item>
|
<item type="reference" name="icon_add_group">@drawable/ic_group_add_white_24dp</item>
|
||||||
|
@ -97,6 +98,7 @@
|
||||||
<item type="reference" name="icon_secure">@drawable/ic_lock_open_white_24dp</item>
|
<item type="reference" name="icon_secure">@drawable/ic_lock_open_white_24dp</item>
|
||||||
<item type="reference" name="icon_settings">@drawable/ic_settings_black_24dp</item>
|
<item type="reference" name="icon_settings">@drawable/ic_settings_black_24dp</item>
|
||||||
<item type="reference" name="icon_share">@drawable/ic_share_white_24dp</item>
|
<item type="reference" name="icon_share">@drawable/ic_share_white_24dp</item>
|
||||||
|
<item type="reference" name="ic_cloud_download">@drawable/ic_cloud_download_white_24dp</item>
|
||||||
<item type="reference" name="icon_scan_qr_code">@drawable/ic_qr_code_scan_white_24dp</item>
|
<item type="reference" name="icon_scan_qr_code">@drawable/ic_qr_code_scan_white_24dp</item>
|
||||||
<item type="reference" name="icon_scroll_down">@drawable/ic_scroll_to_end_black</item>
|
<item type="reference" name="icon_scroll_down">@drawable/ic_scroll_to_end_black</item>
|
||||||
|
|
||||||
|
@ -187,6 +189,7 @@
|
||||||
<item type="reference" name="media_preview_calendar">@drawable/ic_event_white_48dp</item>
|
<item type="reference" name="media_preview_calendar">@drawable/ic_event_white_48dp</item>
|
||||||
<item type="reference" name="media_preview_archive">@drawable/ic_archive_white_48dp</item>
|
<item type="reference" name="media_preview_archive">@drawable/ic_archive_white_48dp</item>
|
||||||
<item type="reference" name="media_preview_ebook">@drawable/ic_book_white_48dp</item>
|
<item type="reference" name="media_preview_ebook">@drawable/ic_book_white_48dp</item>
|
||||||
|
<item type="reference" name="media_preview_backup">@drawable/ic_backup_white_48dp</item>
|
||||||
<item type="reference" name="media_preview_unknown">@drawable/ic_help_white_48dp</item>
|
<item type="reference" name="media_preview_unknown">@drawable/ic_help_white_48dp</item>
|
||||||
|
|
||||||
<item type="reference" name="icon_add_group">@drawable/ic_group_add_white_24dp</item>
|
<item type="reference" name="icon_add_group">@drawable/ic_group_add_white_24dp</item>
|
||||||
|
@ -210,6 +213,7 @@
|
||||||
<item type="reference" name="icon_secure">@drawable/ic_lock_open_white_24dp</item>
|
<item type="reference" name="icon_secure">@drawable/ic_lock_open_white_24dp</item>
|
||||||
<item type="reference" name="icon_settings">@drawable/ic_settings_white_24dp</item>
|
<item type="reference" name="icon_settings">@drawable/ic_settings_white_24dp</item>
|
||||||
<item type="reference" name="icon_share">@drawable/ic_share_white_24dp</item>
|
<item type="reference" name="icon_share">@drawable/ic_share_white_24dp</item>
|
||||||
|
<item type="reference" name="ic_cloud_download">@drawable/ic_cloud_download_white_24dp</item>
|
||||||
<item type="reference" name="icon_scan_qr_code">@drawable/ic_qr_code_scan_white_24dp</item>
|
<item type="reference" name="icon_scan_qr_code">@drawable/ic_qr_code_scan_white_24dp</item>
|
||||||
<item type="reference" name="icon_scroll_down">@drawable/ic_scroll_to_end_white</item>
|
<item type="reference" name="icon_scroll_down">@drawable/ic_scroll_to_end_white</item>
|
||||||
|
|
||||||
|
|
|
@ -151,7 +151,13 @@ public class PushManagementService {
|
||||||
if (!task.isSuccessful()) {
|
if (!task.isSuccessful()) {
|
||||||
Log.d(Config.LOGTAG, "unable to get Firebase instance token", task.getException());
|
Log.d(Config.LOGTAG, "unable to get Firebase instance token", task.getException());
|
||||||
}
|
}
|
||||||
final InstanceIdResult result = task.getResult();
|
final InstanceIdResult result;
|
||||||
|
try {
|
||||||
|
result = task.getResult();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.d(Config.LOGTAG, "unable to get Firebase instance token due to bug in library ", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
instanceTokenRetrieved.onGcmInstanceTokenRetrieved(result.getToken());
|
instanceTokenRetrieved.onGcmInstanceTokenRetrieved(result.getToken());
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,13 +119,19 @@ public class PinEntryWrapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEnabled(boolean enabled) {
|
public void setEnabled(final boolean enabled) {
|
||||||
for(EditText digit : digits) {
|
for (EditText digit : digits) {
|
||||||
digit.setEnabled(enabled);
|
digit.setEnabled(enabled);
|
||||||
digit.setCursorVisible(enabled);
|
digit.setCursorVisible(enabled);
|
||||||
digit.setFocusable(enabled);
|
digit.setFocusable(enabled);
|
||||||
digit.setFocusableInTouchMode(enabled);
|
digit.setFocusableInTouchMode(enabled);
|
||||||
}
|
}
|
||||||
|
if (enabled) {
|
||||||
|
final EditText last = digits.get(digits.size() - 1);
|
||||||
|
if (last.getEditableText().length() > 0) {
|
||||||
|
last.requestFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
|
|
|
@ -19,4 +19,5 @@
|
||||||
<string name="no_microphone_permission">Quicksy necesita acceder al micrófono</string>
|
<string name="no_microphone_permission">Quicksy necesita acceder al micrófono</string>
|
||||||
<string name="foreground_service_channel_description">Esta categoría de notificación se usa para mostrar una notificación permantente indicando que Quicksy está ejecutándose.</string>
|
<string name="foreground_service_channel_description">Esta categoría de notificación se usa para mostrar una notificación permantente indicando que Quicksy está ejecutándose.</string>
|
||||||
<string name="set_profile_picture">Foto de perfil en Quicksy</string>
|
<string name="set_profile_picture">Foto de perfil en Quicksy</string>
|
||||||
</resources>
|
<string name="not_available_in_your_country">Quicksy no está disponible en tu país.</string>
|
||||||
|
</resources>
|
||||||
|
|
|
@ -19,4 +19,5 @@
|
||||||
<string name="no_microphone_permission">Quicksy potrzebuje dostępu do mikrofonu.</string>
|
<string name="no_microphone_permission">Quicksy potrzebuje dostępu do mikrofonu.</string>
|
||||||
<string name="foreground_service_channel_description">Ta kategoria powiadomień jest używana do wyświetlania ciągłego powiadomienia o tym, że Quicksy działa.</string>
|
<string name="foreground_service_channel_description">Ta kategoria powiadomień jest używana do wyświetlania ciągłego powiadomienia o tym, że Quicksy działa.</string>
|
||||||
<string name="set_profile_picture">Obrazek profilowy Quicksy</string>
|
<string name="set_profile_picture">Obrazek profilowy Quicksy</string>
|
||||||
</resources>
|
<string name="not_available_in_your_country">Quicksy nie jest dostępne w twoim kraju</string>
|
||||||
|
</resources>
|
||||||
|
|