mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 00:59:49 +01:00
Move all files to natural position.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
package org.thoughtcrime.securesms.webrtc;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
/**
|
||||
* Manages the state of the WebRtc items in the Android notification bar.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class CallNotificationBuilder {
|
||||
|
||||
private static final int WEBRTC_NOTIFICATION = 313388;
|
||||
private static final int WEBRTC_NOTIFICATION_RINGING = 313389;
|
||||
|
||||
public static final int TYPE_INCOMING_RINGING = 1;
|
||||
public static final int TYPE_OUTGOING_RINGING = 2;
|
||||
public static final int TYPE_ESTABLISHED = 3;
|
||||
public static final int TYPE_INCOMING_CONNECTING = 4;
|
||||
|
||||
public static Notification getCallInProgressNotification(Context context, int type, Recipient recipient) {
|
||||
Intent contentIntent = new Intent(context, WebRtcCallActivity.class);
|
||||
contentIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, contentIntent, 0);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, getNotificationChannel(context, type))
|
||||
.setSmallIcon(R.drawable.ic_call_secure_white_24dp)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setOngoing(true)
|
||||
.setContentTitle(recipient.getDisplayName(context));
|
||||
|
||||
if (type == TYPE_INCOMING_CONNECTING) {
|
||||
builder.setContentText(context.getString(R.string.CallNotificationBuilder_connecting));
|
||||
builder.setPriority(NotificationCompat.PRIORITY_MIN);
|
||||
} else if (type == TYPE_INCOMING_RINGING) {
|
||||
builder.setContentText(context.getString(R.string.NotificationBarManager__incoming_signal_call));
|
||||
builder.addAction(getServiceNotificationAction(context, WebRtcCallService.ACTION_DENY_CALL, R.drawable.ic_close_grey600_32dp, R.string.NotificationBarManager__deny_call));
|
||||
builder.addAction(getActivityNotificationAction(context, WebRtcCallActivity.ANSWER_ACTION, R.drawable.ic_phone_grey600_32dp, R.string.NotificationBarManager__answer_call));
|
||||
|
||||
if (callActivityRestricted(context)) {
|
||||
builder.setFullScreenIntent(pendingIntent, true);
|
||||
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
|
||||
builder.setCategory(NotificationCompat.CATEGORY_CALL);
|
||||
}
|
||||
} else if (type == TYPE_OUTGOING_RINGING) {
|
||||
builder.setContentText(context.getString(R.string.NotificationBarManager__establishing_signal_call));
|
||||
builder.addAction(getServiceNotificationAction(context, WebRtcCallService.ACTION_LOCAL_HANGUP, R.drawable.ic_call_end_grey600_32dp, R.string.NotificationBarManager__cancel_call));
|
||||
} else {
|
||||
builder.setContentText(context.getString(R.string.NotificationBarManager_signal_call_in_progress));
|
||||
builder.addAction(getServiceNotificationAction(context, WebRtcCallService.ACTION_LOCAL_HANGUP, R.drawable.ic_call_end_grey600_32dp, R.string.NotificationBarManager__end_call));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static int getNotificationId(@NonNull Context context, int type) {
|
||||
if (callActivityRestricted(context) && type == TYPE_INCOMING_RINGING) {
|
||||
return WEBRTC_NOTIFICATION_RINGING;
|
||||
} else {
|
||||
return WEBRTC_NOTIFICATION;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public static boolean isWebRtcNotification(int notificationId) {
|
||||
return notificationId == WEBRTC_NOTIFICATION || notificationId == WEBRTC_NOTIFICATION_RINGING;
|
||||
}
|
||||
|
||||
private static @NonNull String getNotificationChannel(@NonNull Context context, int type) {
|
||||
if (callActivityRestricted(context) && type == TYPE_INCOMING_RINGING) {
|
||||
return NotificationChannels.CALLS;
|
||||
} else {
|
||||
return NotificationChannels.OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
private static NotificationCompat.Action getServiceNotificationAction(Context context, String action, int iconResId, int titleResId) {
|
||||
Intent intent = new Intent(context, WebRtcCallService.class);
|
||||
intent.setAction(action);
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
|
||||
|
||||
return new NotificationCompat.Action(iconResId, context.getString(titleResId), pendingIntent);
|
||||
}
|
||||
|
||||
private static NotificationCompat.Action getActivityNotificationAction(@NonNull Context context, @NonNull String action,
|
||||
@DrawableRes int iconResId, @StringRes int titleResId)
|
||||
{
|
||||
Intent intent = new Intent(context, WebRtcCallActivity.class);
|
||||
intent.setAction(action);
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
|
||||
|
||||
return new NotificationCompat.Action(iconResId, context.getString(titleResId), pendingIntent);
|
||||
}
|
||||
|
||||
private static boolean callActivityRestricted(@NonNull Context context) {
|
||||
return Build.VERSION.SDK_INT >= 29 && !ApplicationContext.getInstance(context).isAppVisible();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.thoughtcrime.securesms.webrtc;
|
||||
|
||||
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 org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
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.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.i(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 ringing...");
|
||||
return;
|
||||
}
|
||||
|
||||
InCallListener listener = new InCallListener(context, new Handler());
|
||||
|
||||
WebRtcCallService.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.i(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.i(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.thoughtcrime.securesms.webrtc;
|
||||
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Allows multiple default uncaught exception handlers to be registered
|
||||
*
|
||||
* Calls all registered handlers in reverse order of registration.
|
||||
* Errors in one handler do not prevent subsequent handlers from being called.
|
||||
*/
|
||||
public class UncaughtExceptionHandlerManager implements Thread.UncaughtExceptionHandler {
|
||||
private final Thread.UncaughtExceptionHandler originalHandler;
|
||||
private final List<Thread.UncaughtExceptionHandler> handlers = new ArrayList<Thread.UncaughtExceptionHandler>();
|
||||
|
||||
public UncaughtExceptionHandlerManager() {
|
||||
originalHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
registerHandler(originalHandler);
|
||||
Thread.setDefaultUncaughtExceptionHandler(this);
|
||||
}
|
||||
|
||||
public void registerHandler(Thread.UncaughtExceptionHandler handler) {
|
||||
handlers.add(handler);
|
||||
}
|
||||
|
||||
public void unregister() {
|
||||
Thread.setDefaultUncaughtExceptionHandler(originalHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, Throwable throwable) {
|
||||
for (int i = handlers.size() - 1; i >= 0; i--) {
|
||||
try {
|
||||
handlers.get(i).uncaughtException(thread, throwable);
|
||||
} catch(Throwable t) {
|
||||
Log.e("UncaughtExceptionHandlerManager", "Error in uncaught exception handling", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.thoughtcrime.securesms.webrtc;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.provider.ContactsContract;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
|
||||
public class VoiceCallShare extends Activity {
|
||||
|
||||
private static final String TAG = VoiceCallShare.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
if (getIntent().getData() != null && "content".equals(getIntent().getData().getScheme())) {
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = getContentResolver().query(getIntent().getData(), null, null, null, null);
|
||||
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
String destination = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.RawContacts.Data.DATA1));
|
||||
|
||||
SimpleTask.run(() -> Recipient.external(this, destination), recipient -> {
|
||||
if (!TextUtils.isEmpty(destination)) {
|
||||
Intent serviceIntent = new Intent(this, WebRtcCallService.class);
|
||||
serviceIntent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL);
|
||||
serviceIntent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, recipient.getId());
|
||||
startService(serviceIntent);
|
||||
|
||||
Intent activityIntent = new Intent(this, WebRtcCallActivity.class);
|
||||
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(activityIntent);
|
||||
}
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package org.thoughtcrime.securesms.webrtc.audio;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothClass;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothHeadset;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class BluetoothStateManager {
|
||||
|
||||
private static final String TAG = BluetoothStateManager.class.getSimpleName();
|
||||
|
||||
private enum ScoConnection {
|
||||
DISCONNECTED,
|
||||
IN_PROGRESS,
|
||||
CONNECTED
|
||||
}
|
||||
|
||||
private final Object LOCK = new Object();
|
||||
|
||||
private final Context context;
|
||||
private final BluetoothAdapter bluetoothAdapter;
|
||||
private BluetoothScoReceiver bluetoothScoReceiver;
|
||||
private BluetoothConnectionReceiver bluetoothConnectionReceiver;
|
||||
private final BluetoothStateListener listener;
|
||||
private final AtomicBoolean destroyed;
|
||||
|
||||
private BluetoothHeadset bluetoothHeadset = null;
|
||||
private ScoConnection scoConnection = ScoConnection.DISCONNECTED;
|
||||
private boolean wantsConnection = false;
|
||||
|
||||
public BluetoothStateManager(@NonNull Context context, @Nullable BluetoothStateListener listener) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
this.bluetoothScoReceiver = new BluetoothScoReceiver();
|
||||
this.bluetoothConnectionReceiver = new BluetoothConnectionReceiver();
|
||||
this.listener = listener;
|
||||
this.destroyed = new AtomicBoolean(false);
|
||||
|
||||
if (this.bluetoothAdapter == null)
|
||||
return;
|
||||
|
||||
requestHeadsetProxyProfile();
|
||||
|
||||
this.context.registerReceiver(bluetoothConnectionReceiver, new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED));
|
||||
|
||||
Intent sticky = this.context.registerReceiver(bluetoothScoReceiver, new IntentFilter(getScoChangeIntent()));
|
||||
|
||||
if (sticky != null) {
|
||||
bluetoothScoReceiver.onReceive(context, sticky);
|
||||
}
|
||||
|
||||
handleBluetoothStateChange();
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
destroyed.set(true);
|
||||
|
||||
if (bluetoothHeadset != null && bluetoothAdapter != null) {
|
||||
this.bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset);
|
||||
}
|
||||
|
||||
if (bluetoothConnectionReceiver != null) {
|
||||
context.unregisterReceiver(bluetoothConnectionReceiver);
|
||||
bluetoothConnectionReceiver = null;
|
||||
}
|
||||
|
||||
if (bluetoothScoReceiver != null) {
|
||||
context.unregisterReceiver(bluetoothScoReceiver);
|
||||
bluetoothScoReceiver = null;
|
||||
}
|
||||
|
||||
this.bluetoothHeadset = null;
|
||||
}
|
||||
|
||||
public void setWantsConnection(boolean enabled) {
|
||||
synchronized (LOCK) {
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||
|
||||
this.wantsConnection = enabled;
|
||||
|
||||
if (wantsConnection && isBluetoothAvailable() && scoConnection == ScoConnection.DISCONNECTED) {
|
||||
audioManager.startBluetoothSco();
|
||||
scoConnection = ScoConnection.IN_PROGRESS;
|
||||
} else if (!wantsConnection && scoConnection == ScoConnection.CONNECTED) {
|
||||
audioManager.stopBluetoothSco();
|
||||
audioManager.setBluetoothScoOn(false);
|
||||
scoConnection = ScoConnection.DISCONNECTED;
|
||||
} else if (!wantsConnection && scoConnection == ScoConnection.IN_PROGRESS) {
|
||||
audioManager.stopBluetoothSco();
|
||||
scoConnection = ScoConnection.DISCONNECTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleBluetoothStateChange() {
|
||||
if (listener != null && !destroyed.get()) listener.onBluetoothStateChanged(isBluetoothAvailable());
|
||||
}
|
||||
|
||||
private boolean isBluetoothAvailable() {
|
||||
try {
|
||||
synchronized (LOCK) {
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||
|
||||
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) return false;
|
||||
if (!audioManager.isBluetoothScoAvailableOffCall()) return false;
|
||||
|
||||
return bluetoothHeadset != null && !bluetoothHeadset.getConnectedDevices().isEmpty();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String getScoChangeIntent() {
|
||||
return AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED;
|
||||
}
|
||||
|
||||
|
||||
private void requestHeadsetProxyProfile() {
|
||||
this.bluetoothAdapter.getProfileProxy(context, new BluetoothProfile.ServiceListener() {
|
||||
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
|
||||
@Override
|
||||
public void onServiceConnected(int profile, BluetoothProfile proxy) {
|
||||
if (destroyed.get()) {
|
||||
Log.w(TAG, "Got bluetooth profile event after the service was destroyed. Ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (profile == BluetoothProfile.HEADSET) {
|
||||
synchronized (LOCK) {
|
||||
bluetoothHeadset = (BluetoothHeadset) proxy;
|
||||
}
|
||||
|
||||
Intent sticky = context.registerReceiver(null, new IntentFilter(getScoChangeIntent()));
|
||||
bluetoothScoReceiver.onReceive(context, sticky);
|
||||
|
||||
synchronized (LOCK) {
|
||||
if (wantsConnection && isBluetoothAvailable() && scoConnection == ScoConnection.DISCONNECTED) {
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||
audioManager.startBluetoothSco();
|
||||
scoConnection = ScoConnection.IN_PROGRESS;
|
||||
}
|
||||
}
|
||||
|
||||
handleBluetoothStateChange();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(int profile) {
|
||||
Log.i(TAG, "onServiceDisconnected");
|
||||
if (profile == BluetoothProfile.HEADSET) {
|
||||
bluetoothHeadset = null;
|
||||
handleBluetoothStateChange();
|
||||
}
|
||||
}
|
||||
}, BluetoothProfile.HEADSET);
|
||||
}
|
||||
|
||||
private class BluetoothScoReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent == null) return;
|
||||
Log.i(TAG, "onReceive");
|
||||
|
||||
synchronized (LOCK) {
|
||||
if (getScoChangeIntent().equals(intent.getAction())) {
|
||||
int status = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.SCO_AUDIO_STATE_ERROR);
|
||||
|
||||
if (status == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
|
||||
if (bluetoothHeadset != null) {
|
||||
List<BluetoothDevice> devices = bluetoothHeadset.getConnectedDevices();
|
||||
|
||||
for (BluetoothDevice device : devices) {
|
||||
if (bluetoothHeadset.isAudioConnected(device)) {
|
||||
int deviceClass = device.getBluetoothClass().getDeviceClass();
|
||||
|
||||
if (deviceClass == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE ||
|
||||
deviceClass == BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO ||
|
||||
deviceClass == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET)
|
||||
{
|
||||
scoConnection = ScoConnection.CONNECTED;
|
||||
|
||||
if (wantsConnection) {
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||
audioManager.setBluetoothScoOn(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleBluetoothStateChange();
|
||||
}
|
||||
}
|
||||
|
||||
private class BluetoothConnectionReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.i(TAG, "onReceive");
|
||||
handleBluetoothStateChange();
|
||||
}
|
||||
}
|
||||
|
||||
public interface BluetoothStateListener {
|
||||
public void onBluetoothStateChanged(boolean isAvailable);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package org.thoughtcrime.securesms.webrtc.audio;
|
||||
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Vibrator;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class IncomingRinger {
|
||||
|
||||
private static final String TAG = IncomingRinger.class.getSimpleName();
|
||||
|
||||
private static final long[] VIBRATE_PATTERN = {0, 1000, 1000};
|
||||
|
||||
private final Context context;
|
||||
private final Vibrator vibrator;
|
||||
|
||||
private MediaPlayer player;
|
||||
|
||||
IncomingRinger(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
}
|
||||
|
||||
public void start(@Nullable Uri uri, boolean vibrate) {
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||
|
||||
if (player != null) player.release();
|
||||
if (uri != null) player = createPlayer(uri);
|
||||
|
||||
int ringerMode = audioManager.getRingerMode();
|
||||
|
||||
if (shouldVibrate(context, player, ringerMode, vibrate)) {
|
||||
Log.i(TAG, "Starting vibration");
|
||||
vibrator.vibrate(VIBRATE_PATTERN, 1);
|
||||
}
|
||||
|
||||
if (player != null && ringerMode == AudioManager.RINGER_MODE_NORMAL) {
|
||||
try {
|
||||
if (!player.isPlaying()) {
|
||||
player.prepare();
|
||||
player.start();
|
||||
Log.i(TAG, "Playing ringtone now...");
|
||||
} else {
|
||||
Log.w(TAG, "Ringtone is already playing, declining to restart.");
|
||||
}
|
||||
} catch (IllegalStateException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
player = null;
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "Not ringing, mode: " + ringerMode);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (player != null) {
|
||||
Log.i(TAG, "Stopping ringer");
|
||||
player.release();
|
||||
player = null;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Cancelling vibrator");
|
||||
vibrator.cancel();
|
||||
}
|
||||
|
||||
private boolean shouldVibrate(Context context, MediaPlayer player, int ringerMode, boolean vibrate) {
|
||||
if (player == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return shouldVibrateNew(context, ringerMode, vibrate);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
private boolean shouldVibrateNew(Context context, int ringerMode, boolean vibrate) {
|
||||
Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
|
||||
if (vibrator == null || !vibrator.hasVibrator()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vibrate) {
|
||||
return ringerMode != AudioManager.RINGER_MODE_SILENT;
|
||||
} else {
|
||||
return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldVibrateOld(Context context, boolean vibrate) {
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||
return vibrate && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER);
|
||||
}
|
||||
|
||||
private MediaPlayer createPlayer(@NonNull Uri ringtoneUri) {
|
||||
try {
|
||||
MediaPlayer mediaPlayer = new MediaPlayer();
|
||||
|
||||
mediaPlayer.setOnErrorListener(new MediaPlayerErrorListener());
|
||||
mediaPlayer.setDataSource(context, ringtoneUri);
|
||||
mediaPlayer.setLooping(true);
|
||||
mediaPlayer.setAudioStreamType(AudioManager.STREAM_RING);
|
||||
|
||||
return mediaPlayer;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to create player for incoming call ringer");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class MediaPlayerErrorListener implements MediaPlayer.OnErrorListener {
|
||||
@Override
|
||||
public boolean onError(MediaPlayer mp, int what, int extra) {
|
||||
Log.w(TAG, "onError(" + mp + ", " + what + ", " + extra);
|
||||
player = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.thoughtcrime.securesms.webrtc.audio;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.NonNull;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class OutgoingRinger {
|
||||
|
||||
private static final String TAG = OutgoingRinger.class.getSimpleName();
|
||||
|
||||
public enum Type {
|
||||
RINGING,
|
||||
BUSY
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
|
||||
private MediaPlayer mediaPlayer;
|
||||
|
||||
public OutgoingRinger(@NonNull Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void start(Type type) {
|
||||
int soundId;
|
||||
|
||||
if (type == Type.RINGING) soundId = R.raw.redphone_outring;
|
||||
else if (type == Type.BUSY) soundId = R.raw.redphone_busy;
|
||||
else throw new IllegalArgumentException("Not a valid sound type");
|
||||
|
||||
if( mediaPlayer != null ) {
|
||||
mediaPlayer.release();
|
||||
}
|
||||
|
||||
mediaPlayer = new MediaPlayer();
|
||||
mediaPlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL);
|
||||
mediaPlayer.setLooping(true);
|
||||
|
||||
String packageName = context.getPackageName();
|
||||
Uri dataUri = Uri.parse("android.resource://" + packageName + "/" + soundId);
|
||||
|
||||
try {
|
||||
mediaPlayer.setDataSource(context, dataUri);
|
||||
mediaPlayer.prepare();
|
||||
mediaPlayer.start();
|
||||
} catch (IllegalArgumentException | SecurityException | IllegalStateException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (mediaPlayer == null) return;
|
||||
mediaPlayer.release();
|
||||
mediaPlayer = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package org.thoughtcrime.securesms.webrtc.audio;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.media.SoundPool;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
|
||||
public class SignalAudioManager {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = SignalAudioManager.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
private final IncomingRinger incomingRinger;
|
||||
private final OutgoingRinger outgoingRinger;
|
||||
|
||||
private final SoundPool soundPool;
|
||||
private final int connectedSoundId;
|
||||
private final int disconnectedSoundId;
|
||||
|
||||
public SignalAudioManager(@NonNull Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.incomingRinger = new IncomingRinger(context);
|
||||
this.outgoingRinger = new OutgoingRinger(context);
|
||||
this.soundPool = new SoundPool(1, AudioManager.STREAM_VOICE_CALL, 0);
|
||||
|
||||
this.connectedSoundId = this.soundPool.load(context, R.raw.webrtc_completed, 1);
|
||||
this.disconnectedSoundId = this.soundPool.load(context, R.raw.webrtc_disconnected, 1);
|
||||
}
|
||||
|
||||
public void initializeAudioForCall() {
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||
audioManager.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE);
|
||||
}
|
||||
|
||||
public void startIncomingRinger(@Nullable Uri ringtoneUri, boolean vibrate) {
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||
boolean speaker = !audioManager.isWiredHeadsetOn() && !audioManager.isBluetoothScoOn();
|
||||
|
||||
audioManager.setMode(AudioManager.MODE_RINGTONE);
|
||||
audioManager.setMicrophoneMute(false);
|
||||
audioManager.setSpeakerphoneOn(speaker);
|
||||
|
||||
incomingRinger.start(ringtoneUri, vibrate);
|
||||
}
|
||||
|
||||
public void startOutgoingRinger(OutgoingRinger.Type type) {
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||
audioManager.setMicrophoneMute(false);
|
||||
|
||||
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
||||
|
||||
outgoingRinger.start(type);
|
||||
}
|
||||
|
||||
public void silenceIncomingRinger() {
|
||||
incomingRinger.stop();
|
||||
}
|
||||
|
||||
public void startCommunication(boolean preserveSpeakerphone) {
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||
|
||||
incomingRinger.stop();
|
||||
outgoingRinger.stop();
|
||||
|
||||
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
||||
|
||||
if (!preserveSpeakerphone) {
|
||||
audioManager.setSpeakerphoneOn(false);
|
||||
}
|
||||
|
||||
soundPool.play(connectedSoundId, 1.0f, 1.0f, 0, 0, 1.0f);
|
||||
}
|
||||
|
||||
public void stop(boolean playDisconnected) {
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||
|
||||
incomingRinger.stop();
|
||||
outgoingRinger.stop();
|
||||
|
||||
if (playDisconnected) {
|
||||
soundPool.play(disconnectedSoundId, 1.0f, 1.0f, 0, 0, 1.0f);
|
||||
}
|
||||
|
||||
if (audioManager.isBluetoothScoOn()) {
|
||||
audioManager.setBluetoothScoOn(false);
|
||||
audioManager.stopBluetoothSco();
|
||||
}
|
||||
|
||||
audioManager.setSpeakerphoneOn(false);
|
||||
audioManager.setMicrophoneMute(false);
|
||||
audioManager.setMode(AudioManager.MODE_NORMAL);
|
||||
audioManager.abandonAudioFocus(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.webrtc.locks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
/**
|
||||
* This class is used to listen to the accelerometer to monitor the
|
||||
* orientation of the phone. The client of this class is notified when
|
||||
* the orientation changes between horizontal and vertical.
|
||||
*/
|
||||
public final class AccelerometerListener {
|
||||
private static final String TAG = "AccelerometerListener";
|
||||
private static final boolean DEBUG = true;
|
||||
private static final boolean VDEBUG = false;
|
||||
|
||||
private SensorManager mSensorManager;
|
||||
private Sensor mSensor;
|
||||
|
||||
// mOrientation is the orientation value most recently reported to the client.
|
||||
private int mOrientation;
|
||||
|
||||
// mPendingOrientation is the latest orientation computed based on the sensor value.
|
||||
// This is sent to the client after a rebounce delay, at which point it is copied to
|
||||
// mOrientation.
|
||||
private int mPendingOrientation;
|
||||
|
||||
private OrientationListener mListener;
|
||||
|
||||
// Device orientation
|
||||
public static final int ORIENTATION_UNKNOWN = 0;
|
||||
public static final int ORIENTATION_VERTICAL = 1;
|
||||
public static final int ORIENTATION_HORIZONTAL = 2;
|
||||
|
||||
private static final int ORIENTATION_CHANGED = 1234;
|
||||
|
||||
private static final int VERTICAL_DEBOUNCE = 100;
|
||||
private static final int HORIZONTAL_DEBOUNCE = 500;
|
||||
private static final double VERTICAL_ANGLE = 50.0;
|
||||
|
||||
public interface OrientationListener {
|
||||
public void orientationChanged(int orientation);
|
||||
}
|
||||
|
||||
public AccelerometerListener(Context context, OrientationListener listener) {
|
||||
mListener = listener;
|
||||
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
|
||||
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
|
||||
}
|
||||
|
||||
public void enable(boolean enable) {
|
||||
if (DEBUG) Log.d(TAG, "enable(" + enable + ")");
|
||||
synchronized (this) {
|
||||
if (enable) {
|
||||
mOrientation = ORIENTATION_UNKNOWN;
|
||||
mPendingOrientation = ORIENTATION_UNKNOWN;
|
||||
mSensorManager.registerListener(mSensorListener, mSensor,
|
||||
SensorManager.SENSOR_DELAY_NORMAL);
|
||||
} else {
|
||||
mSensorManager.unregisterListener(mSensorListener);
|
||||
mHandler.removeMessages(ORIENTATION_CHANGED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setOrientation(int orientation) {
|
||||
synchronized (this) {
|
||||
if (mPendingOrientation == orientation) {
|
||||
// Pending orientation has not changed, so do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel any pending messages.
|
||||
// We will either start a new timer or cancel alltogether
|
||||
// if the orientation has not changed.
|
||||
mHandler.removeMessages(ORIENTATION_CHANGED);
|
||||
|
||||
if (mOrientation != orientation) {
|
||||
// Set timer to send an event if the orientation has changed since its
|
||||
// previously reported value.
|
||||
mPendingOrientation = orientation;
|
||||
Message m = mHandler.obtainMessage(ORIENTATION_CHANGED);
|
||||
// set delay to our debounce timeout
|
||||
int delay = (orientation == ORIENTATION_VERTICAL ? VERTICAL_DEBOUNCE
|
||||
: HORIZONTAL_DEBOUNCE);
|
||||
mHandler.sendMessageDelayed(m, delay);
|
||||
} else {
|
||||
// no message is pending
|
||||
mPendingOrientation = ORIENTATION_UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onSensorEvent(double x, double y, double z) {
|
||||
if (VDEBUG) Log.d(TAG, "onSensorEvent(" + x + ", " + y + ", " + z + ")");
|
||||
|
||||
// If some values are exactly zero, then likely the sensor is not powered up yet.
|
||||
// ignore these events to avoid false horizontal positives.
|
||||
if (x == 0.0 || y == 0.0 || z == 0.0) return;
|
||||
|
||||
// magnitude of the acceleration vector projected onto XY plane
|
||||
double xy = Math.sqrt(x * x + y * y);
|
||||
// compute the vertical angle
|
||||
double angle = Math.atan2(xy, z);
|
||||
// convert to degrees
|
||||
angle = angle * 180.0 / Math.PI;
|
||||
int orientation = (angle > VERTICAL_ANGLE ? ORIENTATION_VERTICAL : ORIENTATION_HORIZONTAL);
|
||||
if (VDEBUG) Log.d(TAG, "angle: " + angle + " orientation: " + orientation);
|
||||
setOrientation(orientation);
|
||||
}
|
||||
|
||||
SensorEventListener mSensorListener = new SensorEventListener() {
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
onSensorEvent(event.values[0], event.values[1], event.values[2]);
|
||||
}
|
||||
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
Handler mHandler = new Handler() {
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case ORIENTATION_CHANGED:
|
||||
synchronized (this) {
|
||||
mOrientation = mPendingOrientation;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "orientation: " +
|
||||
(mOrientation == ORIENTATION_HORIZONTAL ? "horizontal"
|
||||
: (mOrientation == ORIENTATION_VERTICAL ? "vertical"
|
||||
: "unknown")));
|
||||
}
|
||||
mListener.orientationChanged(mOrientation);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package org.thoughtcrime.securesms.webrtc.locks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
/**
|
||||
* Maintains wake lock state.
|
||||
*
|
||||
* @author Stuart O. Anderson
|
||||
*/
|
||||
public class LockManager {
|
||||
|
||||
private static final String TAG = LockManager.class.getSimpleName();
|
||||
|
||||
private final PowerManager.WakeLock fullLock;
|
||||
private final PowerManager.WakeLock partialLock;
|
||||
private final WifiManager.WifiLock wifiLock;
|
||||
private final ProximityLock proximityLock;
|
||||
|
||||
private final AccelerometerListener accelerometerListener;
|
||||
private final boolean wifiLockEnforced;
|
||||
|
||||
|
||||
private int orientation = AccelerometerListener.ORIENTATION_UNKNOWN;
|
||||
private boolean proximityDisabled = false;
|
||||
|
||||
public enum PhoneState {
|
||||
IDLE,
|
||||
PROCESSING, //used when the phone is active but before the user should be alerted.
|
||||
INTERACTIVE,
|
||||
IN_CALL,
|
||||
IN_HANDS_FREE_CALL,
|
||||
IN_VIDEO
|
||||
}
|
||||
|
||||
private enum LockState {
|
||||
FULL,
|
||||
PARTIAL,
|
||||
SLEEP,
|
||||
PROXIMITY
|
||||
}
|
||||
|
||||
public LockManager(Context context) {
|
||||
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
fullLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "signal:full");
|
||||
partialLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "signal:partial");
|
||||
proximityLock = new ProximityLock(pm);
|
||||
|
||||
WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||
wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "signal:wifi");
|
||||
|
||||
fullLock.setReferenceCounted(false);
|
||||
partialLock.setReferenceCounted(false);
|
||||
wifiLock.setReferenceCounted(false);
|
||||
|
||||
accelerometerListener = new AccelerometerListener(context, new AccelerometerListener.OrientationListener() {
|
||||
@Override
|
||||
public void orientationChanged(int newOrientation) {
|
||||
orientation = newOrientation;
|
||||
Log.d(TAG, "Orentation Update: " + newOrientation);
|
||||
updateInCallLockState();
|
||||
}
|
||||
});
|
||||
|
||||
wifiLockEnforced = isWifiPowerActiveModeEnabled(context);
|
||||
}
|
||||
|
||||
private boolean isWifiPowerActiveModeEnabled(Context context) {
|
||||
int wifi_pwr_active_mode = Settings.Secure.getInt(context.getContentResolver(), "wifi_pwr_active_mode", -1);
|
||||
Log.d(TAG, "Wifi Activity Policy: " + wifi_pwr_active_mode);
|
||||
|
||||
if (wifi_pwr_active_mode == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateInCallLockState() {
|
||||
if (orientation != AccelerometerListener.ORIENTATION_HORIZONTAL && wifiLockEnforced && !proximityDisabled) {
|
||||
setLockState(LockState.PROXIMITY);
|
||||
} else {
|
||||
setLockState(LockState.FULL);
|
||||
}
|
||||
}
|
||||
|
||||
public void updatePhoneState(PhoneState state) {
|
||||
switch(state) {
|
||||
case IDLE:
|
||||
setLockState(LockState.SLEEP);
|
||||
accelerometerListener.enable(false);
|
||||
break;
|
||||
case PROCESSING:
|
||||
setLockState(LockState.PARTIAL);
|
||||
accelerometerListener.enable(false);
|
||||
break;
|
||||
case INTERACTIVE:
|
||||
setLockState(LockState.FULL);
|
||||
accelerometerListener.enable(false);
|
||||
break;
|
||||
case IN_HANDS_FREE_CALL:
|
||||
setLockState(LockState.PARTIAL);
|
||||
proximityDisabled = true;
|
||||
accelerometerListener.enable(false);
|
||||
break;
|
||||
case IN_VIDEO:
|
||||
proximityDisabled = true;
|
||||
accelerometerListener.enable(false);
|
||||
updateInCallLockState();
|
||||
break;
|
||||
case IN_CALL:
|
||||
proximityDisabled = false;
|
||||
accelerometerListener.enable(true);
|
||||
updateInCallLockState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void setLockState(LockState newState) {
|
||||
switch(newState) {
|
||||
case FULL:
|
||||
fullLock.acquire();
|
||||
partialLock.acquire();
|
||||
wifiLock.acquire();
|
||||
proximityLock.release();
|
||||
break;
|
||||
case PARTIAL:
|
||||
partialLock.acquire();
|
||||
wifiLock.acquire();
|
||||
fullLock.release();
|
||||
proximityLock.release();
|
||||
break;
|
||||
case SLEEP:
|
||||
fullLock.release();
|
||||
partialLock.release();
|
||||
wifiLock.release();
|
||||
proximityLock.release();
|
||||
break;
|
||||
case PROXIMITY:
|
||||
partialLock.acquire();
|
||||
proximityLock.acquire();
|
||||
wifiLock.acquire();
|
||||
fullLock.release();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unhandled Mode: " + newState);
|
||||
}
|
||||
Log.d(TAG, "Entered Lock State: " + newState);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.thoughtcrime.securesms.webrtc.locks;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.PowerManager;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Controls access to the proximity lock.
|
||||
* The proximity lock is not part of the public API.
|
||||
*
|
||||
* @author Stuart O. Anderson
|
||||
*/
|
||||
class ProximityLock {
|
||||
|
||||
private static final String TAG = ProximityLock.class.getSimpleName();
|
||||
|
||||
private final Method wakelockParameterizedRelease = getWakelockParamterizedReleaseMethod();
|
||||
private final Optional<PowerManager.WakeLock> proximityLock;
|
||||
|
||||
private static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32;
|
||||
private static final int WAIT_FOR_PROXIMITY_NEGATIVE = 1;
|
||||
|
||||
ProximityLock(PowerManager pm) {
|
||||
proximityLock = getProximityLock(pm);
|
||||
}
|
||||
|
||||
private Optional<PowerManager.WakeLock> getProximityLock(PowerManager pm) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (pm.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
|
||||
return Optional.fromNullable(pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, "signal:proximity"));
|
||||
} else {
|
||||
return Optional.absent();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return Optional.fromNullable(pm.newWakeLock(PROXIMITY_SCREEN_OFF_WAKE_LOCK, "signal:incall"));
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "Failed to create proximity lock", t);
|
||||
return Optional.absent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void acquire() {
|
||||
if (!proximityLock.isPresent() || proximityLock.get().isHeld()) {
|
||||
return;
|
||||
}
|
||||
|
||||
proximityLock.get().acquire();
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (!proximityLock.isPresent() || !proximityLock.get().isHeld()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
proximityLock.get().release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
|
||||
} else {
|
||||
boolean released = false;
|
||||
|
||||
if (wakelockParameterizedRelease != null) {
|
||||
try {
|
||||
wakelockParameterizedRelease.invoke(proximityLock.get(), WAIT_FOR_PROXIMITY_NEGATIVE);
|
||||
released = true;
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.w(TAG, e);
|
||||
} catch (InvocationTargetException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!released) {
|
||||
proximityLock.get().release();
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Released proximity lock:" + proximityLock.get().isHeld());
|
||||
}
|
||||
|
||||
private static Method getWakelockParamterizedReleaseMethod() {
|
||||
try {
|
||||
return PowerManager.WakeLock.class.getDeclaredMethod("release", Integer.TYPE);
|
||||
} catch (NoSuchMethodException e) {
|
||||
Log.d(TAG, "Parameterized WakeLock release not available on this device.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user