NekoX/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPBaseService.java

1894 lines
67 KiB
Java

package org.telegram.messenger.voip;
import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Icon;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.MediaPlayer;
import android.media.MediaRouter;
import android.media.RingtoneManager;
import android.media.SoundPool;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.Vibrator;
import android.telecom.CallAudioState;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.view.View;
import android.view.WindowManager;
import android.widget.RemoteViews;
import org.telegram.messenger.AccountInstance;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.BuildConfig;
import org.telegram.messenger.BuildVars;
import org.telegram.messenger.ChatObject;
import org.telegram.messenger.ContactsController;
import org.telegram.messenger.FileLoader;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.ImageLoader;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.NotificationsController;
import org.telegram.messenger.R;
import org.telegram.messenger.SharedConfig;
import org.telegram.messenger.StatsController;
import org.telegram.messenger.UserConfig;
import org.telegram.messenger.Utilities;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.TLObject;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.BottomSheet;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Components.AvatarDrawable;
import org.telegram.ui.Components.voip.VoIPHelper;
import org.telegram.ui.LaunchActivity;
import org.telegram.ui.VoIPPermissionActivity;
import org.webrtc.voiceengine.WebRtcAudioTrack;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
/**
* Created by grishka on 21.07.17.
*/
@SuppressLint("NewApi")
public abstract class VoIPBaseService extends Service implements SensorEventListener, AudioManager.OnAudioFocusChangeListener, VoIPController.ConnectionStateListener, NotificationCenter.NotificationCenterDelegate {
protected int currentAccount = -1;
public static final int STATE_WAIT_INIT = Instance.STATE_WAIT_INIT;
public static final int STATE_WAIT_INIT_ACK = Instance.STATE_WAIT_INIT_ACK;
public static final int STATE_ESTABLISHED = Instance.STATE_ESTABLISHED;
public static final int STATE_FAILED = Instance.STATE_FAILED;
public static final int STATE_RECONNECTING = Instance.STATE_RECONNECTING;
public static final int STATE_CREATING = 6;
public static final int STATE_ENDED = 11;
public static final String ACTION_HEADSET_PLUG = "android.intent.action.HEADSET_PLUG";
protected static final int ID_ONGOING_CALL_NOTIFICATION = 201;
protected static final int ID_INCOMING_CALL_NOTIFICATION = 202;
public static final int DISCARD_REASON_HANGUP = 1;
public static final int DISCARD_REASON_DISCONNECT = 2;
public static final int DISCARD_REASON_MISSED = 3;
public static final int DISCARD_REASON_LINE_BUSY = 4;
public static final int AUDIO_ROUTE_EARPIECE = 0;
public static final int AUDIO_ROUTE_SPEAKER = 1;
public static final int AUDIO_ROUTE_BLUETOOTH = 2;
protected static final boolean USE_CONNECTION_SERVICE = isDeviceCompatibleWithConnectionServiceAPI();
protected static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32;
protected static VoIPBaseService sharedInstance;
protected static Runnable setModeRunnable;
protected static final Object sync = new Object();
protected NetworkInfo lastNetInfo;
protected int currentState = 0;
protected Notification ongoingCallNotification;
protected NativeInstance tgVoip;
protected boolean wasConnected;
protected int currentStreamRequestId;
protected TLRPC.Chat chat;
protected boolean isVideoAvailable;
protected boolean notificationsDisabled;
protected boolean switchingCamera;
protected boolean isFrontFaceCamera = true;
protected String lastError;
protected PowerManager.WakeLock proximityWakelock;
protected PowerManager.WakeLock cpuWakelock;
protected boolean isProximityNear;
protected boolean isHeadsetPlugged;
protected int previousAudioOutput = -1;
protected ArrayList<StateListener> stateListeners = new ArrayList<>();
protected MediaPlayer ringtonePlayer;
protected Vibrator vibrator;
protected SoundPool soundPool;
protected int spRingbackID;
protected int spFailedID;
protected int spEndId;
protected int spVoiceChatEndId;
protected int spVoiceChatStartId;
protected int spVoiceChatConnecting;
protected int spBusyId;
protected int spConnectingId;
protected int spPlayId;
protected int spStartRecordId;
protected int spAllowTalkId;
protected boolean needPlayEndSound;
protected boolean hasAudioFocus;
protected boolean micMute;
protected boolean unmutedByHold;
protected BluetoothAdapter btAdapter;
protected Instance.TrafficStats prevTrafficStats;
protected boolean isBtHeadsetConnected;
protected boolean screenOn;
protected Runnable updateNotificationRunnable;
protected Runnable onDestroyRunnable;
protected Runnable switchingStreamTimeoutRunnable;
protected boolean playedConnectedSound;
protected boolean switchingStream;
protected int videoState = Instance.VIDEO_STATE_INACTIVE;
public TLRPC.PhoneCall privateCall;
public ChatObject.Call groupCall;
public boolean currentGroupModeStreaming = false;
protected int mySource;
protected String myJson;
protected boolean createGroupCall;
protected int scheduleDate;
protected TLRPC.InputPeer groupCallPeer;
public boolean hasFewPeers;
protected String joinHash;
protected long callStartTime;
protected boolean playingSound;
protected boolean isOutgoing;
public boolean videoCall;
protected long videoCapturer;
protected Runnable timeoutRunnable;
protected int currentStreamType;
private Boolean mHasEarpiece;
private boolean wasEstablished;
protected int signalBarCount;
protected int currentAudioState = Instance.AUDIO_STATE_ACTIVE;
protected int currentVideoState = Instance.VIDEO_STATE_INACTIVE;
protected boolean audioConfigured;
protected int audioRouteToSet = AUDIO_ROUTE_BLUETOOTH;
protected boolean speakerphoneStateToSet;
protected CallConnection systemCallConnection;
protected int callDiscardReason;
protected boolean bluetoothScoActive;
protected boolean needSwitchToBluetoothAfterScoActivates;
protected boolean didDeleteConnectionServiceContact;
protected Runnable connectingSoundRunnable;
private String currentBluetoothDeviceName;
public final SharedUIParams sharedUIParams = new SharedUIParams();
protected Runnable afterSoundRunnable = new Runnable() {
@Override
public void run() {
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
am.abandonAudioFocus(VoIPBaseService.this);
am.unregisterMediaButtonEventReceiver(new ComponentName(VoIPBaseService.this, VoIPMediaButtonReceiver.class));
if (!USE_CONNECTION_SERVICE && sharedInstance == null) {
if (isBtHeadsetConnected) {
am.stopBluetoothSco();
am.setBluetoothScoOn(false);
bluetoothScoActive = false;
}
am.setSpeakerphoneOn(false);
}
Utilities.globalQueue.postRunnable(() -> soundPool.release());
Utilities.globalQueue.postRunnable(setModeRunnable = () -> {
synchronized (sync) {
if (setModeRunnable == null) {
return;
}
setModeRunnable = null;
}
try {
am.setMode(AudioManager.MODE_NORMAL);
} catch (SecurityException x) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("Error setting audio more to normal", x);
}
}
});
}
};
boolean fetchingBluetoothDeviceName;
private BluetoothProfile.ServiceListener serviceListener = new BluetoothProfile.ServiceListener() {
@Override
public void onServiceDisconnected(int profile) {
}
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
for (BluetoothDevice device : proxy.getConnectedDevices()) {
if (proxy.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
continue;
}
currentBluetoothDeviceName = device.getName();
break;
}
BluetoothAdapter.getDefaultAdapter().closeProfileProxy(profile, proxy);
fetchingBluetoothDeviceName = false;
}
};
protected BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_HEADSET_PLUG.equals(intent.getAction())) {
isHeadsetPlugged = intent.getIntExtra("state", 0) == 1;
if (isHeadsetPlugged && proximityWakelock != null && proximityWakelock.isHeld()) {
proximityWakelock.release();
}
if (isHeadsetPlugged) {
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
if (am.isSpeakerphoneOn()) {
previousAudioOutput = 0;
} else if (am.isBluetoothScoOn()) {
previousAudioOutput = 2;
} else {
previousAudioOutput = 1;
}
setAudioOutput(1);
} else {
if (previousAudioOutput >= 0) {
setAudioOutput(previousAudioOutput);
previousAudioOutput = -1;
}
}
isProximityNear = false;
updateOutputGainControlState();
} else if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
updateNetworkType();
} else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("bt headset state = " + intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0));
}
updateBluetoothHeadsetState(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED) == BluetoothProfile.STATE_CONNECTED);
} else if (AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED.equals(intent.getAction())) {
int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
if (BuildVars.LOGS_ENABLED) {
FileLog.e("Bluetooth SCO state updated: " + state);
}
if (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED && isBtHeadsetConnected) {
if (!btAdapter.isEnabled() || btAdapter.getProfileConnectionState(BluetoothProfile.HEADSET) != BluetoothProfile.STATE_CONNECTED) {
updateBluetoothHeadsetState(false);
return;
}
}
bluetoothScoActive = state == AudioManager.SCO_AUDIO_STATE_CONNECTED;
if (bluetoothScoActive) {
fetchBluetoothDeviceName();
if (needSwitchToBluetoothAfterScoActivates) {
needSwitchToBluetoothAfterScoActivates = false;
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
am.setSpeakerphoneOn(false);
am.setBluetoothScoOn(true);
}
}
for (StateListener l : stateListeners) {
l.onAudioSettingsChanged();
}
} else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(intent.getAction())) {
String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(state)) {
hangUp();
}
} else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
screenOn = true;
for (int i = 0; i< stateListeners.size(); i++) {
stateListeners.get(i).onScreenOnChange(screenOn);
}
} else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
screenOn = false;
for (int i = 0; i< stateListeners.size(); i++) {
stateListeners.get(i).onScreenOnChange(screenOn);
}
}
}
};
public boolean hasEarpiece() {
if (USE_CONNECTION_SERVICE) {
if (systemCallConnection != null && systemCallConnection.getCallAudioState() != null) {
int routeMask = systemCallConnection.getCallAudioState().getSupportedRouteMask();
return (routeMask & (CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_WIRED_HEADSET)) != 0;
}
}
if (((TelephonyManager) getSystemService(TELEPHONY_SERVICE)).getPhoneType() != TelephonyManager.PHONE_TYPE_NONE) {
return true;
}
if (mHasEarpiece != null) {
return mHasEarpiece;
}
// not calculated yet, do it now
try {
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
Method method = AudioManager.class.getMethod("getDevicesForStream", Integer.TYPE);
Field field = AudioManager.class.getField("DEVICE_OUT_EARPIECE");
int earpieceFlag = field.getInt(null);
int bitmaskResult = (int) method.invoke(am, AudioManager.STREAM_VOICE_CALL);
// check if masked by the earpiece flag
if ((bitmaskResult & earpieceFlag) == earpieceFlag) {
mHasEarpiece = Boolean.TRUE;
} else {
mHasEarpiece = Boolean.FALSE;
}
} catch (Throwable error) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("Error while checking earpiece! ", error);
}
mHasEarpiece = Boolean.TRUE;
}
return mHasEarpiece;
}
protected int getStatsNetworkType() {
int netType = StatsController.TYPE_WIFI;
if (lastNetInfo != null) {
if (lastNetInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
netType = lastNetInfo.isRoaming() ? StatsController.TYPE_ROAMING : StatsController.TYPE_MOBILE;
}
}
return netType;
}
protected void setSwitchingCamera(boolean switching, boolean isFrontFace) {
switchingCamera = switching;
if (!switching) {
isFrontFaceCamera = isFrontFace;
for (int a = 0; a < stateListeners.size(); a++) {
StateListener l = stateListeners.get(a);
l.onCameraSwitch(isFrontFaceCamera);
}
}
}
public void registerStateListener(StateListener l) {
if (stateListeners.contains(l)) {
return;
}
stateListeners.add(l);
if (currentState != 0) {
l.onStateChanged(currentState);
}
if (signalBarCount != 0) {
l.onSignalBarsCountChanged(signalBarCount);
}
}
public void unregisterStateListener(StateListener l) {
stateListeners.remove(l);
}
public void editCallMember(TLObject object, boolean mute, int volume, Boolean raiseHand) {
if (object == null || groupCall == null) {
return;
}
TLRPC.TL_phone_editGroupCallParticipant req = new TLRPC.TL_phone_editGroupCallParticipant();
req.call = groupCall.getInputGroupCall();
if (object instanceof TLRPC.User) {
TLRPC.User user = (TLRPC.User) object;
req.participant = MessagesController.getInputPeer(user);
if (BuildVars.LOGS_ENABLED) {
FileLog.d("edit group call part id = " + req.participant.user_id + " access_hash = " + req.participant.user_id);
}
} else if (object instanceof TLRPC.Chat) {
TLRPC.Chat chat = (TLRPC.Chat) object;
req.participant = MessagesController.getInputPeer(chat);
if (BuildVars.LOGS_ENABLED) {
FileLog.d("edit group call part id = " + (req.participant.chat_id != 0 ? req.participant.chat_id : req.participant.channel_id) + " access_hash = " + req.participant.access_hash);
}
}
req.muted = mute;
if (volume >= 0) {
req.volume = volume;
req.flags |= 2;
}
if (raiseHand != null) {
req.raise_hand = raiseHand;
req.flags |= 4;
}
if (BuildVars.LOGS_ENABLED) {
FileLog.d("edit group call flags = " + req.flags);
}
int account = currentAccount;
AccountInstance.getInstance(account).getConnectionsManager().sendRequest(req, (response, error) -> {
if (response != null) {
AccountInstance.getInstance(account).getMessagesController().processUpdates((TLRPC.Updates) response, false);
}
});
}
public boolean isMicMute() {
return micMute;
}
public void toggleSpeakerphoneOrShowRouteSheet(Context context, boolean fromOverlayWindow) {
if (isBluetoothHeadsetConnected() && hasEarpiece()) {
BottomSheet.Builder builder = new BottomSheet.Builder(context)
.setTitle(LocaleController.getString("VoipOutputDevices", R.string.VoipOutputDevices), true)
.setItems(new CharSequence[]{
LocaleController.getString("VoipAudioRoutingSpeaker", R.string.VoipAudioRoutingSpeaker),
isHeadsetPlugged ? LocaleController.getString("VoipAudioRoutingHeadset", R.string.VoipAudioRoutingHeadset) : LocaleController.getString("VoipAudioRoutingEarpiece", R.string.VoipAudioRoutingEarpiece),
currentBluetoothDeviceName != null ? currentBluetoothDeviceName : LocaleController.getString("VoipAudioRoutingBluetooth", R.string.VoipAudioRoutingBluetooth)},
new int[]{R.drawable.calls_menu_speaker,
isHeadsetPlugged ? R.drawable.calls_menu_headset : R.drawable.calls_menu_phone,
R.drawable.calls_menu_bluetooth}, (dialog, which) -> {
if (getSharedInstance() == null) {
return;
}
setAudioOutput(which);
});
BottomSheet bottomSheet = builder.create();
if (fromOverlayWindow) {
if (Build.VERSION.SDK_INT >= 26) {
bottomSheet.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
} else {
bottomSheet.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
}
}
builder.show();
return;
}
if (USE_CONNECTION_SERVICE && systemCallConnection != null && systemCallConnection.getCallAudioState() != null) {
if (hasEarpiece()) {
systemCallConnection.setAudioRoute(systemCallConnection.getCallAudioState().getRoute() == CallAudioState.ROUTE_SPEAKER ? CallAudioState.ROUTE_WIRED_OR_EARPIECE : CallAudioState.ROUTE_SPEAKER);
} else {
systemCallConnection.setAudioRoute(systemCallConnection.getCallAudioState().getRoute() == CallAudioState.ROUTE_BLUETOOTH ? CallAudioState.ROUTE_WIRED_OR_EARPIECE : CallAudioState.ROUTE_BLUETOOTH);
}
} else if (audioConfigured && !USE_CONNECTION_SERVICE) {
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
if (hasEarpiece()) {
am.setSpeakerphoneOn(!am.isSpeakerphoneOn());
} else {
am.setBluetoothScoOn(!am.isBluetoothScoOn());
}
updateOutputGainControlState();
} else {
speakerphoneStateToSet = !speakerphoneStateToSet;
}
for (StateListener l : stateListeners) {
l.onAudioSettingsChanged();
}
}
protected void setAudioOutput(int which) {
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
if (USE_CONNECTION_SERVICE && systemCallConnection != null) {
switch (which) {
case 2:
systemCallConnection.setAudioRoute(CallAudioState.ROUTE_BLUETOOTH);
break;
case 1:
systemCallConnection.setAudioRoute(CallAudioState.ROUTE_WIRED_OR_EARPIECE);
break;
case 0:
systemCallConnection.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
break;
}
} else if (audioConfigured && !USE_CONNECTION_SERVICE) {
switch (which) {
case 2:
if (!bluetoothScoActive) {
needSwitchToBluetoothAfterScoActivates = true;
try {
am.startBluetoothSco();
} catch (Throwable ignore) {
}
} else {
am.setBluetoothScoOn(true);
am.setSpeakerphoneOn(false);
}
break;
case 1:
if (bluetoothScoActive) {
am.stopBluetoothSco();
bluetoothScoActive = false;
}
am.setSpeakerphoneOn(false);
am.setBluetoothScoOn(false);
break;
case 0:
if (bluetoothScoActive) {
am.stopBluetoothSco();
bluetoothScoActive = false;
}
am.setBluetoothScoOn(false);
am.setSpeakerphoneOn(true);
break;
}
updateOutputGainControlState();
} else {
switch (which) {
case 2:
audioRouteToSet = AUDIO_ROUTE_BLUETOOTH;
speakerphoneStateToSet = false;
break;
case 1:
audioRouteToSet = AUDIO_ROUTE_EARPIECE;
speakerphoneStateToSet = false;
break;
case 0:
audioRouteToSet = AUDIO_ROUTE_SPEAKER;
speakerphoneStateToSet = true;
break;
}
}
for (StateListener l : stateListeners) {
l.onAudioSettingsChanged();
}
}
public boolean isSpeakerphoneOn() {
if (USE_CONNECTION_SERVICE && systemCallConnection != null && systemCallConnection.getCallAudioState() != null) {
int route = systemCallConnection.getCallAudioState().getRoute();
return hasEarpiece() ? route == CallAudioState.ROUTE_SPEAKER : route == CallAudioState.ROUTE_BLUETOOTH;
} else if (audioConfigured && !USE_CONNECTION_SERVICE) {
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
return hasEarpiece() ? am.isSpeakerphoneOn() : am.isBluetoothScoOn();
}
return speakerphoneStateToSet;
}
public int getCurrentAudioRoute() {
if (USE_CONNECTION_SERVICE) {
if (systemCallConnection != null && systemCallConnection.getCallAudioState() != null) {
switch (systemCallConnection.getCallAudioState().getRoute()) {
case CallAudioState.ROUTE_BLUETOOTH:
return AUDIO_ROUTE_BLUETOOTH;
case CallAudioState.ROUTE_EARPIECE:
case CallAudioState.ROUTE_WIRED_HEADSET:
return AUDIO_ROUTE_EARPIECE;
case CallAudioState.ROUTE_SPEAKER:
return AUDIO_ROUTE_SPEAKER;
}
}
return audioRouteToSet;
}
if (audioConfigured) {
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
if (am.isBluetoothScoOn()) {
return AUDIO_ROUTE_BLUETOOTH;
} else if (am.isSpeakerphoneOn()) {
return AUDIO_ROUTE_SPEAKER;
} else {
return AUDIO_ROUTE_EARPIECE;
}
}
return audioRouteToSet;
}
public String getDebugString() {
return tgVoip != null ? tgVoip.getDebugInfo() : "";
}
public long getCallDuration() {
if (callStartTime == 0) {
return 0;
}
return SystemClock.elapsedRealtime() - callStartTime;
}
public static VoIPBaseService getSharedInstance() {
return sharedInstance;
}
public void stopRinging() {
if (ringtonePlayer != null) {
ringtonePlayer.stop();
ringtonePlayer.release();
ringtonePlayer = null;
}
if (vibrator != null) {
vibrator.cancel();
vibrator = null;
}
}
protected void showNotification(String name, Bitmap photo) {
Intent intent = new Intent(this, LaunchActivity.class).setAction(groupCall != null ? "voip_chat" : "voip");
if (groupCall != null) {
intent.putExtra("currentAccount", currentAccount);
}
Notification.Builder builder = new Notification.Builder(this)
.setContentTitle(groupCall != null ? LocaleController.getString("VoipVoiceChat", R.string.VoipVoiceChat) : LocaleController.getString("VoipOutgoingCall", R.string.VoipOutgoingCall))
.setContentText(name)
.setContentIntent(PendingIntent.getActivity(this, 50, intent, 0));
if (groupCall != null) {
builder.setSmallIcon(isMicMute() ? R.drawable.voicechat_muted : R.drawable.voicechat_active);
} else {
builder.setSmallIcon(R.drawable.notification);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
Intent endIntent = new Intent(this, VoIPActionsReceiver.class);
endIntent.setAction(getPackageName() + ".END_CALL");
builder.addAction(R.drawable.ic_call_end_white_24dp, groupCall != null ? LocaleController.getString("VoipGroupLeaveAlertTitle", R.string.VoipGroupLeaveAlertTitle) : LocaleController.getString("VoipEndCall", R.string.VoipEndCall), PendingIntent.getBroadcast(this, 0, endIntent, PendingIntent.FLAG_UPDATE_CURRENT));
builder.setPriority(Notification.PRIORITY_MAX);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
builder.setShowWhen(false);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setColor(0xff282e31);
builder.setColorized(true);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setColor(0xff2ca5e0);
}
if (Build.VERSION.SDK_INT >= 26) {
NotificationsController.checkOtherNotificationsChannel();
builder.setChannelId(NotificationsController.OTHER_NOTIFICATIONS_CHANNEL);
}
if (photo != null) {
builder.setLargeIcon(photo);
}
ongoingCallNotification = builder.getNotification();
startForeground(ID_ONGOING_CALL_NOTIFICATION, ongoingCallNotification);
}
protected void startRingtoneAndVibration(int chatID) {
SharedPreferences prefs = MessagesController.getNotificationsSettings(currentAccount);
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
boolean needRing = am.getRingerMode() != AudioManager.RINGER_MODE_SILENT;
if (needRing) {
ringtonePlayer = new MediaPlayer();
ringtonePlayer.setOnPreparedListener(mediaPlayer -> {
try {
ringtonePlayer.start();
} catch (Throwable e) {
FileLog.e(e);
}
});
ringtonePlayer.setLooping(true);
if (isHeadsetPlugged) {
ringtonePlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL);
} else {
ringtonePlayer.setAudioStreamType(AudioManager.STREAM_RING);
if (!USE_CONNECTION_SERVICE) {
am.requestAudioFocus(this, AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN);
}
}
try {
String notificationUri;
if (prefs.getBoolean("custom_" + chatID, false)) {
notificationUri = prefs.getString("ringtone_path_" + chatID, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE).toString());
} else {
notificationUri = prefs.getString("CallsRingtonePath", RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE).toString());
}
ringtonePlayer.setDataSource(this, Uri.parse(notificationUri));
ringtonePlayer.prepareAsync();
} catch (Exception e) {
FileLog.e(e);
if (ringtonePlayer != null) {
ringtonePlayer.release();
ringtonePlayer = null;
}
}
int vibrate;
if (prefs.getBoolean("custom_" + chatID, false)) {
vibrate = prefs.getInt("calls_vibrate_" + chatID, 0);
} else {
vibrate = prefs.getInt("vibrate_calls", 0);
}
if ((vibrate != 2 && vibrate != 4 && (am.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE || am.getRingerMode() == AudioManager.RINGER_MODE_NORMAL)) || (vibrate == 4 && am.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE)) {
vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
long duration = 700;
if (vibrate == 1) {
duration /= 2;
} else if (vibrate == 3) {
duration *= 2;
}
vibrator.vibrate(new long[]{0, duration, 500}, 0);
}
}
}
@Override
public void onDestroy() {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("=============== VoIPService STOPPING ===============");
}
stopForeground(true);
stopRinging();
if (ApplicationLoader.mainInterfacePaused || !ApplicationLoader.isScreenOn) {
MessagesController.getInstance(currentAccount).ignoreSetOnline = false;
}
NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.appDidLogout);
SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor proximity = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY);
if (proximity != null) {
sm.unregisterListener(this);
}
if (proximityWakelock != null && proximityWakelock.isHeld()) {
proximityWakelock.release();
}
if (updateNotificationRunnable != null) {
Utilities.globalQueue.cancelRunnable(updateNotificationRunnable);
updateNotificationRunnable = null;
}
if (switchingStreamTimeoutRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(switchingStreamTimeoutRunnable);
switchingStreamTimeoutRunnable = null;
}
unregisterReceiver(receiver);
if (timeoutRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(timeoutRunnable);
timeoutRunnable = null;
}
super.onDestroy();
sharedInstance = null;
AndroidUtilities.runOnUIThread(() -> NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.didEndCall));
if (tgVoip != null) {
StatsController.getInstance(currentAccount).incrementTotalCallsTime(getStatsNetworkType(), (int) (getCallDuration() / 1000) % 5);
onTgVoipPreStop();
if (tgVoip.isGroup()) {
NativeInstance instance = tgVoip;
Utilities.globalQueue.postRunnable(instance::stopGroup);
AccountInstance.getInstance(currentAccount).getConnectionsManager().cancelRequest(currentStreamRequestId, true);
currentStreamRequestId = 0;
} else {
Instance.FinalState state = tgVoip.stop();
updateTrafficStats(state.trafficStats);
onTgVoipStop(state);
}
prevTrafficStats = null;
callStartTime = 0;
tgVoip = null;
Instance.destroyInstance();
}
if (videoCapturer != 0) {
NativeInstance.destroyVideoCapturer(videoCapturer);
videoCapturer = 0;
}
cpuWakelock.release();
if (!playingSound) {
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
if (!USE_CONNECTION_SERVICE) {
if (isBtHeadsetConnected) {
am.stopBluetoothSco();
am.setBluetoothScoOn(false);
am.setSpeakerphoneOn(false);
bluetoothScoActive = false;
}
if (onDestroyRunnable == null) {
Utilities.globalQueue.postRunnable(setModeRunnable = () -> {
synchronized (sync) {
if (setModeRunnable == null) {
return;
}
setModeRunnable = null;
}
try {
am.setMode(AudioManager.MODE_NORMAL);
} catch (SecurityException x) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("Error setting audio more to normal", x);
}
}
});
}
am.abandonAudioFocus(this);
}
am.unregisterMediaButtonEventReceiver(new ComponentName(this, VoIPMediaButtonReceiver.class));
if (hasAudioFocus) {
am.abandonAudioFocus(this);
}
Utilities.globalQueue.postRunnable(() -> soundPool.release());
}
if (USE_CONNECTION_SERVICE) {
if (!didDeleteConnectionServiceContact) {
ContactsController.getInstance(currentAccount).deleteConnectionServiceContact();
}
if (systemCallConnection != null && !playingSound) {
systemCallConnection.destroy();
}
}
ConnectionsManager.getInstance(currentAccount).setAppPaused(true, false);
VoIPHelper.lastCallTime = SystemClock.elapsedRealtime();
}
public abstract long getCallID();
public abstract void hangUp();
public abstract void hangUp(Runnable onDone);
public abstract void acceptIncomingCall();
public abstract void declineIncomingCall(int reason, Runnable onDone);
public abstract void declineIncomingCall();
protected abstract Class<? extends Activity> getUIActivityClass();
public abstract CallConnection getConnectionAndStartCall();
protected abstract void startRinging();
public abstract void startRingtoneAndVibration();
protected abstract void updateServerConfig();
protected abstract void showNotification();
protected void onTgVoipPreStop() {
}
protected void onTgVoipStop(Instance.FinalState finalState) {
}
protected void initializeAccountRelatedThings() {
updateServerConfig();
NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.appDidLogout);
ConnectionsManager.getInstance(currentAccount).setAppPaused(false, false);
}
@SuppressLint("InvalidWakeLockTag")
@Override
public void onCreate() {
super.onCreate();
if (BuildVars.LOGS_ENABLED) {
FileLog.d("=============== VoIPService STARTING ===============");
}
try {
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER) != null) {
int outFramesPerBuffer = Integer.parseInt(am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER));
Instance.setBufferSize(outFramesPerBuffer);
} else {
Instance.setBufferSize(AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT) / 2);
}
cpuWakelock = ((PowerManager) getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "telegram-voip");
cpuWakelock.acquire();
btAdapter = am.isBluetoothScoAvailableOffCall() ? BluetoothAdapter.getDefaultAdapter() : null;
IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
if (!USE_CONNECTION_SERVICE) {
filter.addAction(ACTION_HEADSET_PLUG);
if (btAdapter != null) {
filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
}
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
}
registerReceiver(receiver, filter);
fetchBluetoothDeviceName();
am.registerMediaButtonEventReceiver(new ComponentName(this, VoIPMediaButtonReceiver.class));
if (!USE_CONNECTION_SERVICE && btAdapter != null && btAdapter.isEnabled()) {
try {
MediaRouter mr = (MediaRouter) getSystemService(Context.MEDIA_ROUTER_SERVICE);
if (Build.VERSION.SDK_INT < 24) {
int headsetState = btAdapter.getProfileConnectionState(BluetoothProfile.HEADSET);
updateBluetoothHeadsetState(headsetState == BluetoothProfile.STATE_CONNECTED);
for (StateListener l : stateListeners) {
l.onAudioSettingsChanged();
}
} else {
MediaRouter.RouteInfo ri = mr.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_AUDIO);
if (ri.getDeviceType() == MediaRouter.RouteInfo.DEVICE_TYPE_BLUETOOTH) {
int headsetState = btAdapter.getProfileConnectionState(BluetoothProfile.HEADSET);
updateBluetoothHeadsetState(headsetState == BluetoothProfile.STATE_CONNECTED);
for (StateListener l : stateListeners) {
l.onAudioSettingsChanged();
}
} else {
updateBluetoothHeadsetState(false);
}
}
} catch (Throwable e) {
FileLog.e(e);
}
}
} catch (Exception x) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("error initializing voip controller", x);
}
callFailed();
}
}
protected void loadResources() {
if (chat != null && SharedConfig.useMediaStream) {
currentStreamType = AudioManager.STREAM_MUSIC;
if (Build.VERSION.SDK_INT >= 21) {
WebRtcAudioTrack.setAudioTrackUsageAttribute(AudioAttributes.USAGE_MEDIA);
}
} else {
currentStreamType = AudioManager.STREAM_VOICE_CALL;
if (Build.VERSION.SDK_INT >= 21) {
WebRtcAudioTrack.setAudioTrackUsageAttribute(AudioAttributes.USAGE_VOICE_COMMUNICATION);
}
}
WebRtcAudioTrack.setAudioStreamType(currentStreamType);
Utilities.globalQueue.postRunnable(() -> {
soundPool = new SoundPool(1, currentStreamType, 0);
spConnectingId = soundPool.load(this, R.raw.voip_connecting, 1);
spRingbackID = soundPool.load(this, R.raw.voip_ringback, 1);
spFailedID = soundPool.load(this, R.raw.voip_failed, 1);
spEndId = soundPool.load(this, R.raw.voip_end, 1);
spBusyId = soundPool.load(this, R.raw.voip_busy, 1);
spVoiceChatEndId = soundPool.load(this, R.raw.voicechat_leave, 1);
spVoiceChatStartId = soundPool.load(this, R.raw.voicechat_join, 1);
spVoiceChatConnecting = soundPool.load(this, R.raw.voicechat_connecting, 1);
spAllowTalkId = soundPool.load(this, R.raw.voip_onallowtalk, 1);
spStartRecordId = soundPool.load(this, R.raw.voip_recordstart, 1);
});
}
protected void dispatchStateChanged(int state) {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("== Call " + getCallID() + " state changed to " + state + " ==");
}
currentState = state;
if (USE_CONNECTION_SERVICE && state == STATE_ESTABLISHED /*&& !wasEstablished*/ && systemCallConnection != null) {
systemCallConnection.setActive();
}
for (int a = 0; a < stateListeners.size(); a++) {
StateListener l = stateListeners.get(a);
l.onStateChanged(state);
}
}
protected void updateTrafficStats(Instance.TrafficStats trafficStats) {
if (trafficStats == null) {
trafficStats = tgVoip.getTrafficStats();
}
final long wifiSentDiff = trafficStats.bytesSentWifi - (prevTrafficStats != null ? prevTrafficStats.bytesSentWifi : 0);
final long wifiRecvdDiff = trafficStats.bytesReceivedWifi - (prevTrafficStats != null ? prevTrafficStats.bytesReceivedWifi : 0);
final long mobileSentDiff = trafficStats.bytesSentMobile - (prevTrafficStats != null ? prevTrafficStats.bytesSentMobile : 0);
final long mobileRecvdDiff = trafficStats.bytesReceivedMobile - (prevTrafficStats != null ? prevTrafficStats.bytesReceivedMobile : 0);
prevTrafficStats = trafficStats;
if (wifiSentDiff > 0) {
StatsController.getInstance(currentAccount).incrementSentBytesCount(StatsController.TYPE_WIFI, StatsController.TYPE_CALLS, wifiSentDiff);
}
if (wifiRecvdDiff > 0) {
StatsController.getInstance(currentAccount).incrementReceivedBytesCount(StatsController.TYPE_WIFI, StatsController.TYPE_CALLS, wifiRecvdDiff);
}
if (mobileSentDiff > 0) {
StatsController.getInstance(currentAccount).incrementSentBytesCount(lastNetInfo != null && lastNetInfo.isRoaming() ? StatsController.TYPE_ROAMING : StatsController.TYPE_MOBILE, StatsController.TYPE_CALLS, mobileSentDiff);
}
if (mobileRecvdDiff > 0) {
StatsController.getInstance(currentAccount).incrementReceivedBytesCount(lastNetInfo != null && lastNetInfo.isRoaming() ? StatsController.TYPE_ROAMING : StatsController.TYPE_MOBILE, StatsController.TYPE_CALLS, mobileRecvdDiff);
}
}
@SuppressLint("InvalidWakeLockTag")
protected void configureDeviceForCall() {
needPlayEndSound = true;
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
if (!USE_CONNECTION_SERVICE) {
if (currentStreamType == AudioManager.STREAM_VOICE_CALL) {
Utilities.globalQueue.postRunnable(() -> {
try {
am.setMode(AudioManager.MODE_IN_COMMUNICATION);
} catch (Exception e) {
FileLog.e(e);
}
});
}
am.requestAudioFocus(this, currentStreamType, AudioManager.AUDIOFOCUS_GAIN);
if (isBluetoothHeadsetConnected() && hasEarpiece()) {
switch (audioRouteToSet) {
case AUDIO_ROUTE_BLUETOOTH:
if (!bluetoothScoActive) {
needSwitchToBluetoothAfterScoActivates = true;
try {
am.startBluetoothSco();
} catch (Throwable ignore) {
}
} else {
am.setBluetoothScoOn(true);
am.setSpeakerphoneOn(false);
}
break;
case AUDIO_ROUTE_EARPIECE:
am.setBluetoothScoOn(false);
am.setSpeakerphoneOn(false);
break;
case AUDIO_ROUTE_SPEAKER:
am.setBluetoothScoOn(false);
am.setSpeakerphoneOn(true);
break;
}
} else if (isBluetoothHeadsetConnected()) {
am.setBluetoothScoOn(speakerphoneStateToSet);
} else {
am.setSpeakerphoneOn(speakerphoneStateToSet);
}
}
updateOutputGainControlState();
audioConfigured = true;
SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor proximity = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY);
try {
if (proximity != null) {
proximityWakelock = ((PowerManager) getSystemService(Context.POWER_SERVICE)).newWakeLock(PROXIMITY_SCREEN_OFF_WAKE_LOCK, "telegram-voip-prx");
sm.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL);
}
} catch (Exception x) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("Error initializing proximity sensor", x);
}
}
}
private void fetchBluetoothDeviceName() {
if (fetchingBluetoothDeviceName) {
return;
}
try {
currentBluetoothDeviceName = null;
fetchingBluetoothDeviceName = true;
BluetoothAdapter.getDefaultAdapter().getProfileProxy(this, serviceListener, BluetoothProfile.HEADSET);
} catch (Throwable e) {
FileLog.e(e);
}
}
@SuppressLint("NewApi")
@Override
public void onSensorChanged(SensorEvent event) {
if (unmutedByHold || currentVideoState == Instance.VIDEO_STATE_ACTIVE || videoState == Instance.VIDEO_STATE_ACTIVE) {
return;
}
if (event.sensor.getType() == Sensor.TYPE_PROXIMITY) {
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
if (isHeadsetPlugged || am.isSpeakerphoneOn() || (isBluetoothHeadsetConnected() && am.isBluetoothScoOn())) {
return;
}
boolean newIsNear = event.values[0] < Math.min(event.sensor.getMaximumRange(), 3);
checkIsNear(newIsNear);
}
}
protected void checkIsNear() {
if (currentVideoState == Instance.VIDEO_STATE_ACTIVE || videoState == Instance.VIDEO_STATE_ACTIVE) {
checkIsNear(false);
}
}
private void checkIsNear(boolean newIsNear) {
if (newIsNear != isProximityNear) {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("proximity " + newIsNear);
}
isProximityNear = newIsNear;
try {
if (isProximityNear) {
proximityWakelock.acquire();
} else {
proximityWakelock.release(1); // this is non-public API before L
}
} catch (Exception x) {
FileLog.e(x);
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
public boolean isBluetoothHeadsetConnected() {
if (USE_CONNECTION_SERVICE && systemCallConnection != null && systemCallConnection.getCallAudioState() != null) {
return (systemCallConnection.getCallAudioState().getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH) != 0;
}
return isBtHeadsetConnected;
}
public void onAudioFocusChange(int focusChange) {
if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
hasAudioFocus = true;
} else {
hasAudioFocus = false;
}
}
protected void updateBluetoothHeadsetState(boolean connected) {
if (connected == isBtHeadsetConnected) {
return;
}
if (BuildVars.LOGS_ENABLED) {
FileLog.d("updateBluetoothHeadsetState: " + connected);
}
isBtHeadsetConnected = connected;
final AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
if (connected && !isRinging() && currentState != 0) {
if (bluetoothScoActive) {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("SCO already active, setting audio routing");
}
am.setSpeakerphoneOn(false);
am.setBluetoothScoOn(true);
} else {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("startBluetoothSco");
}
needSwitchToBluetoothAfterScoActivates = true;
// some devices ignore startBluetoothSco when called immediately after the headset is connected, so delay it
AndroidUtilities.runOnUIThread(() -> {
try {
am.startBluetoothSco();
} catch (Throwable ignore) {
}
}, 500);
}
} else {
bluetoothScoActive = false;
}
for (StateListener l : stateListeners) {
l.onAudioSettingsChanged();
}
}
public String getLastError() {
return lastError;
}
public int getCallState() {
return currentState;
}
public TLRPC.InputPeer getGroupCallPeer() {
return groupCallPeer;
}
protected void updateNetworkType() {
if (tgVoip != null) {
if (tgVoip.isGroup()) {
} else {
tgVoip.setNetworkType(getNetworkType());
}
} else {
lastNetInfo = getActiveNetworkInfo();
}
}
protected int getNetworkType() {
final NetworkInfo info = lastNetInfo = getActiveNetworkInfo();
int type = Instance.NET_TYPE_UNKNOWN;
if (info != null) {
switch (info.getType()) {
case ConnectivityManager.TYPE_MOBILE:
switch (info.getSubtype()) {
case TelephonyManager.NETWORK_TYPE_GPRS:
type = Instance.NET_TYPE_GPRS;
break;
case TelephonyManager.NETWORK_TYPE_EDGE:
case TelephonyManager.NETWORK_TYPE_1xRTT:
type = Instance.NET_TYPE_EDGE;
break;
case TelephonyManager.NETWORK_TYPE_UMTS:
case TelephonyManager.NETWORK_TYPE_EVDO_0:
type = Instance.NET_TYPE_3G;
break;
case TelephonyManager.NETWORK_TYPE_HSDPA:
case TelephonyManager.NETWORK_TYPE_HSPA:
case TelephonyManager.NETWORK_TYPE_HSPAP:
case TelephonyManager.NETWORK_TYPE_HSUPA:
case TelephonyManager.NETWORK_TYPE_EVDO_A:
case TelephonyManager.NETWORK_TYPE_EVDO_B:
type = Instance.NET_TYPE_HSPA;
break;
case TelephonyManager.NETWORK_TYPE_LTE:
type = Instance.NET_TYPE_LTE;
break;
default:
type = Instance.NET_TYPE_OTHER_MOBILE;
break;
}
break;
case ConnectivityManager.TYPE_WIFI:
type = Instance.NET_TYPE_WIFI;
break;
case ConnectivityManager.TYPE_ETHERNET:
type = Instance.NET_TYPE_ETHERNET;
break;
}
}
return type;
}
protected NetworkInfo getActiveNetworkInfo() {
return ((ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
}
protected void callFailed() {
callFailed(tgVoip != null ? tgVoip.getLastError() : Instance.ERROR_UNKNOWN);
}
protected Bitmap getRoundAvatarBitmap(TLObject userOrChat) {
Bitmap bitmap = null;
try {
if (userOrChat instanceof TLRPC.User) {
TLRPC.User user = (TLRPC.User) userOrChat;
if (user.photo != null && user.photo.photo_small != null) {
BitmapDrawable img = ImageLoader.getInstance().getImageFromMemory(user.photo.photo_small, null, "50_50");
if (img != null) {
bitmap = img.getBitmap().copy(Bitmap.Config.ARGB_8888, true);
} else {
try {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inMutable = true;
bitmap = BitmapFactory.decodeFile(FileLoader.getPathToAttach(user.photo.photo_small, true).toString(), opts);
} catch (Throwable e) {
FileLog.e(e);
}
}
}
} else {
TLRPC.Chat chat = (TLRPC.Chat) userOrChat;
if (chat.photo != null && chat.photo.photo_small != null) {
BitmapDrawable img = ImageLoader.getInstance().getImageFromMemory(chat.photo.photo_small, null, "50_50");
if (img != null) {
bitmap = img.getBitmap().copy(Bitmap.Config.ARGB_8888, true);
} else {
try {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inMutable = true;
bitmap = BitmapFactory.decodeFile(FileLoader.getPathToAttach(chat.photo.photo_small, true).toString(), opts);
} catch (Throwable e) {
FileLog.e(e);
}
}
}
}
} catch (Throwable e) {
FileLog.e(e);
}
if (bitmap == null) {
Theme.createDialogsResources(this);
AvatarDrawable placeholder;
if (userOrChat instanceof TLRPC.User) {
placeholder = new AvatarDrawable((TLRPC.User) userOrChat);
} else {
placeholder = new AvatarDrawable((TLRPC.Chat) userOrChat);
}
bitmap = Bitmap.createBitmap(AndroidUtilities.dp(42), AndroidUtilities.dp(42), Bitmap.Config.ARGB_8888);
placeholder.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
placeholder.draw(new Canvas(bitmap));
}
Canvas canvas = new Canvas(bitmap);
Path circlePath = new Path();
circlePath.addCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2, bitmap.getWidth() / 2, Path.Direction.CW);
circlePath.toggleInverseFillType();
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawPath(circlePath, paint);
return bitmap;
}
protected void showIncomingNotification(String name, CharSequence subText, TLObject userOrChat, boolean video, int additionalMemberCount) {
Intent intent = new Intent(this, LaunchActivity.class);
intent.setAction("voip");
Notification.Builder builder = new Notification.Builder(this)
.setContentTitle(video ? LocaleController.getString("VoipInVideoCallBranding", R.string.VoipInVideoCallBranding) : LocaleController.getString("VoipInCallBranding", R.string.VoipInCallBranding))
.setContentText(name)
.setSmallIcon(R.drawable.notification)
.setSubText(subText)
.setContentIntent(PendingIntent.getActivity(this, 0, intent, 0));
Uri soundProviderUri = Uri.parse("content://" + BuildConfig.APPLICATION_ID + ".call_sound_provider/start_ringing");
if (Build.VERSION.SDK_INT >= 26) {
SharedPreferences nprefs = MessagesController.getGlobalNotificationsSettings();
int chanIndex = nprefs.getInt("calls_notification_channel", 0);
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationChannel oldChannel = nm.getNotificationChannel("incoming_calls2" + chanIndex);
if (oldChannel != null) {
nm.deleteNotificationChannel(oldChannel.getId());
}
NotificationChannel existingChannel = nm.getNotificationChannel("incoming_calls3" + chanIndex);
boolean needCreate = true;
if (existingChannel != null) {
if (existingChannel.getImportance() < NotificationManager.IMPORTANCE_HIGH || !soundProviderUri.equals(existingChannel.getSound()) || existingChannel.getVibrationPattern() != null || existingChannel.shouldVibrate()) {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("User messed up the notification channel; deleting it and creating a proper one");
}
nm.deleteNotificationChannel("incoming_calls3" + chanIndex);
chanIndex++;
nprefs.edit().putInt("calls_notification_channel", chanIndex).commit();
} else {
needCreate = false;
}
}
if (needCreate) {
AudioAttributes attrs = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setLegacyStreamType(AudioManager.STREAM_RING)
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.build();
NotificationChannel chan = new NotificationChannel("incoming_calls3" + chanIndex, LocaleController.getString("IncomingCalls", R.string.IncomingCalls), NotificationManager.IMPORTANCE_HIGH);
chan.setSound(soundProviderUri, attrs);
chan.enableVibration(false);
chan.enableLights(false);
chan.setBypassDnd(true);
try {
nm.createNotificationChannel(chan);
} catch (Exception e) {
FileLog.e(e);
this.stopSelf();
return;
}
}
builder.setChannelId("incoming_calls3" + chanIndex);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setSound(soundProviderUri, AudioManager.STREAM_RING);
}
Intent endIntent = new Intent(this, VoIPActionsReceiver.class);
endIntent.setAction(getPackageName() + ".DECLINE_CALL");
endIntent.putExtra("call_id", getCallID());
CharSequence endTitle = LocaleController.getString("VoipDeclineCall", R.string.VoipDeclineCall);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
endTitle = new SpannableString(endTitle);
((SpannableString) endTitle).setSpan(new ForegroundColorSpan(0xFFF44336), 0, endTitle.length(), 0);
}
PendingIntent endPendingIntent = PendingIntent.getBroadcast(this, 0, endIntent, PendingIntent.FLAG_CANCEL_CURRENT);
builder.addAction(R.drawable.ic_call_end_white_24dp, endTitle, endPendingIntent);
Intent answerIntent = new Intent(this, VoIPActionsReceiver.class);
answerIntent.setAction(getPackageName() + ".ANSWER_CALL");
answerIntent.putExtra("call_id", getCallID());
CharSequence answerTitle = LocaleController.getString("VoipAnswerCall", R.string.VoipAnswerCall);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
answerTitle = new SpannableString(answerTitle);
((SpannableString) answerTitle).setSpan(new ForegroundColorSpan(0xFF00AA00), 0, answerTitle.length(), 0);
}
PendingIntent answerPendingIntent = PendingIntent.getBroadcast(this, 0, answerIntent, PendingIntent.FLAG_CANCEL_CURRENT);
builder.addAction(R.drawable.ic_call, answerTitle, answerPendingIntent);
builder.setPriority(Notification.PRIORITY_MAX);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
builder.setShowWhen(false);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setColor(0xff2ca5e0);
builder.setVibrate(new long[0]);
builder.setCategory(Notification.CATEGORY_CALL);
builder.setFullScreenIntent(PendingIntent.getActivity(this, 0, intent, 0), true);
if (userOrChat instanceof TLRPC.User) {
TLRPC.User user = (TLRPC.User) userOrChat;
if (!TextUtils.isEmpty(user.phone)) {
builder.addPerson("tel:" + user.phone);
}
}
}
Notification incomingNotification = builder.getNotification();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
RemoteViews customView = new RemoteViews(getPackageName(), LocaleController.isRTL ? R.layout.call_notification_rtl : R.layout.call_notification);
customView.setTextViewText(R.id.name, name);
boolean subtitleVisible = true;
if (TextUtils.isEmpty(subText)) {
customView.setViewVisibility(R.id.subtitle, View.GONE);
if (UserConfig.getActivatedAccountsCount() > 1) {
TLRPC.User self = UserConfig.getInstance(currentAccount).getCurrentUser();
customView.setTextViewText(R.id.title, video ? LocaleController.formatString("VoipInVideoCallBrandingWithName", R.string.VoipInVideoCallBrandingWithName, ContactsController.formatName(self.first_name, self.last_name)) : LocaleController.formatString("VoipInCallBrandingWithName", R.string.VoipInCallBrandingWithName, ContactsController.formatName(self.first_name, self.last_name)));
} else {
customView.setTextViewText(R.id.title, video ? LocaleController.getString("VoipInVideoCallBranding", R.string.VoipInVideoCallBranding) : LocaleController.getString("VoipInCallBranding", R.string.VoipInCallBranding));
}
} else {
if (UserConfig.getActivatedAccountsCount() > 1) {
TLRPC.User self = UserConfig.getInstance(currentAccount).getCurrentUser();
customView.setTextViewText(R.id.subtitle, LocaleController.formatString("VoipAnsweringAsAccount", R.string.VoipAnsweringAsAccount, ContactsController.formatName(self.first_name, self.last_name)));
} else {
customView.setViewVisibility(R.id.subtitle, View.GONE);
}
customView.setTextViewText(R.id.title, subText);
}
Bitmap avatar = getRoundAvatarBitmap(userOrChat);
customView.setTextViewText(R.id.answer_text, LocaleController.getString("VoipAnswerCall", R.string.VoipAnswerCall));
customView.setTextViewText(R.id.decline_text, LocaleController.getString("VoipDeclineCall", R.string.VoipDeclineCall));
customView.setImageViewBitmap(R.id.photo, avatar);
customView.setOnClickPendingIntent(R.id.answer_btn, answerPendingIntent);
customView.setOnClickPendingIntent(R.id.decline_btn, endPendingIntent);
builder.setLargeIcon(avatar);
incomingNotification.headsUpContentView = incomingNotification.bigContentView = customView;
}
startForeground(ID_INCOMING_CALL_NOTIFICATION, incomingNotification);
startRingtoneAndVibration();
}
protected void callFailed(String error) {
try {
throw new Exception("Call " + getCallID() + " failed with error: " + error);
} catch (Exception x) {
FileLog.e(x);
}
lastError = error;
AndroidUtilities.runOnUIThread(() -> dispatchStateChanged(STATE_FAILED));
if (TextUtils.equals(error, Instance.ERROR_LOCALIZED) && soundPool != null) {
playingSound = true;
Utilities.globalQueue.postRunnable(() -> soundPool.play(spFailedID, 1, 1, 0, 0, 1));
AndroidUtilities.runOnUIThread(afterSoundRunnable, 1000);
}
if (USE_CONNECTION_SERVICE && systemCallConnection != null) {
systemCallConnection.setDisconnected(new DisconnectCause(DisconnectCause.ERROR));
systemCallConnection.destroy();
systemCallConnection = null;
}
stopSelf();
}
void callFailedFromConnectionService() {
if (isOutgoing) {
callFailed(Instance.ERROR_CONNECTION_SERVICE);
} else {
hangUp();
}
}
@Override
public void onConnectionStateChanged(int newState, boolean inTransition) {
if (newState == STATE_FAILED) {
callFailed();
return;
}
if (newState == STATE_ESTABLISHED) {
if (connectingSoundRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(connectingSoundRunnable);
connectingSoundRunnable = null;
}
Utilities.globalQueue.postRunnable(() -> {
if (spPlayId != 0) {
soundPool.stop(spPlayId);
spPlayId = 0;
}
});
if (groupCall == null && !wasEstablished) {
wasEstablished = true;
if (!isProximityNear && !privateCall.video) {
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
if (vibrator.hasVibrator()) {
vibrator.vibrate(100);
}
}
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
if (tgVoip != null) {
StatsController.getInstance(currentAccount).incrementTotalCallsTime(getStatsNetworkType(), 5);
AndroidUtilities.runOnUIThread(this, 5000);
}
}
}, 5000);
if (isOutgoing) {
StatsController.getInstance(currentAccount).incrementSentItemsCount(getStatsNetworkType(), StatsController.TYPE_CALLS, 1);
} else {
StatsController.getInstance(currentAccount).incrementReceivedItemsCount(getStatsNetworkType(), StatsController.TYPE_CALLS, 1);
}
}
}
if (newState == STATE_RECONNECTING) {
Utilities.globalQueue.postRunnable(() -> {
if (spPlayId != 0) {
soundPool.stop(spPlayId);
}
spPlayId = soundPool.play(groupCall != null ? spVoiceChatConnecting : spConnectingId, 1, 1, 0, -1, 1);
});
}
dispatchStateChanged(newState);
}
public void playStartRecordSound() {
Utilities.globalQueue.postRunnable(() -> soundPool.play(spStartRecordId, 0.5f, 0.5f, 0, 0, 1));
}
public void playAllowTalkSound() {
Utilities.globalQueue.postRunnable(() -> soundPool.play(spAllowTalkId, 0.5f, 0.5f, 0, 0, 1));
}
@Override
public void onSignalBarCountChanged(int newCount) {
AndroidUtilities.runOnUIThread(() -> {
signalBarCount = newCount;
for (int a = 0; a < stateListeners.size(); a++) {
StateListener l = stateListeners.get(a);
l.onSignalBarsCountChanged(newCount);
}
});
}
public boolean isBluetoothOn() {
final AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
return am.isBluetoothScoOn();
}
public boolean isBluetoothWillOn() {
return needSwitchToBluetoothAfterScoActivates;
}
public boolean isHeadsetPlugged() {
return isHeadsetPlugged;
}
public void onMediaStateUpdated(int audioState, int videoState) {
AndroidUtilities.runOnUIThread(() -> {
currentAudioState = audioState;
currentVideoState = videoState;
checkIsNear();
for (int a = 0; a < stateListeners.size(); a++) {
StateListener l = stateListeners.get(a);
l.onMediaStateUpdated(audioState, videoState);
}
});
}
protected void callEnded() {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("Call " + getCallID() + " ended");
}
if (groupCall != null && (!playedConnectedSound || onDestroyRunnable != null)) {
needPlayEndSound = false;
}
AndroidUtilities.runOnUIThread(() -> dispatchStateChanged(STATE_ENDED));
int delay = 700;
Utilities.globalQueue.postRunnable(() -> {
if (spPlayId != 0) {
soundPool.stop(spPlayId);
spPlayId = 0;
}
});
if (connectingSoundRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(connectingSoundRunnable);
connectingSoundRunnable = null;
}
if (needPlayEndSound) {
playingSound = true;
if (groupCall == null) {
Utilities.globalQueue.postRunnable(() -> soundPool.play(spEndId, 1, 1, 0, 0, 1));
} else {
Utilities.globalQueue.postRunnable(() -> soundPool.play(spVoiceChatEndId, 1.0f, 1.0f, 0, 0, 1), 100);
delay = 500;
}
AndroidUtilities.runOnUIThread(afterSoundRunnable, delay);
}
if (timeoutRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(timeoutRunnable);
timeoutRunnable = null;
}
endConnectionServiceCall(needPlayEndSound ? delay : 0);
stopSelf();
}
protected void endConnectionServiceCall(long delay) {
if (USE_CONNECTION_SERVICE) {
Runnable r = () -> {
if (systemCallConnection != null) {
switch (callDiscardReason) {
case DISCARD_REASON_HANGUP:
systemCallConnection.setDisconnected(new DisconnectCause(isOutgoing ? DisconnectCause.LOCAL : DisconnectCause.REJECTED));
break;
case DISCARD_REASON_DISCONNECT:
systemCallConnection.setDisconnected(new DisconnectCause(DisconnectCause.ERROR));
break;
case DISCARD_REASON_LINE_BUSY:
systemCallConnection.setDisconnected(new DisconnectCause(DisconnectCause.BUSY));
break;
case DISCARD_REASON_MISSED:
systemCallConnection.setDisconnected(new DisconnectCause(isOutgoing ? DisconnectCause.CANCELED : DisconnectCause.MISSED));
break;
default:
systemCallConnection.setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
break;
}
systemCallConnection.destroy();
systemCallConnection = null;
}
};
if (delay > 0) {
AndroidUtilities.runOnUIThread(r, delay);
} else {
r.run();
}
}
}
public boolean isOutgoing() {
return isOutgoing;
}
public void handleNotificationAction(Intent intent) {
if ((getPackageName() + ".END_CALL").equals(intent.getAction())) {
stopForeground(true);
hangUp();
} else if ((getPackageName() + ".DECLINE_CALL").equals(intent.getAction())) {
stopForeground(true);
declineIncomingCall(DISCARD_REASON_LINE_BUSY, null);
} else if ((getPackageName() + ".ANSWER_CALL").equals(intent.getAction())) {
acceptIncomingCallFromNotification();
}
}
private void acceptIncomingCallFromNotification() {
showNotification();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && (checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED || privateCall.video && checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)) {
try {
PendingIntent.getActivity(VoIPBaseService.this, 0, new Intent(VoIPBaseService.this, VoIPPermissionActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0).send();
} catch (Exception x) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("Error starting permission activity", x);
}
}
return;
}
acceptIncomingCall();
try {
PendingIntent.getActivity(VoIPBaseService.this, 0, new Intent(VoIPBaseService.this, getUIActivityClass()).setAction("voip"), 0).send();
} catch (Exception x) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("Error starting incall activity", x);
}
}
}
public void updateOutputGainControlState() {
if (tgVoip != null) {
if (!USE_CONNECTION_SERVICE) {
final AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
tgVoip.setAudioOutputGainControlEnabled(hasEarpiece() && !am.isSpeakerphoneOn() && !am.isBluetoothScoOn() && !isHeadsetPlugged);
tgVoip.setEchoCancellationStrength(isHeadsetPlugged || (hasEarpiece() && !am.isSpeakerphoneOn() && !am.isBluetoothScoOn() && !isHeadsetPlugged) ? 0 : 1);
} else {
final boolean isEarpiece = systemCallConnection.getCallAudioState().getRoute() == CallAudioState.ROUTE_EARPIECE;
tgVoip.setAudioOutputGainControlEnabled(isEarpiece);
tgVoip.setEchoCancellationStrength(isEarpiece ? 0 : 1);
}
}
}
public int getAccount() {
return currentAccount;
}
@Override
public void didReceivedNotification(int id, int account, Object... args) {
if (id == NotificationCenter.appDidLogout) {
callEnded();
}
}
public static boolean isAnyKindOfCallActive() {
if (VoIPService.getSharedInstance() != null) {
return VoIPService.getSharedInstance().getCallState() != VoIPService.STATE_WAITING_INCOMING;
}
return false;
}
protected boolean isFinished() {
return currentState == STATE_ENDED || currentState == STATE_FAILED;
}
protected boolean isRinging() {
return false;
}
public int getCurrentAudioState() {
return currentAudioState;
}
public int getCurrentVideoState() {
return currentVideoState;
}
@TargetApi(Build.VERSION_CODES.O)
protected PhoneAccountHandle addAccountToTelecomManager() {
TelecomManager tm = (TelecomManager) getSystemService(TELECOM_SERVICE);
TLRPC.User self = UserConfig.getInstance(currentAccount).getCurrentUser();
PhoneAccountHandle handle = new PhoneAccountHandle(new ComponentName(this, TelegramConnectionService.class), "" + self.id);
PhoneAccount account = new PhoneAccount.Builder(handle, ContactsController.formatName(self.first_name, self.last_name))
.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
.setIcon(Icon.createWithResource(this, R.drawable.ic_launcher_dr))
.setHighlightColor(0xff2ca5e0)
.addSupportedUriScheme("sip")
.build();
tm.registerPhoneAccount(account);
return handle;
}
private static boolean isDeviceCompatibleWithConnectionServiceAPI() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return false;
}
// some non-Google devices don't implement the ConnectionService API correctly so, sadly,
// we'll have to whitelist only a handful of known-compatible devices for now
return false;/*"angler".equals(Build.PRODUCT) // Nexus 6P
|| "bullhead".equals(Build.PRODUCT) // Nexus 5X
|| "sailfish".equals(Build.PRODUCT) // Pixel
|| "marlin".equals(Build.PRODUCT) // Pixel XL
|| "walleye".equals(Build.PRODUCT) // Pixel 2
|| "taimen".equals(Build.PRODUCT) // Pixel 2 XL
|| "blueline".equals(Build.PRODUCT) // Pixel 3
|| "crosshatch".equals(Build.PRODUCT) // Pixel 3 XL
|| MessagesController.getGlobalMainSettings().getBoolean("dbg_force_connection_service", false);*/
}
public interface StateListener {
default void onStateChanged(int state) {
}
default void onSignalBarsCountChanged(int count) {
}
default void onAudioSettingsChanged() {
}
default void onMediaStateUpdated(int audioState, int videoState) {
}
default void onCameraSwitch(boolean isFrontFace) {
}
default void onVideoAvailableChange(boolean isAvailable) {
}
default void onScreenOnChange(boolean screenOn) {
}
}
public class CallConnection extends Connection {
public CallConnection() {
setConnectionProperties(PROPERTY_SELF_MANAGED);
setAudioModeIsVoip(true);
}
@Override
public void onCallAudioStateChanged(CallAudioState state) {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("ConnectionService call audio state changed: " + state);
}
for (StateListener l : stateListeners) {
l.onAudioSettingsChanged();
}
}
@Override
public void onDisconnect() {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("ConnectionService onDisconnect");
}
setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
destroy();
systemCallConnection = null;
hangUp();
}
@Override
public void onAnswer() {
acceptIncomingCallFromNotification();
}
@Override
public void onReject() {
needPlayEndSound = false;
declineIncomingCall(DISCARD_REASON_HANGUP, null);
}
@Override
public void onShowIncomingCallUi() {
startRinging();
}
@Override
public void onStateChanged(int state) {
super.onStateChanged(state);
if (BuildVars.LOGS_ENABLED) {
FileLog.d("ConnectionService onStateChanged " + stateToString(state));
}
if (state == Connection.STATE_ACTIVE) {
ContactsController.getInstance(currentAccount).deleteConnectionServiceContact();
didDeleteConnectionServiceContact = true;
}
}
@Override
public void onCallEvent(String event, Bundle extras) {
super.onCallEvent(event, extras);
if (BuildVars.LOGS_ENABLED)
FileLog.d("ConnectionService onCallEvent " + event);
}
//undocumented API
public void onSilence() {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("onSlience");
}
stopRinging();
}
}
public static class SharedUIParams {
public boolean tapToVideoTooltipWasShowed;
public boolean cameraAlertWasShowed;
public boolean wasVideoCall;
}
}