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 @@ - -