request READ_MEDIA_* permission when restoring backup on fdroid version

This commit is contained in:
Daniel Gultsch 2024-04-17 14:58:44 +02:00
parent 7088c1f507
commit 9cabc0262f
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
4 changed files with 182 additions and 71 deletions

View file

@ -58,6 +58,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
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;
@ -188,12 +189,7 @@ public class ImportBackupService extends Service {
} }
} }
Collections.sort( Collections.sort(
backupFiles, backupFiles, Comparator.comparing(a -> a.header.getJid().toString()));
(a, b) ->
a.header
.getJid()
.toString()
.compareTo(b.header.getJid().toString()));
onBackupFilesLoaded.onBackupFilesLoaded(backupFiles); onBackupFilesLoaded.onBackupFilesLoaded(backupFiles);
}); });
} }

View file

@ -1,11 +1,14 @@
package eu.siacs.conversations.ui; package eu.siacs.conversations.ui;
import android.Manifest;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.net.Uri; 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.util.Log; import android.util.Log;
@ -14,15 +17,16 @@ import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.databinding.DataBindingUtil; import androidx.databinding.DataBindingUtil;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import com.google.common.collect.ImmutableList;
import java.io.IOException; import com.google.common.collect.ImmutableSet;
import java.util.List;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; 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.databinding.DialogEnterPasswordBinding;
import eu.siacs.conversations.services.ImportBackupService; import eu.siacs.conversations.services.ImportBackupService;
import eu.siacs.conversations.ui.adapter.BackupFileAdapter; import eu.siacs.conversations.ui.adapter.BackupFileAdapter;
import eu.siacs.conversations.ui.util.SettingsUtils;
import eu.siacs.conversations.utils.BackupFileHeader; 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; private ActivityImportBackupBinding binding;
@ -41,8 +53,18 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo
private ImportBackupService service; private ImportBackupService service;
private boolean mLoadingState = false; private boolean mLoadingState = false;
private final ActivityResultLauncher<String[]> requestPermissions =
private int mTheme; registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(),
results -> {
if (results.containsValue(Boolean.TRUE)) {
final var service = this.service;
if (service == null) {
return;
}
service.loadBackupFiles(this);
}
});
@Override @Override
protected void onCreate(final Bundle savedInstanceState) { 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); binding = DataBindingUtil.setContentView(this, R.layout.activity_import_backup);
Activities.setStatusAndNavigationBarColors(this, binding.getRoot()); Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
setSupportActionBar(binding.toolbar); setSupportActionBar(binding.toolbar);
setLoadingState(savedInstanceState != null && savedInstanceState.getBoolean("loading_state", false)); 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);
@ -72,15 +96,55 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
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(); 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(); Uri uri = intent.getData();
if (uri != null) { if (uri != null) {
openBackupFileFromUri(uri, true); openBackupFileFromUri(uri, true);
return;
} }
} }
final List<String> 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<String> 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<String> 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 @Override
@ -94,7 +158,8 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo
@Override @Override
public void onServiceConnected(ComponentName name, IBinder service) { 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 = binder.getService();
this.service.addOnBackupProcessedListener(this); this.service.addOnBackupProcessedListener(this);
setLoadingState(this.service.getLoadingState()); setLoadingState(this.service.getLoadingState());
@ -118,55 +183,81 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo
private void openBackupFileFromUri(final Uri uri, final boolean finishOnCancel) { private void openBackupFileFromUri(final Uri uri, final boolean finishOnCancel) {
try { try {
final ImportBackupService.BackupFile backupFile = ImportBackupService.BackupFile.read(this, uri); final ImportBackupService.BackupFile backupFile =
ImportBackupService.BackupFile.read(this, uri);
showEnterPasswordDialog(backupFile, finishOnCancel); showEnterPasswordDialog(backupFile, finishOnCancel);
} catch (final BackupFileHeader.OutdatedBackupFileVersion e) { } 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) { } catch (final IOException | IllegalArgumentException e) {
Log.d(Config.LOGTAG, "unable to open backup file " + uri, 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) { } 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) { private void showEnterPasswordDialog(
final DialogEnterPasswordBinding enterPasswordBinding = DataBindingUtil.inflate(LayoutInflater.from(this), R.layout.dialog_enter_password, null, false); 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()); 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); final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(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, (dialog, which) -> { builder.setNegativeButton(
if (finishOnCancel) { R.string.cancel,
finish(); (dialog, which) -> {
} if (finishOnCancel) {
}); finish();
}
});
builder.setPositiveButton(R.string.restore, null); builder.setPositiveButton(R.string.restore, null);
builder.setCancelable(false); builder.setCancelable(false);
final AlertDialog dialog = builder.create(); final AlertDialog dialog = builder.create();
dialog.setOnShowListener((d) -> { dialog.setOnShowListener(
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { (d) -> {
final String password = enterPasswordBinding.accountPassword.getEditableText().toString(); dialog.getButton(DialogInterface.BUTTON_POSITIVE)
if (password.isEmpty()) { .setOnClickListener(
enterPasswordBinding.accountPasswordLayout.setError(getString(R.string.please_enter_password)); v -> {
return; final String password =
} enterPasswordBinding
final Uri uri = backupFile.getUri(); .accountPassword
Intent intent = new Intent(this, ImportBackupService.class); .getEditableText()
intent.setAction(Intent.ACTION_SEND); .toString();
intent.putExtra("password", password); if (password.isEmpty()) {
if ("file".equals(uri.getScheme())) { enterPasswordBinding.accountPasswordLayout.setError(
intent.putExtra("file", uri.getPath()); getString(R.string.please_enter_password));
} else { return;
intent.setData(uri); }
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); final Uri uri = backupFile.getUri();
} Intent intent = new Intent(this, ImportBackupService.class);
setLoadingState(true); intent.setAction(Intent.ACTION_SEND);
ContextCompat.startForegroundService(this, intent); intent.putExtra("password", password);
d.dismiss(); 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(); dialog.show();
} }
@ -192,36 +283,55 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo
@Override @Override
public void onAccountAlreadySetup() { public void onAccountAlreadySetup() {
runOnUiThread(() -> { runOnUiThread(
setLoadingState(false); () -> {
Snackbar.make(binding.coordinator, R.string.account_already_setup, Snackbar.LENGTH_LONG).show(); setLoadingState(false);
}); Snackbar.make(
binding.coordinator,
R.string.account_already_setup,
Snackbar.LENGTH_LONG)
.show();
});
} }
@Override @Override
public void onBackupRestored() { public void onBackupRestored() {
runOnUiThread(() -> { 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); Intent intent = new Intent(this, ConversationActivity.class);
startActivity(intent); intent.addFlags(
finish(); Intent.FLAG_ACTIVITY_CLEAR_TOP
}); | Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
});
} }
@Override @Override
public void onBackupDecryptionFailed() { public void onBackupDecryptionFailed() {
runOnUiThread(() -> { runOnUiThread(
setLoadingState(false); () -> {
Snackbar.make(binding.coordinator, R.string.unable_to_decrypt_backup, Snackbar.LENGTH_LONG).show(); setLoadingState(false);
}); 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); () -> {
Snackbar.make(binding.coordinator, R.string.unable_to_restore_backup, Snackbar.LENGTH_LONG).show(); setLoadingState(false);
}); Snackbar.make(
binding.coordinator,
R.string.unable_to_restore_backup,
Snackbar.LENGTH_LONG)
.show();
});
} }
@Override @Override
@ -238,6 +348,7 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo
intent.setType("*/*"); intent.setType("*/*");
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
intent.addCategory(Intent.CATEGORY_OPENABLE); 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);
} }
} }

View file

@ -3,4 +3,9 @@
<uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_PROFILE" /> <uses-permission android:name="android.permission.READ_PROFILE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED"/>
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
</manifest> </manifest>

View file

@ -769,8 +769,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
} }
private void askForContactsPermissions() { private void askForContactsPermissions() {
if (QuickConversationsService.isContactListIntegration(this) if (QuickConversationsService.isContactListIntegration(this)) {
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.READ_CONTACTS) if (checkSelfPermission(Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) { != PackageManager.PERMISSION_GRANTED) {
if (mRequestedContactsPermission.compareAndSet(false, true)) { if (mRequestedContactsPermission.compareAndSet(false, true)) {