Correctly manage busy state for (webrtc, redphone, pstn) tuple

Fixes #6156
// FREEBIE
This commit is contained in:
Moxie Marlinspike 2017-02-05 12:38:08 -08:00
parent d92cbfe305
commit fb8440f886
5 changed files with 241 additions and 100 deletions

View File

@ -18,15 +18,20 @@
package org.thoughtcrime.redphone;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.ResultReceiver;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@ -40,8 +45,7 @@ import org.thoughtcrime.redphone.call.InitiatingCallManager;
import org.thoughtcrime.redphone.call.LockManager;
import org.thoughtcrime.redphone.call.ResponderCallManager;
import org.thoughtcrime.redphone.crypto.zrtp.SASInfo;
import org.thoughtcrime.redphone.pstn.CallStateView;
import org.thoughtcrime.redphone.pstn.IncomingPstnCallListener;
import org.thoughtcrime.redphone.pstn.IncomingPstnCallReceiver;
import org.thoughtcrime.redphone.signaling.OtpCounterProvider;
import org.thoughtcrime.redphone.signaling.SessionDescriptor;
import org.thoughtcrime.redphone.signaling.SignalingException;
@ -56,13 +60,16 @@ import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.WebRtcCallService;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.internal.util.concurrent.SettableFuture;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.concurrent.ExecutionException;
import de.greenrobot.event.EventBus;
@ -75,7 +82,7 @@ import de.greenrobot.event.EventBus;
* @author Moxie Marlinspike
*
*/
public class RedPhoneService extends Service implements CallStateListener, CallStateView {
public class RedPhoneService extends Service implements CallStateListener {
private static final String TAG = RedPhoneService.class.getSimpleName();
@ -88,6 +95,7 @@ public class RedPhoneService extends Service implements CallStateListener, CallS
public static final String EXTRA_REMOTE_NUMBER = "remote_number";
public static final String EXTRA_SESSION_DESCRIPTOR = "session_descriptor";
public static final String EXTRA_MUTE = "mute_value";
public static final String EXTRA_RESULT_RECEIVER = "result_receiver";
public static final String ACTION_INCOMING_CALL = "org.thoughtcrime.redphone.RedPhoneService.INCOMING_CALL";
public static final String ACTION_OUTGOING_CALL = "org.thoughtcrime.redphone.RedPhoneService.OUTGOING_CALL";
@ -95,6 +103,7 @@ public class RedPhoneService extends Service implements CallStateListener, CallS
public static final String ACTION_DENY_CALL = "org.thoughtcrime.redphone.RedPhoneService.DENY_CALL";
public static final String ACTION_HANGUP_CALL = "org.thoughtcrime.redphone.RedPhoneService.HANGUP";
public static final String ACTION_SET_MUTE = "org.thoughtcrime.redphone.RedPhoneService.SET_MUTE";
public static final String ACTION_IS_IN_CALL_QUERY = "org.thoughtcrime.redphone.RedPhoneService.IS_IN_CALL";
private final Handler serviceHandler = new Handler();
@ -108,7 +117,7 @@ public class RedPhoneService extends Service implements CallStateListener, CallS
private LockManager lockManager;
private UncaughtExceptionHandlerManager uncaughtExceptionHandlerManager;
private IncomingPstnCallListener pstnCallListener;
private IncomingPstnCallReceiver pstnCallListener;
@Override
public void onCreate() {
@ -150,6 +159,7 @@ public class RedPhoneService extends Service implements CallStateListener, CallS
else if (intent.getAction().equals(ACTION_DENY_CALL)) handleDenyCall(intent);
else if (intent.getAction().equals(ACTION_HANGUP_CALL)) handleHangupCall(intent);
else if (intent.getAction().equals(ACTION_SET_MUTE)) handleSetMute(intent);
else if (intent.getAction().equals(ACTION_IS_IN_CALL_QUERY)) handleIsInCallQuery(intent);
else Log.w(TAG, "Unhandled intent: " + intent.getAction() + ", state: " + state);
}
@ -161,7 +171,7 @@ public class RedPhoneService extends Service implements CallStateListener, CallS
}
private void initializePstnCallListener() {
pstnCallListener = new IncomingPstnCallListener(this);
pstnCallListener = new IncomingPstnCallReceiver();
registerReceiver(pstnCallListener, new IntentFilter("android.intent.action.PHONE_STATE"));
}
@ -279,12 +289,22 @@ public class RedPhoneService extends Service implements CallStateListener, CallS
}
}
private void handleIsInCallQuery(Intent intent) {
ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER);
if (resultReceiver != null) {
resultReceiver.send(state != STATE_IDLE ? 1 : 0, null);
}
}
/// Helper Methods
private boolean isBusy() {
TelephonyManager telephonyManager = (TelephonyManager)getSystemService(TELEPHONY_SERVICE);
return ((currentCallManager != null && state != STATE_IDLE) ||
telephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE);
return ((currentCallManager != null && state != STATE_IDLE) ||
telephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) ||
WebRtcCallService.isCallActive(this);
}
private boolean isIdle() {
@ -528,22 +548,6 @@ public class RedPhoneService extends Service implements CallStateListener, CallS
}
}
@Override
public boolean isInCall() {
switch(state) {
case STATE_IDLE:
return false;
case STATE_DIALING:
case STATE_RINGING:
case STATE_ANSWERING:
case STATE_CONNECTED:
return true;
default:
Log.e(TAG, "Unhandled call state: " + state);
return false;
}
}
private static class ProximityLockRelease implements Thread.UncaughtExceptionHandler {
private final LockManager lockManager;
@ -557,4 +561,47 @@ public class RedPhoneService extends Service implements CallStateListener, CallS
lockManager.updatePhoneState(LockManager.PhoneState.IDLE);
}
}
@WorkerThread
public static boolean isCallActive(Context context) {
Log.w(TAG, "isCallActive()");
HandlerThread handlerThread = null;
try {
handlerThread = new HandlerThread("webrtc-callback");
handlerThread.start();
final SettableFuture<Boolean> future = new SettableFuture<>();
ResultReceiver resultReceiver = new ResultReceiver(new Handler(handlerThread.getLooper())) {
protected void onReceiveResult(int resultCode, Bundle resultData) {
Log.w(TAG, "onReceiveResult");
future.set(resultCode == 1);
}
};
Intent intent = new Intent(context, RedPhoneService.class);
intent.setAction(ACTION_IS_IN_CALL_QUERY);
intent.putExtra(EXTRA_RESULT_RECEIVER, resultReceiver);
context.startService(intent);
Log.w(TAG, "Blocking on result...");
return future.get();
} catch (InterruptedException | ExecutionException e) {
Log.w(TAG, e);
return false;
} finally {
if (handlerThread != null) handlerThread.quit();
}
}
public static void isCallActive(Context context, ResultReceiver resultReceiver) {
Intent intent = new Intent(context, RedPhoneService.class);
intent.setAction(ACTION_IS_IN_CALL_QUERY);
intent.putExtra(EXTRA_RESULT_RECEIVER, resultReceiver);
context.startService(intent);
}
}

