diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsNotifications.java b/app/src/main/java/eu/faircode/email/FragmentOptionsNotifications.java index 2a7edc7c7c..185387d68c 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsNotifications.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsNotifications.java @@ -92,6 +92,7 @@ public class FragmentOptionsNotifications extends FragmentBase implements Shared private SwitchCompat swNotifyBackgroundOnly; private SwitchCompat swNotifyKnownOnly; private SwitchCompat swNotifySuppressInCall; + private SwitchCompat swNotifySuppressInCar; private TextView tvNotifyKnownPro; private SwitchCompat swNotifyRemove; private SwitchCompat swNotifyClear; @@ -122,7 +123,8 @@ public class FragmentOptionsNotifications extends FragmentBase implements Shared "notify_flag", "notify_seen", "notify_hide", "notify_snooze", "light", "sound", "notify_screen_on", "badge", "unseen_ignored", - "notify_background_only", "notify_known", "notify_suppress_in_call", "notify_remove", "notify_clear", + "notify_background_only", "notify_known", "notify_suppress_in_call", "notify_suppress_in_car", + "notify_remove", "notify_clear", "notify_subtext", "notify_preview", "notify_preview_all", "notify_preview_only", "notify_transliterate", "wearable_preview", "notify_messaging", @@ -173,6 +175,7 @@ public class FragmentOptionsNotifications extends FragmentBase implements Shared swNotifyBackgroundOnly = view.findViewById(R.id.swNotifyBackgroundOnly); swNotifyKnownOnly = view.findViewById(R.id.swNotifyKnownOnly); swNotifySuppressInCall = view.findViewById(R.id.swNotifySuppressInCall); + swNotifySuppressInCar = view.findViewById(R.id.swNotifySuppressInCar); tvNotifyKnownPro = view.findViewById(R.id.tvNotifyKnownPro); swNotifyRemove = view.findViewById(R.id.swNotifyRemove); swNotifyClear = view.findViewById(R.id.swNotifyClear); @@ -478,6 +481,16 @@ public class FragmentOptionsNotifications extends FragmentBase implements Shared } }); + swNotifySuppressInCar.setVisibility( + Build.VERSION.SDK_INT < Build.VERSION_CODES.M + ? View.GONE : View.VISIBLE); + swNotifySuppressInCar.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { + prefs.edit().putBoolean("notify_suppress_in_car", checked).apply(); + } + }); + swNotifyRemove.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { @@ -689,6 +702,7 @@ public class FragmentOptionsNotifications extends FragmentBase implements Shared swNotifyBackgroundOnly.setChecked(prefs.getBoolean("notify_background_only", false)); swNotifyKnownOnly.setChecked(prefs.getBoolean("notify_known", false)); swNotifySuppressInCall.setChecked(prefs.getBoolean("notify_suppress_in_call", false)); + swNotifySuppressInCar.setChecked(prefs.getBoolean("notify_suppress_in_car", false)); swNotifyRemove.setChecked(prefs.getBoolean("notify_remove", true)); swNotifyClear.setChecked(prefs.getBoolean("notify_clear", false)); swNotifySubtext.setChecked(prefs.getBoolean("notify_subtext", true)); diff --git a/app/src/main/java/eu/faircode/email/MediaPlayerHelper.java b/app/src/main/java/eu/faircode/email/MediaPlayerHelper.java index cf864e53e3..fe5711025c 100644 --- a/app/src/main/java/eu/faircode/email/MediaPlayerHelper.java +++ b/app/src/main/java/eu/faircode/email/MediaPlayerHelper.java @@ -5,6 +5,12 @@ import android.media.AudioAttributes; import android.media.AudioManager; import android.media.MediaPlayer; import android.net.Uri; +import android.os.Build; + +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.OnLifecycleEvent; import java.io.IOException; import java.util.concurrent.ExecutorService; @@ -79,6 +85,51 @@ public class MediaPlayerHelper { } } + static void liveInCall(Context context, LifecycleOwner owner, IInCall intf) { + AudioManager am = Helper.getSystemService(context, AudioManager.class); + if (am == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + intf.onChanged(false); + Log.i("Audio mode legacy"); + } else { + AudioManager.OnModeChangedListener listener = new AudioManager.OnModeChangedListener() { + @Override + public void onModeChanged(int mode) { + ApplicationEx.getMainHandler().post(new RunnableEx("AudioMode") { + @Override + public void delegate() { + intf.onChanged(isInCall(mode)); + } + }); + } + }; + listener.onModeChanged(am.getMode()); // Init + + owner.getLifecycle().addObserver(new LifecycleObserver() { + private boolean registered = false; + + @OnLifecycleEvent(Lifecycle.Event.ON_ANY) + public void onStateChanged() { + try { + if (owner.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { + if (!registered) { + am.addOnModeChangedListener(executor, listener); + registered = true; + } + } else { + if (registered) { + am.removeOnModeChangedListener(listener); + registered = false; + } + } + Log.i("Audio mode registered=" + registered); + } catch (Throwable ex) { + Log.e(ex); + } + } + }); + } + } + static boolean isInCall(Context context) { AudioManager am = Helper.getSystemService(context, AudioManager.class); if (am == null) @@ -100,4 +151,8 @@ public class MediaPlayerHelper { mode == AudioManager.MODE_IN_CALL || mode == AudioManager.MODE_IN_COMMUNICATION); } + + interface IInCall { + void onChanged(boolean inCall); + } } diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index 6bfdeb8ccb..ac17bc3fa2 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -31,7 +31,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; -import android.media.AudioManager; import android.net.ConnectivityManager; import android.net.LinkProperties; import android.net.Network; @@ -46,6 +45,7 @@ import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; +import androidx.car.app.connection.CarConnection; import androidx.core.app.NotificationCompat; import androidx.core.content.ContextCompat; import androidx.lifecycle.Lifecycle; @@ -116,6 +116,8 @@ public class ServiceSynchronize extends ServiceBase implements SharedPreferences private int lastAccounts = 0; private int lastOperations = 0; private ConnectionHelper.NetworkState lastNetworkState = null; + private boolean isInCall = false; + private boolean isInCar = false; private boolean foreground = false; private final Map coreStates = new Hashtable<>(); @@ -768,58 +770,45 @@ public class ServiceSynchronize extends ServiceBase implements SharedPreferences }); final TwoStateOwner mowner = new TwoStateOwner(this, "mutableUnseenNotify"); + mowner.getLifecycle().addObserver(new LifecycleObserver() { + @OnLifecycleEvent(Lifecycle.Event.ON_ANY) + public void onStateChanged() { + Lifecycle.State state = mowner.getLifecycle().getCurrentState(); + EntityLog.log(ServiceSynchronize.this, EntityLog.Type.Debug, "Owner state=" + state); + if (state.equals(Lifecycle.State.DESTROYED)) + mowner.getLifecycle().removeObserver(this); + } + }); - AudioManager am = Helper.getSystemService(this, AudioManager.class); - if (am == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { - mowner.start(); - Log.i("Audio mode legacy"); - } else { - AudioManager.OnModeChangedListener listener = new AudioManager.OnModeChangedListener() { - @Override - public void onModeChanged(int mode) { - getMainHandler().post(new RunnableEx("AudioMode") { - @Override - public void delegate() { - boolean incall = MediaPlayerHelper.isInCall(mode); - boolean suppress = prefs.getBoolean("notify_suppress_in_call", false); - boolean start = (!suppress || !incall); - Log.i("Audio mode start=" + start + - " incall=" + incall + - " suppress=" + suppress); - if (start) - mowner.start(); - else - mowner.stop(); - } - }); - } - }; - listener.onModeChanged(am.getMode()); // Init - - getLifecycle().addObserver(new LifecycleObserver() { - private boolean registered = false; + MediaPlayerHelper.liveInCall(this, this, new MediaPlayerHelper.IInCall() { + @Override + public void onChanged(boolean inCall) { + boolean suppress = prefs.getBoolean("notify_suppress_in_call", false); + EntityLog.log(ServiceSynchronize.this, EntityLog.Type.Debug, + "In call=" + inCall + " suppress=" + suppress); + isInCall = (inCall && suppress); + if (isInCall || isInCar) + mowner.stop(); + else + mowner.start(); + } + }); - @OnLifecycleEvent(Lifecycle.Event.ON_ANY) - public void onStateChanged() { - try { - if (ServiceSynchronize.this.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { - if (!registered) { - am.addOnModeChangedListener(executor, listener); - registered = true; - } - } else { - if (registered) { - am.removeOnModeChangedListener(listener); - registered = false; - } - } - Log.i("Audio mode registered=" + registered); - } catch (Throwable ex) { - Log.e(ex); - } - } - }); - } + new CarConnection(this).getType().observe(this, new Observer() { + @Override + public void onChanged(Integer connectionState) { + boolean projection = (connectionState != null && + connectionState == CarConnection.CONNECTION_TYPE_PROJECTION); + boolean suppress = prefs.getBoolean("notify_suppress_in_car", false); + EntityLog.log(ServiceSynchronize.this, EntityLog.Type.Debug, + "Projection=" + projection + " state=" + connectionState + " suppress=" + suppress); + isInCar = (projection && suppress); + if (isInCall || isInCar) + mowner.stop(); + else + mowner.start(); + } + }); mutableUnseenNotify.observe(mowner, new Observer>() { private final ExecutorService executor = diff --git a/app/src/main/res/layout/fragment_options_notifications.xml b/app/src/main/res/layout/fragment_options_notifications.xml index f3a6f85bba..f1cacdd6aa 100644 --- a/app/src/main/res/layout/fragment_options_notifications.xml +++ b/app/src/main/res/layout/fragment_options_notifications.xml @@ -560,6 +560,17 @@ app:layout_constraintTop_toBottomOf="@id/tvNotifyKnownPro" app:switchPadding="12dp" /> + + Show notifications when in the background only Show notifications for contacts only Delay notifications while on a call + Delay notifications while Android Auto is connected Show summary notification only Show message preview in notifications Preview all text