diff --git a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java index 334962df0..4c7387200 100644 --- a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java +++ b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java @@ -58,6 +58,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -188,12 +189,7 @@ public class ImportBackupService extends Service { } } Collections.sort( - backupFiles, - (a, b) -> - a.header - .getJid() - .toString() - .compareTo(b.header.getJid().toString())); + backupFiles, Comparator.comparing(a -> a.header.getJid().toString())); onBackupFilesLoaded.onBackupFilesLoaded(backupFiles); }); } diff --git a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java index 1f1040c09..3cbde4d5a 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java @@ -1,11 +1,14 @@ package eu.siacs.conversations.ui; +import android.Manifest; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.util.Log; @@ -14,15 +17,16 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; import androidx.databinding.DataBindingUtil; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; - -import java.io.IOException; -import java.util.List; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -30,10 +34,18 @@ import eu.siacs.conversations.databinding.ActivityImportBackupBinding; import eu.siacs.conversations.databinding.DialogEnterPasswordBinding; import eu.siacs.conversations.services.ImportBackupService; import eu.siacs.conversations.ui.adapter.BackupFileAdapter; -import eu.siacs.conversations.ui.util.SettingsUtils; import eu.siacs.conversations.utils.BackupFileHeader; -public class ImportBackupActivity extends ActionBarActivity implements ServiceConnection, ImportBackupService.OnBackupFilesLoaded, BackupFileAdapter.OnItemClickedListener, ImportBackupService.OnBackupProcessed { +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class ImportBackupActivity extends ActionBarActivity + implements ServiceConnection, + ImportBackupService.OnBackupFilesLoaded, + BackupFileAdapter.OnItemClickedListener, + ImportBackupService.OnBackupProcessed { private ActivityImportBackupBinding binding; @@ -41,8 +53,18 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo private ImportBackupService service; private boolean mLoadingState = false; - - private int mTheme; + private final ActivityResultLauncher requestPermissions = + registerForActivityResult( + new ActivityResultContracts.RequestMultiplePermissions(), + results -> { + if (results.containsValue(Boolean.TRUE)) { + final var service = this.service; + if (service == null) { + return; + } + service.loadBackupFiles(this); + } + }); @Override protected void onCreate(final Bundle savedInstanceState) { @@ -50,7 +72,9 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo binding = DataBindingUtil.setContentView(this, R.layout.activity_import_backup); Activities.setStatusAndNavigationBarColors(this, binding.getRoot()); setSupportActionBar(binding.toolbar); - setLoadingState(savedInstanceState != null && savedInstanceState.getBoolean("loading_state", false)); + setLoadingState( + savedInstanceState != null + && savedInstanceState.getBoolean("loading_state", false)); this.backupFileAdapter = new BackupFileAdapter(); this.binding.list.setAdapter(this.backupFileAdapter); this.backupFileAdapter.setOnItemClickedListener(this); @@ -72,15 +96,55 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo @Override public void onStart() { + super.onStart(); 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) { + if (intent != null + && Intent.ACTION_VIEW.equals(intent.getAction()) + && !this.mLoadingState) { Uri uri = intent.getData(); if (uri != null) { openBackupFileFromUri(uri, true); + return; } } + final List desiredPermission; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + desiredPermission = + ImmutableList.of( + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.READ_MEDIA_AUDIO, + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED); + } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU) { + desiredPermission = + ImmutableList.of( + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.READ_MEDIA_AUDIO); + } else { + desiredPermission = ImmutableList.of(Manifest.permission.READ_EXTERNAL_STORAGE); + } + final Set declaredPermission = getDeclaredPermission(); + if (declaredPermission.containsAll(desiredPermission)) { + requestPermissions.launch(desiredPermission.toArray(new String[0])); + } else { + Log.d(Config.LOGTAG, "Manifest is lacking some desired permission. not requesting"); + } + } + + private Set getDeclaredPermission() { + final String[] permissions; + try { + permissions = + getPackageManager() + .getPackageInfo(getPackageName(), PackageManager.GET_PERMISSIONS) + .requestedPermissions; + } catch (final PackageManager.NameNotFoundException e) { + return Collections.emptySet(); + } + return ImmutableSet.copyOf(permissions); } @Override @@ -94,7 +158,8 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo @Override public void onServiceConnected(ComponentName name, IBinder service) { - ImportBackupService.ImportBackupServiceBinder binder = (ImportBackupService.ImportBackupServiceBinder) service; + ImportBackupService.ImportBackupServiceBinder binder = + (ImportBackupService.ImportBackupServiceBinder) service; this.service = binder.getService(); this.service.addOnBackupProcessedListener(this); setLoadingState(this.service.getLoadingState()); @@ -118,55 +183,81 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo private void openBackupFileFromUri(final Uri uri, final boolean finishOnCancel) { try { - final ImportBackupService.BackupFile backupFile = ImportBackupService.BackupFile.read(this, uri); + final ImportBackupService.BackupFile backupFile = + ImportBackupService.BackupFile.read(this, uri); showEnterPasswordDialog(backupFile, finishOnCancel); } catch (final BackupFileHeader.OutdatedBackupFileVersion e) { - Snackbar.make(binding.coordinator, R.string.outdated_backup_file_format, Snackbar.LENGTH_LONG).show(); + Snackbar.make( + binding.coordinator, + R.string.outdated_backup_file_format, + Snackbar.LENGTH_LONG) + .show(); } catch (final IOException | IllegalArgumentException e) { Log.d(Config.LOGTAG, "unable to open backup file " + uri, e); - Snackbar.make(binding.coordinator, R.string.not_a_backup_file, Snackbar.LENGTH_LONG).show(); + Snackbar.make(binding.coordinator, R.string.not_a_backup_file, Snackbar.LENGTH_LONG) + .show(); } catch (final SecurityException e) { - Snackbar.make(binding.coordinator, R.string.sharing_application_not_grant_permission, Snackbar.LENGTH_LONG).show(); + Snackbar.make( + binding.coordinator, + R.string.sharing_application_not_grant_permission, + 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); + 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); 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())); final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); builder.setView(enterPasswordBinding.getRoot()); builder.setTitle(R.string.enter_password); - builder.setNegativeButton(R.string.cancel, (dialog, which) -> { - if (finishOnCancel) { - finish(); - } - }); + builder.setNegativeButton( + R.string.cancel, + (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(); - 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.setAction(Intent.ACTION_SEND); - intent.putExtra("password", password); - if ("file".equals(uri.getScheme())) { - intent.putExtra("file", uri.getPath()); - } else { - intent.setData(uri); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - setLoadingState(true); - ContextCompat.startForegroundService(this, intent); - d.dismiss(); - }); - }); + dialog.setOnShowListener( + (d) -> { + dialog.getButton(DialogInterface.BUTTON_POSITIVE) + .setOnClickListener( + v -> { + 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.setAction(Intent.ACTION_SEND); + intent.putExtra("password", password); + if ("file".equals(uri.getScheme())) { + intent.putExtra("file", uri.getPath()); + } else { + intent.setData(uri); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + setLoadingState(true); + ContextCompat.startForegroundService(this, intent); + d.dismiss(); + }); + }); dialog.show(); } @@ -192,36 +283,55 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo @Override public void onAccountAlreadySetup() { - runOnUiThread(() -> { - setLoadingState(false); - Snackbar.make(binding.coordinator, R.string.account_already_setup, Snackbar.LENGTH_LONG).show(); - }); + runOnUiThread( + () -> { + setLoadingState(false); + Snackbar.make( + binding.coordinator, + R.string.account_already_setup, + Snackbar.LENGTH_LONG) + .show(); + }); } @Override public void onBackupRestored() { - runOnUiThread(() -> { - Intent intent = new Intent(this, ConversationActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - startActivity(intent); - finish(); - }); + runOnUiThread( + () -> { + Intent intent = new Intent(this, ConversationActivity.class); + intent.addFlags( + Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + finish(); + }); } @Override public void onBackupDecryptionFailed() { - runOnUiThread(() -> { - setLoadingState(false); - Snackbar.make(binding.coordinator, R.string.unable_to_decrypt_backup, Snackbar.LENGTH_LONG).show(); - }); + runOnUiThread( + () -> { + setLoadingState(false); + Snackbar.make( + binding.coordinator, + R.string.unable_to_decrypt_backup, + Snackbar.LENGTH_LONG) + .show(); + }); } @Override public void onBackupRestoreFailed() { - runOnUiThread(() -> { - setLoadingState(false); - Snackbar.make(binding.coordinator, R.string.unable_to_restore_backup, Snackbar.LENGTH_LONG).show(); - }); + runOnUiThread( + () -> { + setLoadingState(false); + Snackbar.make( + binding.coordinator, + R.string.unable_to_restore_backup, + Snackbar.LENGTH_LONG) + .show(); + }); } @Override @@ -238,6 +348,7 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo intent.setType("*/*"); intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); intent.addCategory(Intent.CATEGORY_OPENABLE); - startActivityForResult(Intent.createChooser(intent, getString(R.string.open_backup)), 0xbac); + startActivityForResult( + Intent.createChooser(intent, getString(R.string.open_backup)), 0xbac); } } diff --git a/src/free/AndroidManifest.xml b/src/free/AndroidManifest.xml index b127401a9..515402c5d 100644 --- a/src/free/AndroidManifest.xml +++ b/src/free/AndroidManifest.xml @@ -3,4 +3,9 @@ + + + + + diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index 8f1b4ad25..8c29ec08e 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -769,8 +769,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } private void askForContactsPermissions() { - if (QuickConversationsService.isContactListIntegration(this) - && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (QuickConversationsService.isContactListIntegration(this)) { if (checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { if (mRequestedContactsPermission.compareAndSet(false, true)) {