View File

@ -1,10 +0,0 @@
package org.thoughtcrime.redphone.pstn;
/**
* Provides access to the state of a call
*
* @author Stuart O. Anderson
*/
public interface CallStateView {
boolean isInCall();
}

View File

@ -1,51 +0,0 @@
package org.thoughtcrime.redphone.pstn;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Listens for incoming PSTN calls and rejects them if a RedPhone call is already in progress.
*
* Unstable use of reflection employed to gain access to ITelephony.
*
* @author Stuart O. Anderson
*/
public class IncomingPstnCallListener extends BroadcastReceiver {
private static final String TAG = IncomingPstnCallListener.class.getName();
private final CallStateView callState;
public IncomingPstnCallListener(CallStateView callState) {
this.callState = callState;
}
@Override
public void onReceive(Context context, Intent intent) {
if(!(intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER) != null
&& intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(TelephonyManager.EXTRA_STATE_RINGING)
&& callState.isInCall())) {
return;
}
Log.d(TAG, "Attempting to deny incoming PSTN call.");
TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
try {
Method getTelephony = tm.getClass().getDeclaredMethod("getITelephony");
getTelephony.setAccessible(true);
Object telephonyService = getTelephony.invoke(tm);
Method endCall = telephonyService.getClass().getDeclaredMethod("endCall");
endCall.invoke(telephonyService);
Log.d(TAG, "Denied Incoming Call From: " + intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER));
} catch (NoSuchMethodException e) {
Log.d(TAG, "Unable to access ITelephony API", e);
} catch (IllegalAccessException e) {
Log.d(TAG, "Unable to access ITelephony API", e);
} catch (InvocationTargetException e) {
Log.d(TAG, "Unable to access ITelephony API", e);
}
}
}

