2022-01-04 08:11:51 +00:00
|
|
|
package eu.faircode.email;
|
|
|
|
|
2024-01-01 07:50:49 +00:00
|
|
|
/*
|
|
|
|
This file is part of FairEmail.
|
|
|
|
|
|
|
|
FairEmail is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
FairEmail is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
Copyright 2018-2024 by Marcel Bokhorst (M66B)
|
|
|
|
*/
|
|
|
|
|
2022-07-30 21:02:17 +00:00
|
|
|
import android.app.NotificationManager;
|
2023-08-25 19:52:54 +00:00
|
|
|
import android.app.PendingIntent;
|
2022-01-04 08:11:51 +00:00
|
|
|
import android.content.Context;
|
2023-08-25 19:52:54 +00:00
|
|
|
import android.content.Intent;
|
2022-01-04 08:11:51 +00:00
|
|
|
import android.media.AudioAttributes;
|
2022-01-10 17:18:40 +00:00
|
|
|
import android.media.AudioManager;
|
2022-01-04 08:11:51 +00:00
|
|
|
import android.media.MediaPlayer;
|
|
|
|
import android.net.Uri;
|
2022-07-06 19:22:02 +00:00
|
|
|
import android.os.Build;
|
2024-01-23 16:42:07 +00:00
|
|
|
import android.os.PowerManager;
|
2022-07-06 19:22:02 +00:00
|
|
|
|
2023-08-25 19:52:54 +00:00
|
|
|
import androidx.core.app.NotificationCompat;
|
2022-07-06 19:22:02 +00:00
|
|
|
import androidx.lifecycle.Lifecycle;
|
|
|
|
import androidx.lifecycle.LifecycleObserver;
|
|
|
|
import androidx.lifecycle.LifecycleOwner;
|
|
|
|
import androidx.lifecycle.OnLifecycleEvent;
|
2022-01-04 08:11:51 +00:00
|
|
|
|
|
|
|
import java.io.IOException;
|
2024-01-23 16:42:07 +00:00
|
|
|
import java.util.Objects;
|
2022-01-04 08:11:51 +00:00
|
|
|
import java.util.concurrent.Semaphore;
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
public class MediaPlayerHelper {
|
2022-03-23 08:24:54 +00:00
|
|
|
static final int DEFAULT_SOUND_DURATION = 30; // seconds
|
2022-01-04 18:40:27 +00:00
|
|
|
static final int DEFAULT_ALARM_DURATION = 30; // seconds
|
2022-01-04 08:11:51 +00:00
|
|
|
|
2023-08-25 19:52:54 +00:00
|
|
|
private static Semaphore sem;
|
2023-08-25 20:09:02 +00:00
|
|
|
private static final Object lock = new Object();
|
2023-08-25 19:52:54 +00:00
|
|
|
|
2024-01-23 16:42:07 +00:00
|
|
|
private static MediaPlayer player = null;
|
|
|
|
private static Uri uri = null;
|
|
|
|
private static Runnable onCompleted = null;
|
|
|
|
|
2023-08-25 19:52:54 +00:00
|
|
|
static void stop(Context context) {
|
|
|
|
EntityLog.log(context, "Alarm stop");
|
|
|
|
synchronized (lock) {
|
|
|
|
if (sem != null)
|
|
|
|
sem.release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-23 08:24:54 +00:00
|
|
|
static void queue(Context context, String uri) {
|
|
|
|
try {
|
|
|
|
queue(context, Uri.parse(uri), false, DEFAULT_SOUND_DURATION);
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.e(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void queue(Context context, Uri uri, boolean alarm, int duration) {
|
|
|
|
Log.i("Queuing sound=" + uri);
|
|
|
|
|
2023-01-01 10:43:43 +00:00
|
|
|
Helper.getMediaTaskExecutor().submit(new Runnable() {
|
2022-03-23 08:24:54 +00:00
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
try {
|
2022-10-09 19:57:53 +00:00
|
|
|
if (!alarm && (isInCall(context) || isDnd(context)))
|
2022-07-30 21:02:17 +00:00
|
|
|
return;
|
|
|
|
play(context, uri, alarm, duration);
|
2022-03-23 08:24:54 +00:00
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.e(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void play(Context context, Uri uri, boolean alarm, int duration) throws IOException {
|
2023-08-25 19:52:54 +00:00
|
|
|
synchronized (lock) {
|
|
|
|
sem = new Semaphore(0);
|
|
|
|
}
|
2022-01-04 08:11:51 +00:00
|
|
|
|
|
|
|
AudioAttributes attrs = new AudioAttributes.Builder()
|
|
|
|
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
|
|
|
.setUsage(alarm ? AudioAttributes.USAGE_ALARM : AudioAttributes.USAGE_NOTIFICATION)
|
|
|
|
.build();
|
|
|
|
|
|
|
|
MediaPlayer mediaPlayer = new MediaPlayer();
|
|
|
|
mediaPlayer.setAudioAttributes(attrs);
|
|
|
|
mediaPlayer.setDataSource(context.getApplicationContext(), uri);
|
|
|
|
mediaPlayer.setLooping(false);
|
|
|
|
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
|
|
|
|
@Override
|
|
|
|
public void onPrepared(MediaPlayer mp) {
|
|
|
|
mp.start();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
|
|
|
|
@Override
|
|
|
|
public void onCompletion(MediaPlayer mp) {
|
|
|
|
sem.release();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
mediaPlayer.prepareAsync();
|
|
|
|
|
2023-08-25 19:52:54 +00:00
|
|
|
NotificationManager nm = Helper.getSystemService(context, NotificationManager.class);
|
|
|
|
if (alarm) {
|
|
|
|
Intent intent = new Intent(context, ServiceUI.class)
|
|
|
|
.setAction("alarm");
|
|
|
|
PendingIntent piStop = PendingIntentCompat.getService(
|
|
|
|
context, ServiceUI.PI_ALARM, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
|
|
|
|
|
|
|
NotificationCompat.Action.Builder actionStop = new NotificationCompat.Action.Builder(
|
|
|
|
R.drawable.twotone_stop_24,
|
|
|
|
context.getString(R.string.title_rule_alarm_stop),
|
|
|
|
piStop)
|
|
|
|
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MUTE)
|
|
|
|
.setShowsUserInterface(false)
|
|
|
|
.setAllowGeneratedReplies(false);
|
|
|
|
|
|
|
|
NotificationCompat.Builder builder =
|
|
|
|
new NotificationCompat.Builder(context, "alerts")
|
|
|
|
.setSmallIcon(R.drawable.baseline_warning_white_24)
|
|
|
|
.setContentTitle(context.getString(R.string.title_rule_alarm_title))
|
|
|
|
.setSilent(true)
|
|
|
|
.setAutoCancel(false)
|
|
|
|
.addAction(actionStop.build())
|
|
|
|
.setShowWhen(true)
|
|
|
|
.setPriority(NotificationCompat.PRIORITY_MAX)
|
|
|
|
.setOnlyAlertOnce(true)
|
|
|
|
.setCategory(NotificationCompat.CATEGORY_ALARM)
|
|
|
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
|
|
|
|
|
|
|
|
nm.notify("alarm", 1, builder.build());
|
|
|
|
}
|
|
|
|
|
2022-01-04 08:11:51 +00:00
|
|
|
try {
|
2023-08-25 12:53:57 +00:00
|
|
|
boolean acquired = sem.tryAcquire(duration, TimeUnit.SECONDS);
|
|
|
|
EntityLog.log(context, "Alarm acquired=" + acquired);
|
2023-08-25 19:52:54 +00:00
|
|
|
mediaPlayer.stop();
|
|
|
|
mediaPlayer.release();
|
2023-08-25 12:53:57 +00:00
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.w(ex);
|
2023-08-25 19:52:54 +00:00
|
|
|
} finally {
|
|
|
|
if (alarm)
|
|
|
|
nm.cancel("alarm", 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
synchronized (lock) {
|
|
|
|
sem = null;
|
2022-01-04 08:11:51 +00:00
|
|
|
}
|
|
|
|
}
|
2022-01-10 17:18:40 +00:00
|
|
|
|
2024-01-27 06:50:22 +00:00
|
|
|
static void startMusic(Context context, Uri uri, Runnable onCompleted) throws IOException {
|
2024-01-23 16:42:07 +00:00
|
|
|
synchronized (lock) {
|
2024-01-24 06:09:24 +00:00
|
|
|
stopMusic(context);
|
|
|
|
|
2024-01-23 16:42:07 +00:00
|
|
|
MediaPlayerHelper.uri = uri;
|
|
|
|
MediaPlayerHelper.onCompleted = onCompleted;
|
|
|
|
|
|
|
|
MediaPlayerHelper.player = new MediaPlayer();
|
|
|
|
MediaPlayerHelper.player.setAudioAttributes(
|
|
|
|
new AudioAttributes.Builder()
|
|
|
|
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
|
|
|
.setUsage(AudioAttributes.USAGE_MEDIA)
|
|
|
|
.build()
|
|
|
|
);
|
|
|
|
MediaPlayerHelper.player.setDataSource(context, uri);
|
|
|
|
MediaPlayerHelper.player.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
|
|
|
|
MediaPlayerHelper.player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
|
|
|
|
@Override
|
|
|
|
public void onPrepared(MediaPlayer mp) {
|
|
|
|
// https://issuetracker.google.com/issues/36921987
|
|
|
|
mp.start();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
MediaPlayerHelper.player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
|
|
|
|
@Override
|
|
|
|
public void onCompletion(MediaPlayer mp) {
|
|
|
|
stopMusic(context);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
MediaPlayerHelper.player.prepareAsync();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void stopMusic(Context context) {
|
|
|
|
synchronized (lock) {
|
|
|
|
if (MediaPlayerHelper.player != null)
|
|
|
|
try {
|
|
|
|
MediaPlayerHelper.player.stop();
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.e(ex);
|
|
|
|
} finally {
|
|
|
|
MediaPlayerHelper.player = null;
|
|
|
|
}
|
|
|
|
MediaPlayerHelper.uri = null;
|
|
|
|
if (MediaPlayerHelper.onCompleted != null) {
|
|
|
|
MediaPlayerHelper.onCompleted.run();
|
|
|
|
MediaPlayerHelper.onCompleted = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static boolean isPlaying(Uri uri) {
|
|
|
|
synchronized (lock) {
|
|
|
|
return (Objects.equals(MediaPlayerHelper.uri, uri));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-06 19:22:02 +00:00
|
|
|
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) {
|
2022-12-13 09:52:39 +00:00
|
|
|
am.addOnModeChangedListener(Helper.getParallelExecutor(), listener);
|
2022-07-06 19:22:02 +00:00
|
|
|
registered = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (registered) {
|
|
|
|
am.removeOnModeChangedListener(listener);
|
|
|
|
registered = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Log.i("Audio mode registered=" + registered);
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.e(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-10 17:18:40 +00:00
|
|
|
static boolean isInCall(Context context) {
|
2022-04-13 20:27:33 +00:00
|
|
|
AudioManager am = Helper.getSystemService(context, AudioManager.class);
|
2022-01-10 17:18:40 +00:00
|
|
|
if (am == null)
|
|
|
|
return false;
|
|
|
|
|
2022-06-14 06:11:54 +00:00
|
|
|
try {
|
|
|
|
// This doesn't require READ_PHONE_STATE permission
|
|
|
|
int mode = am.getMode();
|
|
|
|
Log.i("Audio mode=" + mode);
|
2022-06-17 20:00:21 +00:00
|
|
|
return isInCall(mode);
|
2022-06-14 06:11:54 +00:00
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.e(ex);
|
|
|
|
return false;
|
|
|
|
}
|
2022-01-10 17:18:40 +00:00
|
|
|
}
|
2022-06-17 20:00:21 +00:00
|
|
|
|
2022-07-30 21:02:17 +00:00
|
|
|
static boolean isDnd(Context context) {
|
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
|
|
|
return false;
|
|
|
|
NotificationManager nm = Helper.getSystemService(context, NotificationManager.class);
|
|
|
|
int filter = nm.getCurrentInterruptionFilter();
|
|
|
|
// All: no notifications are suppressed
|
|
|
|
return (filter != NotificationManager.INTERRUPTION_FILTER_ALL);
|
|
|
|
}
|
|
|
|
|
2022-06-17 20:00:21 +00:00
|
|
|
static boolean isInCall(int mode) {
|
|
|
|
return (mode == AudioManager.MODE_RINGTONE ||
|
|
|
|
mode == AudioManager.MODE_IN_CALL ||
|
|
|
|
mode == AudioManager.MODE_IN_COMMUNICATION);
|
|
|
|
}
|
2022-07-06 19:22:02 +00:00
|
|
|
|
|
|
|
interface IInCall {
|
|
|
|
void onChanged(boolean inCall);
|
|
|
|
}
|
2022-01-04 08:11:51 +00:00
|
|
|
}
|