diff --git a/build.gradle b/build.gradle
index ea3039e90..f8e89e30b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -74,7 +74,6 @@ dependencies {
implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0'
implementation 'com.google.android.exoplayer:exoplayer-core:2.15.0'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.15.0'
- implementation 'pub.devrel:easypermissions:3.0.0' // version >= 3.0.0 needs android X libraries
implementation 'com.wefika:flowlayout:0.4.1'
implementation 'com.googlecode.ez-vcard:ez-vcard:0.10.5'
implementation 'org.jxmpp:jxmpp-jid:1.0.1'
@@ -106,11 +105,11 @@ android {
}
}
- compileSdkVersion 29
+ compileSdkVersion 30
defaultConfig {
minSdkVersion 21
- targetSdkVersion 29
+ targetSdkVersion 30
//versionNameSuffix " beta_(2021-12-08)" // " beta_(XXXX-XX-XX)" // activate for beta versions
versionCode 109
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index c0747cd13..a06347a38 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -5,6 +5,7 @@
+
@@ -94,7 +95,7 @@
finish());
+ builder.setPositiveButton(R.string.grant, (dialog, which) -> {
+ permissionCallbacks[STORAGE_PERMISSION] = onPermissionGranted;
+ ActivityCompat.requestPermissions(PermissionsActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_PERMISSION);
+ });
+ builder.setOnCancelListener(dialog -> finish());
+ final AlertDialog dialog = builder.create();
+ dialog.setCanceledOnTouchOutside(false);
+ dialog.setCancelable(false);
+ dialog.show();
+ }
+
+ public void requestAllFilesAccess(@NonNull final PermissionsActivity.OnPermissionGranted onPermissionGranted) {
+ if (Compatibility.runsAndTargetsThirty(this) && !Environment.isExternalStorageManager()) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.intro_required_permissions);
+ builder.setMessage(getString(R.string.no_manage_storage_permission));
+ builder.setNegativeButton(R.string.cancel, (dialog, which) -> finish());
+ builder.setPositiveButton(R.string.grant, (dialog, which) -> {
+ permissionCallbacks[ALL_FILES_PERMISSION] = onPermissionGranted;
+ try {
+ Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
+ Uri uri = Uri.fromParts("package", getPackageName(), null);
+ intent.setData(uri);
+ startActivityForResult(intent, ALL_FILES_PERMISSION);
+ } catch (Exception e) {
+ Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
+ Uri uri = Uri.fromParts("package", getPackageName(), null);
+ intent.setData(uri);
+ startActivityForResult(intent, ALL_FILES_PERMISSION);
+ }
+ });
+ builder.setOnCancelListener(dialog -> finish());
+ final AlertDialog dialog = builder.create();
+ dialog.setCanceledOnTouchOutside(false);
+ dialog.setCancelable(false);
+ dialog.show();
+ } else {
+ StartUI.next(this);
+ }
+ }
+
+ public interface OnPermissionGranted {
+ void onPermissionGranted();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/StartUI.java b/src/main/java/de/pixart/messenger/ui/StartUI.java
index 8c339a93e..8c958449f 100644
--- a/src/main/java/de/pixart/messenger/ui/StartUI.java
+++ b/src/main/java/de/pixart/messenger/ui/StartUI.java
@@ -1,115 +1,66 @@
-package de.monocles.chat.ui;
+package de.pixart.messenger.ui;
-import android.Manifest;
+import android.app.Activity;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.res.Configuration;
-import android.net.Uri;
import android.os.Bundle;
-import android.provider.Settings;
-import android.util.Log;
-import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.app.AppCompatActivity;
-import java.util.List;
-
-import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.ui.ConversationsActivity;
import eu.siacs.conversations.ui.util.IntroHelper;
-import pub.devrel.easypermissions.AfterPermissionGranted;
-import pub.devrel.easypermissions.EasyPermissions;
-public class StartUI extends AppCompatActivity
- implements EasyPermissions.PermissionCallbacks {
-
- private static final int NeededPermissions = 1000;
- String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE,
- Manifest.permission.WRITE_EXTERNAL_STORAGE,
- };
+import eu.siacs.conversations.utils.Compatibility;
+import eu.siacs.conversations.utils.ThemeHelper;
+ public class StartUI extends PermissionsActivity
+ implements PermissionsActivity.OnPermissionGranted {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_start_ui);
+ setTheme(ThemeHelper.findDialog(this));
IntroHelper.showIntro(this, false);
- requestNeededPermissions();
- }
-
- @AfterPermissionGranted(NeededPermissions)
- private void requestNeededPermissions() {
- String PREF_FIRST_START = "FirstStart";
- SharedPreferences FirstStart = getApplicationContext().getSharedPreferences(PREF_FIRST_START, Context.MODE_PRIVATE);
- long FirstStartTime = FirstStart.getLong(PREF_FIRST_START, 0);
- if (EasyPermissions.hasPermissions(this, perms)) {
- // Already have permission, start ConversationsActivity
- Log.d(Config.LOGTAG, "All permissions granted, starting " + getString(R.string.app_name) + "(" + FirstStartTime + ")");
- Intent intent = new Intent(this, ConversationsActivity.class);
- intent.putExtra(PREF_FIRST_START, FirstStartTime);
- startActivity(intent);
- overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
- finish();
- } else {
- // set first start to 0 if there are permissions to request
- Log.d(Config.LOGTAG, "Requesting required permissions");
- SharedPreferences.Editor editor = FirstStart.edit();
- editor.putLong(PREF_FIRST_START, 0);
- editor.commit();
- // Do not have permissions, request them now
- EasyPermissions.requestPermissions(this, getString(R.string.request_permissions_message),
- NeededPermissions, perms);
- }
}
@Override
- public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- // Forward results to EasyPermissions
- EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
+ protected void onStart() {
+ super.onStart();
+ requestNeededPermissions();
}
- @Override
- public void onPermissionsGranted(int requestCode, List list) {
- Log.d(Config.LOGTAG, "Permissions granted:" + requestCode);
- }
-
- @Override
- public void onPermissionsDenied(int requestCode, List list) {
- Log.d(Config.LOGTAG, "Permissions denied:" + requestCode);
- final AlertDialog dialog = new AlertDialog.Builder(this)
- .setMessage(getString(R.string.request_permissions_message_again))
- .setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
- Uri uri = Uri.fromParts("package", getPackageName(), null);
- intent.setData(uri);
- startActivity(intent);
- overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
- }
- })
- .setNegativeButton(getString(R.string.no), new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- finish();
- }
- })
- .create();
- dialog.show();
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
+ private void requestNeededPermissions() {
+ if (Compatibility.runsTwentyThree()) {
+ if (!checkStoragePermission()) {
+ requestStoragePermission(this);
+ }
+ if (Compatibility.runsAndTargetsThirty(this)) {
+ requestAllFilesAccess(this);
+ }
+ }
}
@Override
protected void onDestroy() {
super.onDestroy();
}
+
+ @Override
+ public void onPermissionGranted() {
+ next(this);
+ }
+
+ public static void next(final Activity activity) {
+ String PREF_FIRST_START = "FirstStart";
+ SharedPreferences FirstStart = activity.getSharedPreferences(PREF_FIRST_START, Context.MODE_PRIVATE);
+ long FirstStartTime = FirstStart.getLong(PREF_FIRST_START, 0);
+ Intent intent = new Intent(activity, ConversationsActivity.class);
+ intent.putExtra(PREF_FIRST_START, FirstStartTime);
+ activity.startActivity(intent);
+ activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
+ activity.finish();
+ }
}
\ No newline at end of file
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java
index 4dad50c15..0c583dae3 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java
@@ -150,7 +150,7 @@ public class XmppAxolotlMessage {
try {
SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
- Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
+ Cipher cipher = Compatibility.runsTwentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
this.ciphertext = cipher.doFinal(Config.OMEMO_PADDING ? getPaddedBytes(plaintext) : plaintext.getBytes());
if (Config.PUT_AUTH_TAG_INTO_KEY && this.ciphertext != null) {
@@ -255,7 +255,7 @@ public class XmppAxolotlMessage {
ciphertext = newCipherText;
key = newKey;
- final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
+ final Cipher cipher = Compatibility.runsTwentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
diff --git a/src/main/java/eu/siacs/conversations/services/ExportBackupService.java b/src/main/java/eu/siacs/conversations/services/ExportBackupService.java
index b52ccbc52..d1e87ab27 100644
--- a/src/main/java/eu/siacs/conversations/services/ExportBackupService.java
+++ b/src/main/java/eu/siacs/conversations/services/ExportBackupService.java
@@ -367,7 +367,7 @@ public class ExportBackupService extends Service {
backupFileHeader.write(dataOutputStream);
dataOutputStream.flush();
- final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
+ final Cipher cipher = Compatibility.runsTwentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
final byte[] key = getKey(password, salt);
Log.d(Config.LOGTAG, backupFileHeader.toString());
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
index d499a257c..a88399de7 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
@@ -1862,6 +1862,11 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (Config.ONLY_INTERNAL_STORAGE && permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE) && permission.equals(Manifest.permission.READ_EXTERNAL_STORAGE)) {
continue;
}
+ if (Compatibility.runsAndTargetsThirty(activity)) {
+ if (Config.ONLY_INTERNAL_STORAGE && permission.equals(Manifest.permission.MANAGE_EXTERNAL_STORAGE)) {
+ continue;
+ }
+ }
if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
missingPermissions.add(permission);
}
diff --git a/src/main/java/eu/siacs/conversations/utils/Compatibility.java b/src/main/java/eu/siacs/conversations/utils/Compatibility.java
index fa85c34a1..75f2e7eac 100644
--- a/src/main/java/eu/siacs/conversations/utils/Compatibility.java
+++ b/src/main/java/eu/siacs/conversations/utils/Compatibility.java
@@ -59,14 +59,22 @@ public class Compatibility {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
+ public static boolean runsTwentyThree() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
+ }
+
public static boolean runsTwentyFour() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
}
- public static boolean twentyEight() {
+ public static boolean runsTwentyEight() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
}
+ public static boolean runsThirty() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
+ }
+
private static boolean getBooleanPreference(Context context, String name, @BoolRes int res) {
return getPreferences(context).getBoolean(name, context.getResources().getBoolean(res));
}
@@ -86,6 +94,17 @@ public class Compatibility {
return true; //when in doubt…
}
}
+ private static boolean targetsThirty(Context context) {
+ try {
+ final PackageManager packageManager = context.getPackageManager();
+ final ApplicationInfo applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), 0);
+ return applicationInfo == null || applicationInfo.targetSdkVersion >= 30;
+ } catch (PackageManager.NameNotFoundException e) {
+ return true; //when in doubt…
+ } catch (RuntimeException e) {
+ return true; //when in doubt…
+ }
+ }
private static boolean targetsTwentyFour(Context context) {
try {
@@ -98,13 +117,16 @@ public class Compatibility {
return true; //when in doubt…
}
}
+ public static boolean runsAndTargetsTwentyFour(Context context) {
+ return runsTwentyFour() && targetsTwentyFour(context);
+ }
public static boolean runsAndTargetsTwentySix(Context context) {
return runsTwentySix() && targetsTwentySix(context);
}
- public static boolean runsAndTargetsTwentyFour(Context context) {
- return runsTwentyFour() && targetsTwentyFour(context);
+ public static boolean runsAndTargetsThirty(Context context) {
+ return runsThirty() && targetsThirty(context);
}
public static boolean keepForegroundService(Context context) {
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 57de32c94..14881c3e2 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -1162,4 +1162,6 @@
Plain text document
Import app settings from settings.dat file within the monocles chat backup directory.
Import app settings
+ Grant
+ Since Android 11, apps needs a special permission to access all file types on the external storage. monocles chat will ask you to grant this permission to continue.
diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml
index 51088037f..73a6062ae 100644
--- a/src/main/res/values/styles.xml
+++ b/src/main/res/values/styles.xml
@@ -163,6 +163,10 @@
- @style/Widget.Conversations.Button.Borderless
+
+
diff --git a/src/main/res/values/themes.xml b/src/main/res/values/themes.xml
index 72b092167..ba9b20481 100644
--- a/src/main/res/values/themes.xml
+++ b/src/main/res/values/themes.xml
@@ -373,7 +373,7 @@
-
-