package im.actor.core.modules.calls;
import java.util.HashMap;
import java.util.HashSet;
import im.actor.core.entity.Peer;
import im.actor.core.modules.ModuleContext;
import im.actor.core.modules.calls.peers.AbsCallActor;
import im.actor.core.modules.ModuleActor;
import im.actor.core.providers.CallsProvider;
import im.actor.core.util.RandomUtils;
import im.actor.core.viewmodel.CommandCallback;
import im.actor.runtime.*;
import im.actor.runtime.Runtime;
import im.actor.runtime.actors.Actor;
import im.actor.runtime.actors.ActorCreator;
import im.actor.runtime.actors.ActorRef;
import im.actor.runtime.actors.messages.PoisonPill;
import im.actor.runtime.function.Constructor;
import im.actor.runtime.power.WakeLock;
public class CallManagerActor extends ModuleActor {
public static ActorCreator CONSTRUCTOR(final ModuleContext context) {
return () -> new CallManagerActor(context);
}
private static final String TAG = "CallManagerActor";
private CallsProvider provider;
private HashSet<Long> handledCalls = new HashSet<>();
private HashMap<Long, Integer> handledCallAttempts = new HashMap<>();
private HashSet<Long> answeredCalls = new HashSet<>();
private Long currentCall;
private HashMap<Long, ActorRef> runningCalls = new HashMap<>();
private boolean isBeeping = false;
public CallManagerActor(ModuleContext context) {
super(context);
}
@Override
public void preStart() {
super.preStart();
provider = config().getCallsProvider();
}
//
// Outgoing call
//
private void doCall(final Peer peer, final CommandCallback<Long> callback, boolean isVideoEnabled) {
Log.d(TAG, "doCall (" + peer + ")");
//
// Stopping current call as we started new done
//
if (currentCall != null) {
terminalCall(currentCall);
currentCall = null;
}
//
// Spawning new Actor for call
//
final WakeLock wakeLock = Runtime.makeWakeLock();
system().actorOf("actor/master/" + RandomUtils.nextRid(), () -> {
return new CallActor(peer, callback, wakeLock, isVideoEnabled, context());
});
}
private void onCallCreated(long callId, ActorRef ref) {
//
// Stopping current call some are started during call establishing
//
if (currentCall != null) {
terminalCall(currentCall);
currentCall = null;
if (isBeeping) {
isBeeping = false;
provider.stopOutgoingBeep();
}
}
//
// Saving Reference to call
//
runningCalls.put(callId, ref);
//
// Marking outgoing call as answered
//
answeredCalls.add(callId);
//
// Setting Current Call
//
currentCall = callId;
//
// Notify Provider about new current call
//
provider.onCallStart(callId);
isBeeping = true;
provider.startOutgoingBeep();
}
//
// Incoming call
//
private void onIncomingCall(final long callId, final int attempt, WakeLock wakeLock) {
Log.d(TAG, "onIncomingCall (" + callId + ")");
//
// Filter double updates about incoming call
//
if (handledCalls.contains(callId)) {
if (handledCallAttempts.get(callId) >= attempt) {
if (wakeLock != null) {
wakeLock.releaseLock();
}
return;
}
}
//
// Ignore any incoming call if we already have running call with such call id
//
if (runningCalls.containsKey(callId)) {
if (wakeLock != null) {
wakeLock.releaseLock();
}
return;
}
//
// Marking handled calls as handled
//
handledCalls.add(callId);
handledCallAttempts.put(callId, attempt);
//
// Creating wake lock if needed
//
if (wakeLock == null) {
wakeLock = Runtime.makeWakeLock();
}
//
// Spawning new Actor for call
//
final WakeLock finalWakeLock = wakeLock;
system().actorOf("actor/call" + RandomUtils.nextRid(), () -> {
return new CallActor(callId, finalWakeLock, context());
});
}
private void onIncomingCallReady(long callId, ActorRef ref) {
//
// Saving reference to incoming call
//
runningCalls.put(callId, ref);
//
// Change Current Call if there are no ongoing calls now
//
if (currentCall == null) {
currentCall = callId;
provider.onCallStart(callId);
}
}
private void onIncomingCallHandled(long callId) {
// If We are not answered this call on this device
if (!answeredCalls.contains(callId)) {
//
// Notify provider
//
if (currentCall != null && currentCall == callId) {
currentCall = null;
provider.onCallEnd(callId);
}
//
// Shutdown call actor
//
terminalCall(callId);
}
}
private void doAnswerCall(final long callId) {
Log.d(TAG, "doAnswerCall (" + callId + ")");
// If not already answered
if (!answeredCalls.contains(callId)) {
//
// Mark as answered
//
answeredCalls.add(callId);
//
// Sending answer message to actor.
//
ActorRef ref = runningCalls.get(callId);
if (ref != null) {
ref.send(new CallActor.AnswerCall());
}
//
// Notify Provider to stop playing ringtone
//
if (currentCall != null && currentCall == callId) {
provider.onCallAnswered(callId);
}
}
}
private void onCallAnswered(long callId) {
Log.d(TAG, "onCallAnswered (" + callId + ")");
if (currentCall == callId) {
if (isBeeping) {
isBeeping = false;
provider.stopOutgoingBeep();
}
provider.onCallAnswered(callId);
}
}
//
// Call AudioEnabled/Unmute
//
private void onCallAudioEnable(long callId) {
ActorRef ref = runningCalls.get(callId);
if (ref != null) {
ref.send(new AbsCallActor.AudioEnabled(true));
}
}
private void onCallAudioDisable(long callId) {
ActorRef ref = runningCalls.get(callId);
if (ref != null) {
ref.send(new AbsCallActor.AudioEnabled(false));
}
}
//
// Call video disable/enable
//
private void onCallVideoEnable(long callId) {
ActorRef ref = runningCalls.get(callId);
if (ref != null) {
ref.send(new AbsCallActor.VideoEnabled(true));
}
}
private void onCallVideoDisable(long callId) {
ActorRef ref = runningCalls.get(callId);
if (ref != null) {
ref.send(new AbsCallActor.VideoEnabled(false));
}
}
//
// Ending call
//
private void onCallEnded(long callId) {
Log.d(TAG, "onCallEnded (" + callId + ")");
//
// Event ALWAYS comes from Call Actor and we doesn't need
// to stop it explicitly.
//
// Removing from running calls
//
runningCalls.remove(callId);
//
// Notify Provider if this call was current
//
if (currentCall != null && currentCall == callId) {
currentCall = null;
provider.onCallEnd(callId);
if (isBeeping) {
isBeeping = false;
provider.stopOutgoingBeep();
}
}
}
private void doEndCall(long callId) {
Log.d(TAG, "doEndCall (" + callId + ")");
//
// Action ALWAYS comes from UI side and we need only stop call actor
// explicitly and it will do the rest.
//
ActorRef currentCallActor = runningCalls.remove(callId);
if (currentCallActor != null) {
if (answeredCalls.contains(callId)) {
currentCallActor.send(PoisonPill.INSTANCE);
} else {
currentCallActor.send(new CallActor.RejectCall());
}
}
//
// Notify Provider if this call was current
//
if (currentCall != null && currentCall == callId) {
currentCall = null;
provider.onCallEnd(callId);
if (isBeeping) {
isBeeping = false;
provider.stopOutgoingBeep();
}
}
}
private void probablyEndCall() {
if (currentCall != null) {
doEndCall(currentCall);
}
}
private void terminalCall(long callId) {
ActorRef dest = runningCalls.remove(callId);
if (dest != null) {
dest.send(PoisonPill.INSTANCE);
}
}
private void sendToCall(long callId, Object message) {
ActorRef dest = runningCalls.get(callId);
if (dest != null) {
dest.send(message);
}
}
//
// Messages
//
@Override
public void onReceive(Object message) {
if (message instanceof OnIncomingCall) {
OnIncomingCall call = (OnIncomingCall) message;
onIncomingCall(call.getCallId(), call.getAttempt(), null);
} else if (message instanceof OnIncomingCallLocked) {
OnIncomingCallLocked locked = (OnIncomingCallLocked) message;
onIncomingCall(locked.getCallId(), locked.getAttempt(), locked.getWakeLock());
} else if (message instanceof OnIncomingCallHandled) {
OnIncomingCallHandled incomingCallHandled = (OnIncomingCallHandled) message;
onIncomingCallHandled(incomingCallHandled.getCallId());
} else if (message instanceof DoAnswerCall) {
doAnswerCall(((DoAnswerCall) message).getCallId());
} else if (message instanceof DoEndCall) {
doEndCall(((DoEndCall) message).getCallId());
} else if (message instanceof OnCallEnded) {
onCallEnded(((OnCallEnded) message).getCallId());
} else if (message instanceof DoCall) {
DoCall doCall = (DoCall) message;
doCall(doCall.getPeer(), doCall.getCallback(), doCall.isEnableVideoCall());
} else if (message instanceof DoCallComplete) {
DoCallComplete callCreated = (DoCallComplete) message;
onCallCreated(callCreated.getCallId(), sender());
} else if (message instanceof IncomingCallReady) {
IncomingCallReady callComplete = (IncomingCallReady) message;
onIncomingCallReady(callComplete.getCallId(), sender());
} else if (message instanceof OnCallAnswered) {
OnCallAnswered answered = (OnCallAnswered) message;
onCallAnswered(answered.getCallId());
} else if (message instanceof AudioDisable) {
onCallAudioDisable(((AudioDisable) message).getCallId());
} else if (message instanceof AudioEnable) {
onCallAudioEnable(((AudioEnable) message).getCallId());
} else if (message instanceof DisableVideo) {
onCallVideoDisable(((DisableVideo) message).getCallId());
} else if (message instanceof EnableVideo) {
onCallVideoEnable(((EnableVideo) message).getCallId());
} else if (message instanceof ProbablyEndCall) {
probablyEndCall();
} else {
super.onReceive(message);
}
}
public static class OnIncomingCall {
private long callId;
private int attempt;
public OnIncomingCall(long callId, int attempt) {
this.callId = callId;
this.attempt = attempt;
}
public long getCallId() {
return callId;
}
public int getAttempt() {
return attempt;
}
}
public static class OnIncomingCallLocked {
private long callId;
private int attempt;
private WakeLock wakeLock;
public OnIncomingCallLocked(long callId, int attempt, WakeLock wakeLock) {
this.callId = callId;
this.wakeLock = wakeLock;
this.attempt = attempt;
}
public long getCallId() {
return callId;
}
public WakeLock getWakeLock() {
return wakeLock;
}
public int getAttempt() {
return attempt;
}
}
public static class OnIncomingCallHandled {
private long callId;
private int attempt;
public OnIncomingCallHandled(long callId, int attempt) {
this.callId = callId;
this.attempt = attempt;
}
public int getAttempt() {
return attempt;
}
public long getCallId() {
return callId;
}
}
public static class OnCallEnded {
private long callId;
public OnCallEnded(long callId) {
this.callId = callId;
}
public long getCallId() {
return callId;
}
}
public static class DoAnswerCall {
private long callId;
public DoAnswerCall(long callId) {
this.callId = callId;
}
public long getCallId() {
return callId;
}
}
public static class DoEndCall {
private long callId;
public DoEndCall(long callId) {
this.callId = callId;
}
public long getCallId() {
return callId;
}
}
//
// Call State
//
public static class AudioEnable {
private long callId;
public AudioEnable(long callId) {
this.callId = callId;
}
public long getCallId() {
return callId;
}
}
public static class AudioDisable {
private long callId;
public AudioDisable(long callId) {
this.callId = callId;
}
public long getCallId() {
return callId;
}
}
public static class DisableVideo {
private long callId;
public DisableVideo(long callId) {
this.callId = callId;
}
public long getCallId() {
return callId;
}
}
public static class EnableVideo {
private long callId;
public EnableVideo(long callId) {
this.callId = callId;
}
public long getCallId() {
return callId;
}
}
//
// Call Start
//
public static class DoCall {
private Peer peer;
private CommandCallback<Long> callback;
private boolean enableVideoCall;
public DoCall(Peer peer, CommandCallback<Long> callback, boolean enableVideoCall) {
this.peer = peer;
this.callback = callback;
this.enableVideoCall = enableVideoCall;
}
public CommandCallback<Long> getCallback() {
return callback;
}
public Peer getPeer() {
return peer;
}
public boolean isEnableVideoCall() {
return enableVideoCall;
}
}
public static class DoCallComplete {
private long callId;
public DoCallComplete(long callId) {
this.callId = callId;
}
public long getCallId() {
return callId;
}
}
public static class OnCallAnswered {
private long callId;
public OnCallAnswered(long callId) {
this.callId = callId;
}
public long getCallId() {
return callId;
}
}
public static class IncomingCallReady {
private long callId;
public IncomingCallReady(long callId) {
this.callId = callId;
}
public long getCallId() {
return callId;
}
}
public static class ProbablyEndCall {
}
}