View File

@ -0,0 +1,80 @@
package org.thoughtcrime.redphone.pstn;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.telephony.TelephonyManager;
import android.util.Log;
import org.thoughtcrime.redphone.RedPhoneService;
import org.thoughtcrime.securesms.service.WebRtcCallService;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Listens for incoming PSTN calls and rejects them if a RedPhone call is already in progress.
*
* Unstable use of reflection employed to gain access to ITelephony.
*
*/
public class IncomingPstnCallReceiver extends BroadcastReceiver {
private static final String TAG = IncomingPstnCallReceiver.class.getName();
@Override
public void onReceive(Context context, Intent intent) {
Log.w(TAG, "Checking incoming call...");
if (intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER) == null) {
Log.w(TAG, "Telephony event does not contain number...");
return;
}
if (!intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(TelephonyManager.EXTRA_STATE_RINGING)) {
Log.w(TAG, "Telephony event is not state rining...");
return;
}
InCallListener listener = new InCallListener(context, new Handler());
WebRtcCallService.isCallActive(context, listener);
RedPhoneService.isCallActive(context, listener);
}
private static class InCallListener extends ResultReceiver {
private final Context context;
InCallListener(Context context, Handler handler) {
super(handler);
this.context = context.getApplicationContext();
}
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == 1) {
Log.w(TAG, "Attempting to deny incoming PSTN call.");
TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
try {
Method getTelephony = tm.getClass().getDeclaredMethod("getITelephony");
getTelephony.setAccessible(true);
Object telephonyService = getTelephony.invoke(tm);
Method endCall = telephonyService.getClass().getDeclaredMethod("endCall");
endCall.invoke(telephonyService);
Log.w(TAG, "Denied Incoming Call.");
} catch (NoSuchMethodException e) {
Log.w(TAG, "Unable to access ITelephony API", e);
} catch (IllegalAccessException e) {
Log.w(TAG, "Unable to access ITelephony API", e);
} catch (InvocationTargetException e) {
Log.w(TAG, "Unable to access ITelephony API", e);
}
}
}
}
}

View File

