diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cfa97f6..c73a982 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -24,7 +24,7 @@ android:theme="@android:style/Theme.Translucent.NoTitleBar" android:exported="true" /> diff --git a/app/src/main/java/com/twilio/voice/quickstart/Constants.java b/app/src/main/java/com/twilio/voice/quickstart/Constants.java index 5c87d3e..a7edb11 100644 --- a/app/src/main/java/com/twilio/voice/quickstart/Constants.java +++ b/app/src/main/java/com/twilio/voice/quickstart/Constants.java @@ -1,18 +1,19 @@ package com.twilio.voice.quickstart; public class Constants { - public static final String CALL_SID_KEY = "CALL_SID"; + public static final String CALL_SID = "CALL_SID"; public static final String VOICE_CHANNEL_LOW_IMPORTANCE = "notification-channel-low-importance"; public static final String VOICE_CHANNEL_HIGH_IMPORTANCE = "notification-channel-high-importance"; public static final String OUTGOING_CALL_RECIPIENT = "OUTGOING_CALL_RECIPIENT"; public static final String INCOMING_CALL_INVITE = "INCOMING_CALL_INVITE"; public static final String CANCELLED_CALL_INVITE = "CANCELLED_CALL_INVITE"; public static final String INCOMING_CALL_NOTIFICATION_ID = "INCOMING_CALL_NOTIFICATION_ID"; - public static final String ACTION_ACCEPT = "ACTION_ACCEPT"; - public static final String ACTION_REJECT = "ACTION_REJECT"; + public static final String FCM_TOKEN = "FCM_TOKEN"; public static final String ACTION_INCOMING_CALL_NOTIFICATION = "ACTION_INCOMING_CALL_NOTIFICATION"; public static final String ACTION_INCOMING_CALL = "ACTION_INCOMING_CALL"; - public static final String ACTION_OUTGOING_CALL = "ACTION_OUTGOING_CALL"; public static final String ACTION_CANCEL_CALL = "ACTION_CANCEL_CALL"; + public static final String ACTION_ACCEPT_CALL = "ACTION_ACCEPT"; + public static final String ACTION_REJECT_CALL = "ACTION_REJECT"; + public static final String ACTION_OUTGOING_CALL = "ACTION_OUTGOING_CALL"; public static final String ACTION_FCM_TOKEN = "ACTION_FCM_TOKEN"; } diff --git a/app/src/main/java/com/twilio/voice/quickstart/IncomingMessageService.java b/app/src/main/java/com/twilio/voice/quickstart/IncomingMessageService.java new file mode 100644 index 0000000..5d5e004 --- /dev/null +++ b/app/src/main/java/com/twilio/voice/quickstart/IncomingMessageService.java @@ -0,0 +1,79 @@ +package com.twilio.voice.quickstart; + +import static com.twilio.voice.quickstart.Constants.ACTION_FCM_TOKEN; +import static com.twilio.voice.quickstart.Constants.ACTION_INCOMING_CALL; +import static com.twilio.voice.quickstart.Constants.CALL_SID; +import static com.twilio.voice.quickstart.Constants.FCM_TOKEN; +import static com.twilio.voice.quickstart.Constants.INCOMING_CALL_INVITE; +import static com.twilio.voice.quickstart.Constants.ACTION_CANCEL_CALL; +import static com.twilio.voice.quickstart.Constants.CANCELLED_CALL_INVITE; +import static java.lang.String.format; + +import android.content.Intent; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import android.os.Parcelable; +import android.util.Pair; + +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; +import com.twilio.voice.CallException; +import com.twilio.voice.CallInvite; +import com.twilio.voice.CancelledCallInvite; +import com.twilio.voice.MessageListener; +import com.twilio.voice.Voice; + +public class IncomingMessageService extends FirebaseMessagingService implements MessageListener { + private static final Logger log = new Logger(IncomingMessageService.class); + + @Override + public void onMessageReceived(RemoteMessage remoteMessage) { + log.debug(format( + "Received firebase message\n\tmessage data: %s\n\tfrom: %s", + remoteMessage.getData(), + remoteMessage.getFrom())); + + // Check if message contains a data payload. + if (!remoteMessage.getData().isEmpty() && !Voice.handleMessage(this, remoteMessage.getData(), this)) { + log.error(format("Received message was not a valid Twilio Voice SDK payload: %s", remoteMessage.getData())); + } + } + + @Override + public void onNewToken(@NonNull String token) { + super.onNewToken(token); + startVoiceService(ACTION_FCM_TOKEN, new Pair<>(FCM_TOKEN, token)); + } + + @Override + public void onCallInvite(@NonNull CallInvite callInvite) { + startVoiceService( + ACTION_INCOMING_CALL, + new Pair<>(INCOMING_CALL_INVITE, callInvite)); + } + + @Override + public void onCancelledCallInvite(@NonNull CancelledCallInvite cancelledCallInvite, + @Nullable CallException callException) { + startVoiceService( + ACTION_CANCEL_CALL, + new Pair<>(CANCELLED_CALL_INVITE, cancelledCallInvite)); + } + + @SafeVarargs + private void startVoiceService(@NonNull final String action, + @NonNull final Pair...data) { + final Intent intent = new Intent(this, VoiceService.class); + intent.setAction(action); + for (Pair pair: data) { + if (pair.second instanceof String) { + intent.putExtra(pair.first, (String)pair.second); + } else if (pair.second instanceof Parcelable) { + intent.putExtra(pair.first, (Parcelable)pair.second); + } + } + startService(intent); + } +} diff --git a/app/src/main/java/com/twilio/voice/quickstart/Logger.java b/app/src/main/java/com/twilio/voice/quickstart/Logger.java new file mode 100644 index 0000000..a096695 --- /dev/null +++ b/app/src/main/java/com/twilio/voice/quickstart/Logger.java @@ -0,0 +1,74 @@ +package com.twilio.voice.quickstart; + +import android.util.Log; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.charset.Charset; +import java.util.Vector; + +class Logger extends OutputStream { + private final String logTag; + private final Vector logInfoBuffer = new Vector<>(); + public Logger(Class clazz) { + logTag = clazz.getSimpleName(); + } + + public void debug(final String message) { + if (BuildConfig.DEBUG) { + Log.d(logTag, message); + } + } + + public void log(final String message) { + Log.i(logTag, message); + } + + public void warning(final String message) { + try { + write(message.getBytes()); + flush(); + } catch (Exception ignore) {} + } + + public void error(final String message) { + Log.e(logTag, message); + } + + public void warning(final Exception e, final String message) { + PrintStream printStream = new PrintStream(this); + printStream.println(message); + e.printStackTrace(printStream); + printStream.flush(); + } + + @Override + public synchronized void write(int i) throws IOException { + logInfoBuffer.add((char)i); + } + + @Override + public synchronized void write(byte[] b) throws IOException { + for (char c: (new String(b, Charset.defaultCharset()).toCharArray())) { + logInfoBuffer.add(c); + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + for (char c: (new String(b, off, len, Charset.defaultCharset()).toCharArray())) { + logInfoBuffer.add(c); + } + } + + @Override + public synchronized void flush() throws IOException { + char [] output = new char[logInfoBuffer.size()]; + for (int i = 0; i < logInfoBuffer.size(); ++i) { + output[i] = logInfoBuffer.get(i); + } + logInfoBuffer.clear(); + Log.w(logTag, String.valueOf(output)); + } +} diff --git a/app/src/main/java/com/twilio/voice/quickstart/NotificationProxyActivity.java b/app/src/main/java/com/twilio/voice/quickstart/NotificationProxyActivity.java index e0d92ff..f5bee40 100644 --- a/app/src/main/java/com/twilio/voice/quickstart/NotificationProxyActivity.java +++ b/app/src/main/java/com/twilio/voice/quickstart/NotificationProxyActivity.java @@ -3,7 +3,6 @@ import android.app.Activity; import android.content.Intent; import android.os.Bundle; -import android.speech.tts.Voice; public class NotificationProxyActivity extends Activity { @Override @@ -24,7 +23,7 @@ private void handleIntent(Intent intent) { final String action = intent.getAction(); if (action != null) { final Intent serviceIntent = - (new Intent(intent)).setClass(this, IncomingCallNotificationService.class); + (new Intent(intent)).setClass(this, VoiceService.class); final Intent appIntent = (new Intent(intent)).setClass(this, VoiceActivity.class); switch (action) { @@ -52,7 +51,7 @@ private void launchMainActivity(Intent intent) { } private void launchService(Intent intent) { Intent launchIntent = new Intent(intent); - launchIntent.setClass(this, IncomingCallNotificationService.class); + launchIntent.setClass(this, VoiceService.class); startService(launchIntent); } } diff --git a/app/src/main/java/com/twilio/voice/quickstart/SoundPoolManager.java b/app/src/main/java/com/twilio/voice/quickstart/SoundPoolManager.java index efc2874..64a4133 100644 --- a/app/src/main/java/com/twilio/voice/quickstart/SoundPoolManager.java +++ b/app/src/main/java/com/twilio/voice/quickstart/SoundPoolManager.java @@ -1,94 +1,133 @@ package com.twilio.voice.quickstart; +import android.annotation.SuppressLint; import android.content.Context; import android.media.AudioManager; import android.media.SoundPool; -import android.os.Build; + import static android.content.Context.AUDIO_SERVICE; -public class SoundPoolManager { +import static java.lang.String.format; - private boolean playing = false; - private boolean loaded = false; - private boolean playingCalled = false; - private float volume; - private SoundPool soundPool; - private int ringingSoundId; - private int ringingStreamId; - private int disconnectSoundId; - private static SoundPoolManager instance; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; - private SoundPoolManager(Context context) { - // AudioManager audio settings for adjusting the volume - AudioManager audioManager = (AudioManager) context.getSystemService(AUDIO_SERVICE); - float actualVolume = (float) audioManager.getStreamVolume(AudioManager.STREAM_MUSIC); - float maxVolume = (float) audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); - volume = actualVolume / maxVolume; +@SuppressLint("DefaultLocale") +class SoundPoolManager { + enum Sound { + RINGER, + DISCONNECT + } - // Load the sounds - int maxStreams = 1; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - soundPool = new SoundPool.Builder() - .setMaxStreams(maxStreams) - .build(); - } else { - soundPool = new SoundPool(maxStreams, AudioManager.STREAM_MUSIC, 0); + enum SoundState { + LOADING, + READY, + PLAYING, + ERROR + } + + private static class SoundRecord { + final int id; + final boolean loop; + SoundState state; + + public SoundRecord(Context context, + SoundPool soundPool, + final int resource, + final boolean loop) { + this.id = soundPool.load(context, resource, 1); + this.state = SoundState.LOADING; + this.loop = loop; } + } + + private static final Logger log = new Logger(SoundPoolManager.class); + private final float volume; + private final SoundPool soundPool; + + private Map soundBank; + private int lastActiveAudioStreamId; - soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() { - @Override - public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { - loaded = true; - if (playingCalled) { - playRinging(); - playingCalled = false; + SoundPoolManager(Context context) { + // construct sound pool + soundPool = new SoundPool.Builder().setMaxStreams(1).build(); + soundPool.setOnLoadCompleteListener((soundPool, sampleId, status) -> { + for (Map.Entry entry : soundBank.entrySet()) { + final SoundRecord record = entry.getValue(); + if (record.id == sampleId) { + record.state = (0 == status) ? SoundState.READY : SoundState.ERROR; + if (0 != status) { + log.error( + format("Failed to load sound %s, error: %d", + entry.getKey().name(), status)); + } } } - }); - ringingSoundId = soundPool.load(context, R.raw.incoming, 1); - disconnectSoundId = soundPool.load(context, R.raw.disconnect, 1); - } - public static SoundPoolManager getInstance(Context context) { - if (instance == null) { - instance = new SoundPoolManager(context); - } - return instance; + // construct sound bank & load + soundBank = new HashMap<>() {{ + put(Sound.RINGER, new SoundRecord(context, soundPool, R.raw.incoming, true)); + put(Sound.DISCONNECT, new SoundRecord(context, soundPool, R.raw.disconnect, false)); + }}; + + // no active stream + lastActiveAudioStreamId = -1; + + // AudioManager audio settings for adjusting the volume + AudioManager audioManager = (AudioManager) context.getSystemService(AUDIO_SERVICE); + float actualVolume = (float) audioManager.getStreamVolume(AudioManager.STREAM_MUSIC); + float maxVolume = (float) audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + volume = actualVolume / maxVolume; } - public void playRinging() { - if (loaded && !playing) { - ringingStreamId = soundPool.play(ringingSoundId, volume, volume, 1, -1, 1f); - playing = true; + void playSound(final Sound sound) { + final SoundRecord soundRecord = Objects.requireNonNull(soundBank.get(sound)); + if (!isSoundPlaying() && SoundState.READY == soundRecord.state) { + lastActiveAudioStreamId = soundPool.play( + soundRecord.id, volume, volume, 1, soundRecord.loop ? -1 : 0, 1f); + soundRecord.state = soundRecord.loop ? SoundState.PLAYING : SoundState.READY; + } else if (isSoundPlaying()) { + log.warning( + format("cannot play sound %s: %d sound stream already active", + sound.name(), lastActiveAudioStreamId)); } else { - playingCalled = true; + log.warning(format("cannot play sound %s: invalid state", sound.name())); } } - public void stopRinging() { - if (playing) { - soundPool.stop(ringingStreamId); - playing = false; + void stopSound(final Sound sound) { + final SoundRecord soundRecord = Objects.requireNonNull(soundBank.get(sound)); + if (SoundState.PLAYING == soundRecord.state) { + soundPool.stop(lastActiveAudioStreamId); + } else { + log.warning(format("cannot stop sound %s: invalid state", sound.name())); } } - public void playDisconnect() { - if (loaded && !playing) { - soundPool.play(disconnectSoundId, volume, volume, 1, 0, 1f); - playing = false; + @Override + protected void finalize() throws Throwable { + for (SoundRecord record : soundBank.values()) { + switch (record.state) { + case PLAYING: + soundPool.stop(lastActiveAudioStreamId); + // intentionally fall through + case READY: + soundPool.unload(record.id); + break; + } } + soundPool.release(); + super.finalize(); } - public void release() { - if (soundPool != null) { - soundPool.unload(ringingSoundId); - soundPool.unload(disconnectSoundId); - soundPool.release(); - soundPool = null; + private boolean isSoundPlaying() { + boolean playbackActive = false; + for (SoundRecord record : soundBank.values()) { + playbackActive |= (SoundState.PLAYING == record.state); } - instance = null; + return playbackActive; } - } diff --git a/app/src/main/java/com/twilio/voice/quickstart/fcm/VoiceFirebaseMessagingService.java b/app/src/main/java/com/twilio/voice/quickstart/fcm/VoiceFirebaseMessagingService.java deleted file mode 100644 index 0bd0f21..0000000 --- a/app/src/main/java/com/twilio/voice/quickstart/fcm/VoiceFirebaseMessagingService.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.twilio.voice.quickstart.fcm; - -import android.content.Intent; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; - -import android.util.Log; - -import com.google.firebase.messaging.FirebaseMessagingService; -import com.google.firebase.messaging.RemoteMessage; -import com.twilio.voice.CallException; -import com.twilio.voice.CallInvite; -import com.twilio.voice.CancelledCallInvite; -import com.twilio.voice.MessageListener; -import com.twilio.voice.Voice; -import com.twilio.voice.quickstart.Constants; -import com.twilio.voice.quickstart.IncomingCallNotificationService; - -public class VoiceFirebaseMessagingService extends FirebaseMessagingService { - - private static final String TAG = "VoiceFCMService"; - - @Override - public void onCreate() { - super.onCreate(); - } - - /** - * Called when message is received. - * - * @param remoteMessage Object representing the message received from Firebase Cloud Messaging. - */ - @Override - public void onMessageReceived(RemoteMessage remoteMessage) { - Log.d(TAG, "Received onMessageReceived()"); - Log.d(TAG, "Bundle data: " + remoteMessage.getData()); - Log.d(TAG, "From: " + remoteMessage.getFrom()); - - // Check if message contains a data payload. - if (remoteMessage.getData().size() > 0) { - boolean valid = Voice.handleMessage(this, remoteMessage.getData(), new MessageListener() { - @Override - public void onCallInvite(@NonNull CallInvite callInvite) { - final int notificationId = (int) System.currentTimeMillis(); - handleInvite(callInvite, notificationId); - } - - @Override - public void onCancelledCallInvite(@NonNull CancelledCallInvite cancelledCallInvite, @Nullable CallException callException) { - handleCanceledCallInvite(cancelledCallInvite); - } - }); - - if (!valid) { - Log.e(TAG, "The message was not a valid Twilio Voice SDK payload: " + - remoteMessage.getData()); - } - } - } - - @Override - public void onNewToken(String token) { - super.onNewToken(token); - Intent intent = new Intent(Constants.ACTION_FCM_TOKEN); - LocalBroadcastManager.getInstance(this).sendBroadcast(intent); - } - - private void handleInvite(CallInvite callInvite, int notificationId) { - Intent intent = new Intent(this, IncomingCallNotificationService.class); - intent.setAction(Constants.ACTION_INCOMING_CALL); - intent.putExtra(Constants.INCOMING_CALL_NOTIFICATION_ID, notificationId); - intent.putExtra(Constants.INCOMING_CALL_INVITE, callInvite); - - startService(intent); - } - - private void handleCanceledCallInvite(CancelledCallInvite cancelledCallInvite) { - Intent intent = new Intent(this, IncomingCallNotificationService.class); - intent.setAction(Constants.ACTION_CANCEL_CALL); - intent.putExtra(Constants.CANCELLED_CALL_INVITE, cancelledCallInvite); - - startService(intent); - } -} diff --git a/app/src/standard/AndroidManifest.xml b/app/src/standard/AndroidManifest.xml index 121a747..273a94f 100644 --- a/app/src/standard/AndroidManifest.xml +++ b/app/src/standard/AndroidManifest.xml @@ -21,7 +21,7 @@ diff --git a/app/src/standard/java/com/twilio/voice/quickstart/VoiceActivity.java b/app/src/standard/java/com/twilio/voice/quickstart/VoiceActivity.java index baf45e3..cf0b3d1 100644 --- a/app/src/standard/java/com/twilio/voice/quickstart/VoiceActivity.java +++ b/app/src/standard/java/com/twilio/voice/quickstart/VoiceActivity.java @@ -461,7 +461,7 @@ public void onReceive(Context context, Intent intent) { private DialogInterface.OnClickListener answerCallClickListener() { return (dialog, which) -> { Log.d(TAG, "Clicked accept"); - Intent acceptIntent = new Intent(getApplicationContext(), IncomingCallNotificationService.class); + Intent acceptIntent = new Intent(getApplicationContext(), VoiceService.class); acceptIntent.setAction(Constants.ACTION_ACCEPT); acceptIntent.putExtra(Constants.INCOMING_CALL_INVITE, activeCallInvite); acceptIntent.putExtra(Constants.INCOMING_CALL_NOTIFICATION_ID, activeCallNotificationId); @@ -489,7 +489,7 @@ private DialogInterface.OnClickListener cancelCallClickListener() { return (dialogInterface, i) -> { SoundPoolManager.getInstance(VoiceActivity.this).stopRinging(); if (activeCallInvite != null) { - Intent intent = new Intent(VoiceActivity.this, IncomingCallNotificationService.class); + Intent intent = new Intent(VoiceActivity.this, VoiceService.class); intent.setAction(Constants.ACTION_REJECT); intent.putExtra(Constants.INCOMING_CALL_INVITE, activeCallInvite); startService(intent); @@ -562,7 +562,7 @@ private void answer() { SoundPoolManager.getInstance(this).stopRinging(); activeCallInvite.accept(this, callListener); notificationManager.cancel(activeCallNotificationId); - stopService(new Intent(getApplicationContext(), IncomingCallNotificationService.class)); + stopService(new Intent(getApplicationContext(), VoiceService.class)); setCallUI(); if (alertDialog != null && alertDialog.isShowing()) { alertDialog.dismiss(); diff --git a/app/src/standard/java/com/twilio/voice/quickstart/IncomingCallNotificationService.java b/app/src/standard/java/com/twilio/voice/quickstart/VoiceService.java similarity index 65% rename from app/src/standard/java/com/twilio/voice/quickstart/IncomingCallNotificationService.java rename to app/src/standard/java/com/twilio/voice/quickstart/VoiceService.java index 3da827a..323e9c8 100644 --- a/app/src/standard/java/com/twilio/voice/quickstart/IncomingCallNotificationService.java +++ b/app/src/standard/java/com/twilio/voice/quickstart/VoiceService.java @@ -1,5 +1,11 @@ package com.twilio.voice.quickstart; +import static com.twilio.voice.quickstart.Constants.ACTION_CANCEL_CALL; +import static com.twilio.voice.quickstart.Constants.ACTION_FCM_TOKEN; +import static com.twilio.voice.quickstart.Constants.ACTION_INCOMING_CALL; +import static com.twilio.voice.quickstart.Constants.ACTION_REJECT_CALL; +import static com.twilio.voice.quickstart.Constants.FCM_TOKEN; + import android.annotation.TargetApi; import android.app.Notification; import android.app.NotificationChannel; @@ -10,55 +16,156 @@ import android.content.Intent; import android.content.pm.ServiceInfo; import android.graphics.Color; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.util.Log; +import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; +import androidx.core.app.ServiceCompat; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.ProcessLifecycleOwner; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.twilio.voice.Call; import com.twilio.voice.CallInvite; +import com.twilio.voice.CancelledCallInvite; +import com.twilio.voice.ConnectOptions; +import com.twilio.voice.Voice; + +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.Vector; + +public class VoiceService extends Service { + private static final Logger log = new Logger(VoiceService.class); + private String fcmToken; + private SoundPoolManager soundPoolManager; + private Map callDatabase; + private WeakReference voiceActiviy; -public class IncomingCallNotificationService extends Service { + public static class CallRecord { + public CallInvite callInvite; + public CallRecord(final CallInvite callInvite) { + this.callInvite = callInvite; + } + } - private static final String TAG = IncomingCallNotificationService.class.getSimpleName(); + public VoiceService() { + fcmToken = "unavailable"; + soundPoolManager = new SoundPoolManager(this); + callDatabase = new HashMap<>(); + voiceActiviy = new WeakReference<>(null); + } @Override - public int onStartCommand(Intent intent, int flags, int startId) { - String action = intent.getAction(); - - if (action != null) { - CallInvite callInvite = intent.getParcelableExtra(Constants.INCOMING_CALL_INVITE); - int notificationId = intent.getIntExtra(Constants.INCOMING_CALL_NOTIFICATION_ID, 0); - switch (action) { - case Constants.ACTION_INCOMING_CALL: - handleIncomingCall(intent, callInvite, notificationId); - break; - case Constants.ACTION_ACCEPT: - accept(callInvite, notificationId); - break; - case Constants.ACTION_REJECT: - reject(callInvite); - break; - case Constants.ACTION_CANCEL_CALL: - handleCancelledCall(intent); - break; - default: - break; + public IBinder onBind(Intent intent) { + return new Binder() { + VoiceService getService() { + return VoiceService.this; } + }; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + switch (Objects.requireNonNull(intent.getAction())) { + case ACTION_FCM_TOKEN: + fcmToken = Objects.requireNonNull(intent.getStringExtra(FCM_TOKEN)); + break; + case ACTION_INCOMING_CALL: + incomingCall( + Objects.requireNonNull( + intent.getParcelableExtra(Constants.INCOMING_CALL_INVITE))); + break; + case ACTION_CANCEL_CALL: + cancelledCall( + Objects.requireNonNull( + intent.getParcelableExtra(Constants.CANCELLED_CALL_INVITE))); + break; + case ACTION_REJECT_CALL: + rejectIncomingCall( + Objects.requireNonNull(intent.getStringExtra(Constants.CALL_SID))); + break; + default: + log.error("should never get here"); } return START_NOT_STICKY; } - @Override - public IBinder onBind(Intent intent) { - return null; + public String getFcmToken() { + return fcmToken; + } + + public void registerVoiceActivity(final VoiceActivity voiceActivity) { + voiceActiviy = new WeakReference<>(voiceActivity); } + public void rejectIncomingCall(final String callSID) { + // find call record + final CallRecord callRecord = Objects.requireNonNull(callDatabase.get(callSID)); + + // remove notification + ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE); + + // kill ringer + soundPoolManager.stopSound(SoundPoolManager.Sound.RINGER); + + // remove call record + callDatabase.remove(callRecord); + + // notify voice activity + if (null != voiceActiviy.get()) { + // todo + } + } + + private void incomingCall(@NonNull final CallInvite callInvite) { + // create call record + callDatabase.put(callInvite.getCallSid(), new CallRecord(callInvite)); + + // create incoming call notification + // todo + + // create ringer sound + soundPoolManager.playSound(SoundPoolManager.Sound.RINGER); + + // notify voice activity + if (null != voiceActiviy.get()) { + // todo + } + } + + private void cancelledCall(@NonNull final CancelledCallInvite cancelledCallInvite) { + // find call record + final CallRecord callRecord = + Objects.requireNonNull(callDatabase.get(cancelledCallInvite.getCallSid())); + + // remove notification + ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE); + + // kill ringer + soundPoolManager.stopSound(SoundPoolManager.Sound.RINGER); + + // remove call record + callDatabase.remove(callRecord); + + // notify voice activity + if (null != voiceActiviy.get()) { + // todo + } + } + + + //// old + + private Notification createNotification(CallInvite callInvite, int notificationId, int channelImportance) { Intent intent = new Intent(this, NotificationProxyActivity.class); intent.setAction(Constants.ACTION_INCOMING_CALL_NOTIFICATION); diff --git a/build.gradle b/build.gradle index 501756b..dee1b78 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { 'targetSdk' : 34, 'material' : '1.9.0', 'firebase' : '23.2.1', - 'voiceAndroid' : '6.6.4', + 'voiceAndroid' : '6.6.+', 'audioSwitch' : '1.2.0', 'androidxLifecycle' : '2.2.0', 'junit' : '1.1.5'