Prepare notification permissions

pull/208/head
M66B 3 years ago
parent 5b668cb5d0
commit 45b5afca61

@ -6,6 +6,7 @@
* Added searching for text in draft * Added searching for text in draft
* Added OAuth for Gmail POP3 accounts * Added OAuth for Gmail POP3 accounts
* Android 13 compatibility (notification permissions)
* Small improvements and minor bug fixes * Small improvements and minor bug fixes
* Updated translations * Updated translations

@ -353,6 +353,7 @@ The following Android permissions are **required**:
* *prevent device from sleeping* (WAKE_LOCK): to keep the device awake while performing actions, like synchronization of messages * *prevent device from sleeping* (WAKE_LOCK): to keep the device awake while performing actions, like synchronization of messages
* *use fingerprint hardware* (USE_FINGERPRINT) and *use biometric hardware* (USE_BIOMETRIC): to use biometric authentication (fingerprint, face unlock, etc) * *use fingerprint hardware* (USE_FINGERPRINT) and *use biometric hardware* (USE_BIOMETRIC): to use biometric authentication (fingerprint, face unlock, etc)
* *ask to ingore battery optimizations* (REQUEST_IGNORE_BATTERY_OPTIMIZATIONS): to disable battery optimizations, please see [this FAQ](#user-content-faq175) for more information * *ask to ingore battery optimizations* (REQUEST_IGNORE_BATTERY_OPTIMIZATIONS): to disable battery optimizations, please see [this FAQ](#user-content-faq175) for more information
* *allow the app to show notifications* (POST_NOTIFICATIONS): to show new message notifications and (account) warnings and errors (Android 13 and later only)
* *Google Play (in-app) billing service* (BILLING): for in-app purchases * *Google Play (in-app) billing service* (BILLING): for in-app purchases
<br /> <br />

@ -23,7 +23,7 @@ android {
defaultConfig { defaultConfig {
applicationId "eu.faircode.email" applicationId "eu.faircode.email"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 32 targetSdkVersion 33
versionCode getVersionCode() versionCode getVersionCode()
versionName "1." + getVersionCode() versionName "1." + getVersionCode()
archivesBaseName = "FairEmail-v$versionName" + getRevision() archivesBaseName = "FairEmail-v$versionName" + getRevision()

@ -13,6 +13,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" /> <uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="com.android.vending.BILLING" /> <uses-permission android:name="com.android.vending.BILLING" />

@ -6,6 +6,7 @@
* Added searching for text in draft * Added searching for text in draft
* Added OAuth for Gmail POP3 accounts * Added OAuth for Gmail POP3 accounts
* Android 13 compatibility (notification permissions)
* Small improvements and minor bug fixes * Small improvements and minor bug fixes
* Updated translations * Updated translations

@ -1638,9 +1638,10 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
try { try {
NotificationManager nm = NotificationManager nm =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); Helper.getSystemService(ActivityView.this, NotificationManager.class);
nm.notify(NotificationHelper.NOTIFICATION_UPDATE, if (NotificationHelper.areNotificationsEnabled(nm))
builder.build()); nm.notify(NotificationHelper.NOTIFICATION_UPDATE,
builder.build());
} catch (Throwable ex) { } catch (Throwable ex) {
Log.w(ex); Log.w(ex);
} }

@ -720,9 +720,10 @@ class Core {
if (title != null) { if (title != null) {
NotificationCompat.Builder builder = NotificationCompat.Builder builder =
getNotificationError(context, "warning", account, message.id, new Throwable(title, ex)); getNotificationError(context, "warning", account, message.id, new Throwable(title, ex));
nm.notify(op.name + ":" + op.message, if (NotificationHelper.areNotificationsEnabled(nm))
NotificationHelper.NOTIFICATION_TAGGED, nm.notify(op.name + ":" + op.message,
builder.build()); NotificationHelper.NOTIFICATION_TAGGED,
builder.build());
} }
} }
@ -5134,7 +5135,8 @@ class Core {
: " channel=" + notification.getChannelId()) + : " channel=" + notification.getChannelId()) +
" sort=" + notification.getSortKey()); " sort=" + notification.getSortKey());
try { try {
nm.notify(tag, NotificationHelper.NOTIFICATION_TAGGED, notification); if (NotificationHelper.areNotificationsEnabled(nm))
nm.notify(tag, NotificationHelper.NOTIFICATION_TAGGED, notification);
// https://github.com/leolin310148/ShortcutBadger/wiki/Xiaomi-Device-Support // https://github.com/leolin310148/ShortcutBadger/wiki/Xiaomi-Device-Support
if (id == 0 && badge && Helper.isXiaomi()) if (id == 0 && badge && Helper.isXiaomi())
ShortcutBadger.applyNotification(context, notification, current); ShortcutBadger.applyNotification(context, notification, current);

@ -1245,7 +1245,8 @@ public class FragmentFolders extends FragmentBase {
builder.setProgress(ids.size(), i, false); builder.setProgress(ids.size(), i, false);
Notification notification = builder.build(); Notification notification = builder.build();
notification.flags |= Notification.FLAG_NO_CLEAR; notification.flags |= Notification.FLAG_NO_CLEAR;
nm.notify("export", NotificationHelper.NOTIFICATION_TAGGED, notification); if (NotificationHelper.areNotificationsEnabled(nm))
nm.notify("export", NotificationHelper.NOTIFICATION_TAGGED, notification);
} }
long id = ids.get(i); long id = ids.get(i);

@ -622,7 +622,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
swCheckWeekly.setEnabled(checked); swCheckWeekly.setEnabled(checked);
if (!checked) { if (!checked) {
NotificationManager nm = NotificationManager nm =
(NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE); Helper.getSystemService(getContext(), NotificationManager.class);
nm.cancel(NotificationHelper.NOTIFICATION_UPDATE); nm.cancel(NotificationHelper.NOTIFICATION_UPDATE);
} }
} }
@ -1550,7 +1550,8 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
if (!Helper.isPlayStoreInstall() && if (!Helper.isPlayStoreInstall() &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager nm = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE); NotificationManager nm =
Helper.getSystemService(getContext(), NotificationManager.class);
NotificationChannel notification = nm.getNotificationChannel("update"); NotificationChannel notification = nm.getNotificationChannel("update");
if (notification != null) { if (notification != null) {

@ -62,6 +62,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.PopupMenu; import androidx.appcompat.widget.PopupMenu;
import androidx.cardview.widget.CardView; import androidx.cardview.widget.CardView;
import androidx.constraintlayout.widget.Group; import androidx.constraintlayout.widget.Group;
import androidx.core.os.BuildCompat;
import androidx.core.view.MenuCompat; import androidx.core.view.MenuCompat;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.Lifecycle; import androidx.lifecycle.Lifecycle;
@ -69,6 +70,7 @@ import androidx.lifecycle.Observer;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class FragmentSetup extends FragmentBase { public class FragmentSetup extends FragmentBase {
@ -470,8 +472,11 @@ public class FragmentSetup extends FragmentBase {
public void onClick(View view) { public void onClick(View view) {
try { try {
btnPermissions.setEnabled(false); btnPermissions.setEnabled(false);
String permission = Manifest.permission.READ_CONTACTS; List<String> requesting = new ArrayList<>();
requestPermissions(new String[]{permission}, REQUEST_PERMISSIONS); for (String permission : getDesiredPermissions())
if (!hasPermission(permission))
requesting.add((permission));
requestPermissions(requesting.toArray(new String[0]), REQUEST_PERMISSIONS);
} catch (Throwable ex) { } catch (Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex); Log.unexpectedError(getParentFragmentManager(), ex);
/* /*
@ -695,7 +700,7 @@ public class FragmentSetup extends FragmentBase {
grpDataSaver.setVisibility(View.GONE); grpDataSaver.setVisibility(View.GONE);
tvStamina.setVisibility(View.GONE); tvStamina.setVisibility(View.GONE);
setContactsPermission(hasPermission(Manifest.permission.READ_CONTACTS)); setGrantedPermissions();
return view; return view;
} }
@ -959,20 +964,38 @@ public class FragmentSetup extends FragmentBase {
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
for (int i = 0; i < permissions.length; i++) setGrantedPermissions();
if (Manifest.permission.READ_CONTACTS.equals(permissions[i]))
setContactsPermission(grantResults[i] == PackageManager.PERMISSION_GRANTED);
} }
private void setContactsPermission(boolean granted) { private List<String> getDesiredPermissions() {
if (granted) List<String> permissions = new ArrayList<>();
permissions.add(Manifest.permission.READ_CONTACTS);
if (BuildCompat.isAtLeastT())
permissions.add(Manifest.permission.POST_NOTIFICATIONS);
return permissions;
}
private void setGrantedPermissions() {
List<String> granted = new ArrayList<>();
for (String permission : getDesiredPermissions())
if (hasPermission(permission))
granted.add(permission);
if (granted.contains(Manifest.permission.READ_CONTACTS))
ContactInfo.init(getContext().getApplicationContext()); ContactInfo.init(getContext().getApplicationContext());
tvPermissionsDone.setText(granted ? R.string.title_setup_done : R.string.title_setup_to_do); boolean all = true;
tvPermissionsDone.setTextColor(granted ? textColorPrimary : colorWarning); for (String permission : getDesiredPermissions())
tvPermissionsDone.setTypeface(null, granted ? Typeface.NORMAL : Typeface.BOLD); if (!granted.contains(permission)) {
tvPermissionsDone.setCompoundDrawablesWithIntrinsicBounds(granted ? check : null, null, null, null); all = false;
btnPermissions.setEnabled(!granted); break;
}
tvPermissionsDone.setText(all ? R.string.title_setup_done : R.string.title_setup_to_do);
tvPermissionsDone.setTextColor(all ? textColorPrimary : colorWarning);
tvPermissionsDone.setTypeface(null, all ? Typeface.NORMAL : Typeface.BOLD);
tvPermissionsDone.setCompoundDrawablesWithIntrinsicBounds(all ? check : null, null, null, null);
btnPermissions.setEnabled(!all);
} }
private void onDeleteAccount(Bundle args) { private void onDeleteAccount(Bundle args) {

@ -149,6 +149,13 @@ class NotificationHelper {
nm.createNotificationChannelGroup(group); nm.createNotificationChannelGroup(group);
} }
static boolean areNotificationsEnabled(NotificationManager nm) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
return true;
else
return nm.areNotificationsEnabled();
}
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
static void clear(Context context) { static void clear(Context context) {
NotificationManager nm = Helper.getSystemService(context, NotificationManager.class); NotificationManager nm = Helper.getSystemService(context, NotificationManager.class);

@ -107,8 +107,10 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar
EntityLog.log(ServiceSend.this, "Unsent=" + (unsent == null ? null : unsent.count)); EntityLog.log(ServiceSend.this, "Unsent=" + (unsent == null ? null : unsent.count));
try { try {
NotificationManager nm = Helper.getSystemService(ServiceSend.this, NotificationManager.class); NotificationManager nm =
nm.notify(NotificationHelper.NOTIFICATION_SEND, getNotificationService(false)); Helper.getSystemService(ServiceSend.this, NotificationManager.class);
if (NotificationHelper.areNotificationsEnabled(nm))
nm.notify(NotificationHelper.NOTIFICATION_SEND, getNotificationService(false));
} catch (Throwable ex) { } catch (Throwable ex) {
Log.w(ex); Log.w(ex);
} }
@ -331,8 +333,10 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar
EntityLog.log(ServiceSend.this, "Service send suitable=" + suitable); EntityLog.log(ServiceSend.this, "Service send suitable=" + suitable);
try { try {
NotificationManager nm = Helper.getSystemService(ServiceSend.this, NotificationManager.class); NotificationManager nm =
nm.notify(NotificationHelper.NOTIFICATION_SEND, getNotificationService(false)); Helper.getSystemService(ServiceSend.this, NotificationManager.class);
if (NotificationHelper.areNotificationsEnabled(nm))
nm.notify(NotificationHelper.NOTIFICATION_SEND, getNotificationService(false));
} catch (Throwable ex) { } catch (Throwable ex) {
Log.w(ex); Log.w(ex);
} }
@ -430,10 +434,11 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar
try { try {
int tries_left = (unrecoverable ? 0 : RETRY_MAX - op.tries); int tries_left = (unrecoverable ? 0 : RETRY_MAX - op.tries);
NotificationManager nm = Helper.getSystemService(this, NotificationManager.class); NotificationManager nm = Helper.getSystemService(this, NotificationManager.class);
nm.notify("send:" + message.id, if (NotificationHelper.areNotificationsEnabled(nm))
NotificationHelper.NOTIFICATION_TAGGED, nm.notify("send:" + message.id,
getNotificationError( NotificationHelper.NOTIFICATION_TAGGED,
MessageHelper.formatAddressesShort(message.to), ex, tries_left).build()); getNotificationError(
MessageHelper.formatAddressesShort(message.to), ex, tries_left).build());
} catch (Throwable ex1) { } catch (Throwable ex1) {
Log.w(ex1); Log.w(ex1);
} }
@ -517,7 +522,8 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar
} }
NotificationManager nm = Helper.getSystemService(this, NotificationManager.class); NotificationManager nm = Helper.getSystemService(this, NotificationManager.class);
nm.notify(NotificationHelper.NOTIFICATION_SEND, getNotificationService(true)); if (NotificationHelper.areNotificationsEnabled(nm))
nm.notify(NotificationHelper.NOTIFICATION_SEND, getNotificationService(true));
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean reply_move = prefs.getBoolean("reply_move", false); boolean reply_move = prefs.getBoolean("reply_move", false);
@ -731,7 +737,8 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar
if (now > last + PROGRESS_UPDATE_INTERVAL) { if (now > last + PROGRESS_UPDATE_INTERVAL) {
last = now; last = now;
lastProgress = progress; lastProgress = progress;
nm.notify(NotificationHelper.NOTIFICATION_SEND, getNotificationService(false)); if (NotificationHelper.areNotificationsEnabled(nm))
nm.notify(NotificationHelper.NOTIFICATION_SEND, getNotificationService(false));
} }
} }
} }
@ -772,7 +779,8 @@ public class ServiceSend extends ServiceBase implements SharedPreferences.OnShar
iservice.close(); iservice.close();
if (lastProgress >= 0) { if (lastProgress >= 0) {
lastProgress = -1; lastProgress = -1;
nm.notify(NotificationHelper.NOTIFICATION_SEND, getNotificationService(false)); if (NotificationHelper.areNotificationsEnabled(nm))
nm.notify(NotificationHelper.NOTIFICATION_SEND, getNotificationService(false));
} }
db.identity().setIdentityState(ident.id, null); db.identity().setIdentityState(ident.id, null);
} }

@ -419,9 +419,11 @@ public class ServiceSynchronize extends ServiceBase implements SharedPreferences
if (!isBackgroundService(ServiceSynchronize.this)) if (!isBackgroundService(ServiceSynchronize.this))
try { try {
NotificationManager nm = Helper.getSystemService(ServiceSynchronize.this, NotificationManager.class); NotificationManager nm =
nm.notify(NotificationHelper.NOTIFICATION_SYNCHRONIZE, Helper.getSystemService(ServiceSynchronize.this, NotificationManager.class);
getNotificationService(lastAccounts, lastOperations)); if (NotificationHelper.areNotificationsEnabled(nm))
nm.notify(NotificationHelper.NOTIFICATION_SYNCHRONIZE,
getNotificationService(lastAccounts, lastOperations));
} catch (Throwable ex) { } catch (Throwable ex) {
/* /*
java.lang.NullPointerException: Attempt to invoke interface method 'java.util.Iterator java.lang.Iterable.iterator()' on a null object reference java.lang.NullPointerException: Attempt to invoke interface method 'java.util.Iterator java.lang.Iterable.iterator()' on a null object reference
@ -1478,10 +1480,12 @@ public class ServiceSynchronize extends ServiceBase implements SharedPreferences
if (!ConnectionHelper.isMaxConnections(message)) if (!ConnectionHelper.isMaxConnections(message))
try { try {
NotificationManager nm = Helper.getSystemService(ServiceSynchronize.this, NotificationManager.class); NotificationManager nm =
nm.notify("alert:" + account.id, Helper.getSystemService(ServiceSynchronize.this, NotificationManager.class);
NotificationHelper.NOTIFICATION_TAGGED, if (NotificationHelper.areNotificationsEnabled(nm))
getNotificationAlert(account, message).build()); nm.notify("alert:" + account.id,
NotificationHelper.NOTIFICATION_TAGGED,
getNotificationAlert(account, message).build());
} catch (Throwable ex) { } catch (Throwable ex) {
Log.w(ex); Log.w(ex);
} }
@ -1541,11 +1545,13 @@ public class ServiceSynchronize extends ServiceBase implements SharedPreferences
try { try {
state.setBackoff(2 * CONNECT_BACKOFF_ALARM_MAX * 60); state.setBackoff(2 * CONNECT_BACKOFF_ALARM_MAX * 60);
NotificationManager nm = Helper.getSystemService(this, NotificationManager.class); NotificationManager nm =
nm.notify("receive:" + account.id, Helper.getSystemService(this, NotificationManager.class);
NotificationHelper.NOTIFICATION_TAGGED, if (NotificationHelper.areNotificationsEnabled(nm))
Core.getNotificationError(this, "error", account, 0, ex) nm.notify("receive:" + account.id,
.build()); NotificationHelper.NOTIFICATION_TAGGED,
Core.getNotificationError(this, "error", account, 0, ex)
.build());
} catch (Throwable ex1) { } catch (Throwable ex1) {
Log.w(ex1); Log.w(ex1);
} }
@ -2339,11 +2345,13 @@ public class ServiceSynchronize extends ServiceBase implements SharedPreferences
Helper.getDateTimeInstance(this, DateFormat.SHORT, DateFormat.SHORT) Helper.getDateTimeInstance(this, DateFormat.SHORT, DateFormat.SHORT)
.format(account.last_connected)), ex); .format(account.last_connected)), ex);
try { try {
NotificationManager nm = Helper.getSystemService(this, NotificationManager.class); NotificationManager nm =
nm.notify("receive:" + account.id, Helper.getSystemService(this, NotificationManager.class);
NotificationHelper.NOTIFICATION_TAGGED, if (NotificationHelper.areNotificationsEnabled(nm))
Core.getNotificationError(this, "warning", account, 0, warning) nm.notify("receive:" + account.id,
.build()); NotificationHelper.NOTIFICATION_TAGGED,
Core.getNotificationError(this, "warning", account, 0, warning)
.build());
} catch (Throwable ex1) { } catch (Throwable ex1) {
Log.w(ex1); Log.w(ex1);
} }
@ -2787,9 +2795,11 @@ public class ServiceSynchronize extends ServiceBase implements SharedPreferences
if (!isBackgroundService(ServiceSynchronize.this)) if (!isBackgroundService(ServiceSynchronize.this))
try { try {
NotificationManager nm = Helper.getSystemService(ServiceSynchronize.this, NotificationManager.class); NotificationManager nm =
nm.notify(NotificationHelper.NOTIFICATION_SYNCHRONIZE, Helper.getSystemService(ServiceSynchronize.this, NotificationManager.class);
getNotificationService(lastAccounts, lastOperations)); if (NotificationHelper.areNotificationsEnabled(nm))
nm.notify(NotificationHelper.NOTIFICATION_SYNCHRONIZE,
getNotificationService(lastAccounts, lastOperations));
} catch (Throwable ex) { } catch (Throwable ex) {
Log.w(ex); Log.w(ex);
} }

@ -6,6 +6,7 @@
* Added searching for text in draft * Added searching for text in draft
* Added OAuth for Gmail POP3 accounts * Added OAuth for Gmail POP3 accounts
* Android 13 compatibility (notification permissions)
* Small improvements and minor bug fixes * Small improvements and minor bug fixes
* Updated translations * Updated translations

Loading…
Cancel
Save