@ -2,13 +2,18 @@ package org.thoughtcrime.securesms.service;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.ResultReceiver;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@ -16,10 +21,10 @@ import android.util.Pair;
import com.google.protobuf.InvalidProtocolBufferException;
import org.thoughtcrime.redphone.RedPhoneService;
import org.thoughtcrime.redphone.audio.IncomingRinger;
import org.thoughtcrime.redphone.call.LockManager;
import org.thoughtcrime.redphone.pstn.CallStateView;
import org.thoughtcrime.redphone.pstn.IncomingPstnCallListener;
import org.thoughtcrime.redphone.pstn.IncomingPstnCallReceiver;
import org.thoughtcrime.redphone.util.AudioUtils;
import org.thoughtcrime.redphone.util.UncaughtExceptionHandlerManager;
import org.thoughtcrime.securesms.ApplicationContext;
@ -71,6 +76,7 @@ import org.whispersystems.signalservice.api.messages.calls.TurnServerInfo;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.internal.util.concurrent.SettableFuture;
import java.io.IOException;
import java.nio.ByteBuffer;
@ -93,7 +99,7 @@ import static org.thoughtcrime.securesms.webrtc.CallNotificationManager.TYPE_EST
import static org.thoughtcrime.securesms.webrtc.CallNotificationManager.TYPE_INCOMING_RINGING;
import static org.thoughtcrime.securesms.webrtc.CallNotificationManager.TYPE_OUTGOING_RINGING;
public class WebRtcCallService extends Service implements InjectableType, CallStateView, PeerConnection.Observer, DataChannel.Observer {
public class WebRtcCallService extends Service implements InjectableType, PeerConnection.Observer, DataChannel.Observer {
private static final String TAG = WebRtcCallService.class.getSimpleName();
@ -111,6 +117,7 @@ public class WebRtcCallService extends Service implements InjectableType, CallSt
public static final String EXTRA_ICE_SDP = "ice_sdp";
public static final String EXTRA_ICE_SDP_MID = "ice_sdp_mid";
public static final String EXTRA_ICE_SDP_LINE_INDEX = "ice_sdp_line_index";
public static final String EXTRA_RESULT_RECEIVER = "result_receiver";
public static final String ACTION_INCOMING_CALL = "CALL_INCOMING";
public static final String ACTION_OUTGOING_CALL = "CALL_OUTGOING";
@ -120,6 +127,7 @@ public class WebRtcCallService extends Service implements InjectableType, CallSt
public static final String ACTION_SET_MUTE_AUDIO = "SET_MUTE_AUDIO";
public static final String ACTION_SET_MUTE_VIDEO = "SET_MUTE_VIDEO";
public static final String ACTION_CHECK_TIMEOUT = "CHECK_TIMEOUT";
public static final String ACTION_IS_IN_CALL_QUERY = "IS_IN_CALL";
public static final String ACTION_RESPONSE_MESSAGE = "RESPONSE_MESSAGE";
public static final String ACTION_ICE_MESSAGE = "ICE_MESSAGE";
@ -145,6 +153,9 @@ public class WebRtcCallService extends Service implements InjectableType, CallSt
private OutgoingRinger outgoingRinger;
private LockManager lockManager;
private IncomingPstnCallReceiver callReceiver;
private UncaughtExceptionHandlerManager uncaughtExceptionHandlerManager;
@Nullable private Long callId;
@Nullable private Recipient recipient;
@Nullable private PeerConnectionWrapper peerConnection;
@ -165,7 +176,8 @@ public class WebRtcCallService extends Service implements InjectableType, CallSt
initializeResources();
initializeRingers();
initializePstnCallListener();
registerIncomingPstnCallReceiver();
registerUncaughtExceptionHandler();
}
@ -194,12 +206,26 @@ public class WebRtcCallService extends Service implements InjectableType, CallSt
else if (intent.getAction().equals(ACTION_ICE_CONNECTED)) handleIceConnected(intent);
else if (intent.getAction().equals(ACTION_CALL_CONNECTED)) handleCallConnected(intent);
else if (intent.getAction().equals(ACTION_CHECK_TIMEOUT)) handleCheckTimeout(intent);
else if (intent.getAction().equals(ACTION_IS_IN_CALL_QUERY)) handleIsInCallQuery(intent);
}
});
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
if (callReceiver != null) {
unregisterReceiver(callReceiver);
}
if (uncaughtExceptionHandlerManager != null) {
uncaughtExceptionHandlerManager.unregister();
}
}
// Initializers
private void initializeRingers() {
@ -207,11 +233,6 @@ public class WebRtcCallService extends Service implements InjectableType, CallSt
this.incomingRinger = new IncomingRinger(this);
}
private void initializePstnCallListener() {
IncomingPstnCallListener pstnCallListener = new IncomingPstnCallListener(this);
registerReceiver(pstnCallListener, new IntentFilter("android.intent.action.PHONE_STATE"));
}
private void initializeResources() {
ApplicationContext.getInstance(this).injectDependencies(this);
@ -223,8 +244,13 @@ public class WebRtcCallService extends Service implements InjectableType, CallSt
this.accountManager.setSoTimeoutMillis(TimeUnit.SECONDS.toMillis(10));
}
private void registerIncomingPstnCallReceiver() {
callReceiver = new IncomingPstnCallReceiver();
registerReceiver(callReceiver, new IntentFilter("android.intent.action.PHONE_STATE"));
}
private void registerUncaughtExceptionHandler() {
UncaughtExceptionHandlerManager uncaughtExceptionHandlerManager = new UncaughtExceptionHandlerManager();
uncaughtExceptionHandlerManager = new UncaughtExceptionHandlerManager();
uncaughtExceptionHandlerManager.registerHandler(new ProximityLockRelease(lockManager));
}
@ -515,6 +541,14 @@ public class WebRtcCallService extends Service implements InjectableType, CallSt
}
}
private void handleIsInCallQuery(Intent intent) {
ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER);
if (resultReceiver != null) {
resultReceiver.send(callState != CallState.STATE_IDLE ? 1 : 0, null);
}
}
private void insertMissedCall(@NonNull Recipient recipient, boolean signal) {
Pair<Long, Long> messageAndThreadId = DatabaseFactory.getSmsDatabase(this).insertMissedCall(recipient.getNumber());
MessageNotifier.updateNotification(this, KeyCachingService.getMasterSecret(this),
@ -643,7 +677,10 @@ public class WebRtcCallService extends Service implements InjectableType, CallSt
private boolean isBusy() {
TelephonyManager telephonyManager = (TelephonyManager)getSystemService(TELEPHONY_SERVICE);
return callState != CallState.STATE_IDLE || telephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
return callState != CallState.STATE_IDLE ||
telephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE ||
RedPhoneService.isCallActive(this);
}
private boolean isIdle() {
@ -793,11 +830,6 @@ public class WebRtcCallService extends Service implements InjectableType, CallSt
return null;
}
@Override
public boolean isInCall() {
return false;
}
/// PeerConnection Observer
@Override
public void onSignalingChange(PeerConnection.SignalingState newState) {
@ -1067,4 +1099,47 @@ public class WebRtcCallService extends Service implements InjectableType, CallSt
throw new AssertionError(throwable);
}
}
@WorkerThread
public static boolean isCallActive(Context context) {
Log.w(TAG, "isCallActive()");
HandlerThread handlerThread = null;
try {
handlerThread = new HandlerThread("webrtc-callback");
handlerThread.start();
final SettableFuture<Boolean> future = new SettableFuture<>();
ResultReceiver resultReceiver = new ResultReceiver(new Handler(handlerThread.getLooper())) {
protected void onReceiveResult(int resultCode, Bundle resultData) {
Log.w(TAG, "Got result...");
future.set(resultCode == 1);
}
};
Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(ACTION_IS_IN_CALL_QUERY);
intent.putExtra(EXTRA_RESULT_RECEIVER, resultReceiver);
context.startService(intent);
Log.w(TAG, "Blocking on result...");
return future.get();
} catch (InterruptedException | ExecutionException e) {
Log.w(TAG, e);
return false;
} finally {
if (handlerThread != null) handlerThread.quit();
}
}
public static void isCallActive(Context context, ResultReceiver resultReceiver) {
Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(ACTION_IS_IN_CALL_QUERY);
intent.putExtra(EXTRA_RESULT_RECEIVER, resultReceiver);
context.startService(intent);
}
}