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

1518 lines
56 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.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
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.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.ViewGroup;
import android.widget.RemoteViews;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.BuildConfig;
import org.telegram.messenger.BuildVars;
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.StatsController;
import org.telegram.messenger.UserConfig;
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.VoIPPermissionActivity;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* Created by grishka on 21.07.17.
*/
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 = TgVoip.STATE_WAIT_INIT;
public static final int STATE_WAIT_INIT_ACK = TgVoip.STATE_WAIT_INIT_ACK;
public static final int STATE_ESTABLISHED = TgVoip.STATE_ESTABLISHED;
public static final int STATE_FAILED = TgVoip.STATE_FAILED;
public static final int STATE_RECONNECTING = TgVoip.STATE_RECONNECTING;
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 NetworkInfo lastNetInfo;
protected int currentState = 0;
protected Notification ongoingCallNotification;
protected TgVoip.Instance tgVoip;
protected String lastError;
protected PowerManager.WakeLock proximityWakelock;
protected PowerManager.WakeLock cpuWakelock;
protected boolean isProximityNear;
protected boolean isHeadsetPlugged;
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 spBusyId;
protected int spConnectingId;
protected int spPlayID;
protected boolean needPlayEndSound;
protected boolean haveAudioFocus;
protected boolean micMute;
protected BluetoothAdapter btAdapter;
protected TgVoip.TrafficStats prevTrafficStats;
protected boolean isBtHeadsetConnected;
protected Runnable afterSoundRunnable=new Runnable(){
@Override
public void run(){
soundPool.release();
if(USE_CONNECTION_SERVICE)
return;
if(isBtHeadsetConnected)
((AudioManager) ApplicationLoader.applicationContext.getSystemService(AUDIO_SERVICE)).stopBluetoothSco();
((AudioManager)ApplicationLoader.applicationContext.getSystemService(AUDIO_SERVICE)).setSpeakerphoneOn(false);
}
};
protected long callStartTime;
protected boolean playingSound;
protected boolean isOutgoing;
protected Runnable timeoutRunnable;
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();
}
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 && 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();
}
}
}
};
private Boolean mHasEarpiece = null;
private boolean wasEstablished;
protected int signalBarCount;
protected boolean audioConfigured;
protected int audioRouteToSet=AUDIO_ROUTE_BLUETOOTH;
protected boolean speakerphoneStateToSet;
protected CallConnection systemCallConnection;
protected int callDiscardReason;
protected boolean bluetoothScoActive=false;
protected boolean needSwitchToBluetoothAfterScoActivates=false;
protected boolean didDeleteConnectionServiceContact=false;
protected Runnable connectingSoundRunnable;
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;
}
public void registerStateListener(StateListener l) {
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 setMicMute(boolean mute) {
micMute = mute;
if (tgVoip != null) {
tgVoip.setMuteMicrophone(mute);
}
}
public boolean isMicMute() {
return micMute;
}
public void toggleSpeakerphoneOrShowRouteSheet(Activity activity){
if(isBluetoothHeadsetConnected() && hasEarpiece()){
BottomSheet.Builder bldr=new BottomSheet.Builder(activity)
.setItems(new CharSequence[]{LocaleController.getString("VoipAudioRoutingBluetooth", R.string.VoipAudioRoutingBluetooth),
LocaleController.getString("VoipAudioRoutingEarpiece", R.string.VoipAudioRoutingEarpiece),
LocaleController.getString("VoipAudioRoutingSpeaker", R.string.VoipAudioRoutingSpeaker)},
new int[]{R.drawable.ic_bluetooth_white_24dp,
R.drawable.ic_phone_in_talk_white_24dp,
R.drawable.ic_volume_up_white_24dp}, new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface dialog, int which){
AudioManager am=(AudioManager)getSystemService(AUDIO_SERVICE);
if(getSharedInstance()==null)
return;
if(USE_CONNECTION_SERVICE && systemCallConnection!=null){
switch(which){
case 0:
systemCallConnection.setAudioRoute(CallAudioState.ROUTE_BLUETOOTH);
break;
case 1:
systemCallConnection.setAudioRoute(CallAudioState.ROUTE_WIRED_OR_EARPIECE);
break;
case 2:
systemCallConnection.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
break;
}
}else if(audioConfigured && !USE_CONNECTION_SERVICE){
switch(which){
case 0:
if(!bluetoothScoActive){
needSwitchToBluetoothAfterScoActivates=true;
try {
am.startBluetoothSco();
} catch (Throwable ignore) {
}
}else{
am.setBluetoothScoOn(true);
am.setSpeakerphoneOn(false);
}
break;
case 1:
if(bluetoothScoActive)
am.stopBluetoothSco();
am.setSpeakerphoneOn(false);
am.setBluetoothScoOn(false);
break;
case 2:
if(bluetoothScoActive)
am.stopBluetoothSco();
am.setBluetoothScoOn(false);
am.setSpeakerphoneOn(true);
break;
}
updateOutputGainControlState();
}else{
switch(which){
case 0:
audioRouteToSet=AUDIO_ROUTE_BLUETOOTH;
break;
case 1:
audioRouteToSet=AUDIO_ROUTE_EARPIECE;
break;
case 2:
audioRouteToSet=AUDIO_ROUTE_SPEAKER;
break;
}
}
for(StateListener l:stateListeners)
l.onAudioSettingsChanged();
}
});
BottomSheet sheet=bldr.create();
sheet.setBackgroundColor(0xff2b2b2b);
sheet.show();
ViewGroup container=sheet.getSheetContainer();
for(int i=0;i<container.getChildCount();i++){
View child = container.getChildAt(i);
if (!(child instanceof BottomSheet.BottomSheetCell)) continue;
BottomSheet.BottomSheetCell cell=(BottomSheet.BottomSheetCell) child;
cell.setTextColor(0xFFFFFFFF);
}
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();
}
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, TLRPC.FileLocation photo, Class<? extends Activity> activity) {
Intent intent = new Intent(this, activity);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
Notification.Builder builder = new Notification.Builder(this)
.setContentTitle(LocaleController.getString("VoipOutgoingCall", R.string.VoipOutgoingCall))
.setContentText(name)
.setSmallIcon(R.drawable.notification)
.setContentIntent(PendingIntent.getActivity(this, 0, intent, 0));
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, 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.LOLLIPOP) {
builder.setColor(0xff2ca5e0);
}
if (Build.VERSION.SDK_INT >= 26) {
NotificationsController.checkOtherNotificationsChannel();
builder.setChannelId(NotificationsController.OTHER_NOTIFICATIONS_CHANNEL);
}
if (photo!= null) {
BitmapDrawable img = ImageLoader.getInstance().getImageFromMemory(photo, null, "50_50");
if (img != null) {
builder.setLargeIcon(img.getBitmap());
} else {
try {
float scaleFactor = 160.0f / AndroidUtilities.dp(50);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = scaleFactor < 1 ? 1 : (int) scaleFactor;
Bitmap bitmap = BitmapFactory.decodeFile(FileLoader.getPathToAttach(photo, true).toString(), options);
if (bitmap != null) {
builder.setLargeIcon(bitmap);
}
} catch (Throwable e) {
FileLog.e(e);
}
}
}
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(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP){
try{
int mode=Settings.Global.getInt(getContentResolver(), "zen_mode");
if(needRing)
needRing=mode==0;
}catch(Exception ignore){}
}*/
if(needRing){
if(!USE_CONNECTION_SERVICE){
am.requestAudioFocus(this, AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN);
}
ringtonePlayer=new MediaPlayer();
ringtonePlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener(){
@Override
public void onPrepared(MediaPlayer mediaPlayer){
ringtonePlayer.start();
}
});
ringtonePlayer.setLooping(true);
ringtonePlayer.setAudioStreamType(AudioManager.STREAM_RING);
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);
}
}
}
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();
@Override
public void onDestroy() {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("=============== VoIPService STOPPING ===============");
}
stopForeground(true);
stopRinging();
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();
}
unregisterReceiver(receiver);
if(timeoutRunnable!=null){
AndroidUtilities.cancelRunOnUIThread(timeoutRunnable);
timeoutRunnable=null;
}
super.onDestroy();
sharedInstance = null;
AndroidUtilities.runOnUIThread(new Runnable(){
@Override
public void run(){
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.didEndCall);
}
});
if (tgVoip != null) {
updateTrafficStats();
StatsController.getInstance(currentAccount).incrementTotalCallsTime(getStatsNetworkType(), (int) (getCallDuration() / 1000) % 5);
onTgVoipPreStop();
onTgVoipStop(tgVoip.stop());
prevTrafficStats = null;
callStartTime = 0;
tgVoip = null;
}
cpuWakelock.release();
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
if(!USE_CONNECTION_SERVICE){
if(isBtHeadsetConnected && !playingSound){
am.stopBluetoothSco();
am.setBluetoothScoOn(false);
am.setSpeakerphoneOn(false);
}
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 (haveAudioFocus)
am.abandonAudioFocus(this);
if (!playingSound)
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=System.currentTimeMillis();
}
protected void onTgVoipPreStop() {
}
protected void onTgVoipStop(TgVoip.FinalState finalState) {
}
protected void initializeAccountRelatedThings(){
updateServerConfig();
NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.appDidLogout);
ConnectionsManager.getInstance(currentAccount).setAppPaused(false, false);
}
@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));
TgVoip.setBufferSize(outFramesPerBuffer);
} else {
TgVoip.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);
}
registerReceiver(receiver, filter);
soundPool = new SoundPool(1, AudioManager.STREAM_VOICE_CALL, 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);
am.registerMediaButtonEventReceiver(new ComponentName(this, VoIPMediaButtonReceiver.class));
if(!USE_CONNECTION_SERVICE && btAdapter!=null && btAdapter.isEnabled()){
int headsetState=btAdapter.getProfileConnectionState(BluetoothProfile.HEADSET);
updateBluetoothHeadsetState(headsetState==BluetoothProfile.STATE_CONNECTED);
//if(headsetState==BluetoothProfile.STATE_CONNECTED)
// am.setBluetoothScoOn(true);
for (StateListener l : stateListeners)
l.onAudioSettingsChanged();
}
} catch (Exception x) {
if (BuildVars.LOGS_ENABLED) {
FileLog.e("error initializing voip controller", x);
}
callFailed();
}
}
protected abstract void updateServerConfig();
protected abstract void showNotification();
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() {
final TgVoip.TrafficStats 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);
}
}
protected void configureDeviceForCall() {
needPlayEndSound = true;
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
if(!USE_CONNECTION_SERVICE){
am.setMode(AudioManager.MODE_IN_COMMUNICATION);
am.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, 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);
}
}/*else{
if(isBluetoothHeadsetConnected() && hasEarpiece()){
switch(audioRouteToSet){
case AUDIO_ROUTE_BLUETOOTH:
systemCallConnection.setAudioRoute(CallAudioState.ROUTE_BLUETOOTH);
break;
case AUDIO_ROUTE_EARPIECE:
systemCallConnection.setAudioRoute(CallAudioState.ROUTE_WIRED_OR_EARPIECE);
break;
case AUDIO_ROUTE_SPEAKER:
systemCallConnection.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
break;
}
}else{
if(hasEarpiece())
systemCallConnection.setAudioRoute(!speakerphoneStateToSet ? CallAudioState.ROUTE_WIRED_OR_EARPIECE : CallAudioState.ROUTE_SPEAKER);
else
systemCallConnection.setAudioRoute(!speakerphoneStateToSet ? CallAudioState.ROUTE_WIRED_OR_EARPIECE : CallAudioState.ROUTE_BLUETOOTH);
}
}*/
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);
}
}
}
@SuppressLint("NewApi")
@Override
public void onSensorChanged(SensorEvent event) {
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);
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) {
haveAudioFocus = true;
} else {
haveAudioFocus = 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(new Runnable(){
@Override
public void run(){
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;
}
protected void updateNetworkType() {
if (tgVoip != null) {
tgVoip.setNetworkType(getNetworkType());
} else {
lastNetInfo = getActiveNetworkInfo();
}
}
protected int getNetworkType() {
final NetworkInfo info = lastNetInfo = getActiveNetworkInfo();
int type = TgVoip.NET_TYPE_UNKNOWN;
if (info != null) {
switch (info.getType()) {
case ConnectivityManager.TYPE_MOBILE:
switch (info.getSubtype()) {
case TelephonyManager.NETWORK_TYPE_GPRS:
type = TgVoip.NET_TYPE_GPRS;
break;
case TelephonyManager.NETWORK_TYPE_EDGE:
case TelephonyManager.NETWORK_TYPE_1xRTT:
type = TgVoip.NET_TYPE_EDGE;
break;
case TelephonyManager.NETWORK_TYPE_UMTS:
case TelephonyManager.NETWORK_TYPE_EVDO_0:
type = TgVoip.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 = TgVoip.NET_TYPE_HSPA;
break;
case TelephonyManager.NETWORK_TYPE_LTE:
type = TgVoip.NET_TYPE_LTE;
break;
default:
type = TgVoip.NET_TYPE_OTHER_MOBILE;
break;
}
break;
case ConnectivityManager.TYPE_WIFI:
type = TgVoip.NET_TYPE_WIFI;
break;
case ConnectivityManager.TYPE_ETHERNET:
type = TgVoip.NET_TYPE_ETHERNET;
break;
}
}
return type;
}
protected NetworkInfo getActiveNetworkInfo() {
return ((ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
}
protected void callFailed() {
callFailed(tgVoip != null ? tgVoip.getLastError() : TgVoip.ERROR_UNKNOWN);
}
protected Bitmap getRoundAvatarBitmap(TLObject userOrChat){
Bitmap bitmap=null;
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);
}
}
}
}
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, List<TLRPC.User> groupUsers, int additionalMemberCount, Class<? extends Activity> activityOnClick) {
Intent intent = new Intent(this, activityOnClick);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
Notification.Builder builder = new Notification.Builder(this)
.setContentTitle(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_calls"+chanIndex);
if(oldChannel!=null)
nm.deleteNotificationChannel(oldChannel.getId());
NotificationChannel existingChannel=nm.getNotificationChannel("incoming_calls2"+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_calls2"+chanIndex);
chanIndex++;
nprefs.edit().putInt("calls_notification_channel", chanIndex).apply();
}else{
needCreate=false;
}
}
if(needCreate){
AudioAttributes attrs=new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
.build();
NotificationChannel chan=new NotificationChannel("incoming_calls2"+chanIndex, LocaleController.getString("IncomingCalls", R.string.IncomingCalls), NotificationManager.IMPORTANCE_HIGH);
chan.setSound(soundProviderUri, attrs);
chan.enableVibration(false);
chan.enableLights(false);
nm.createNotificationChannel(chan);
}
builder.setChannelId("incoming_calls2"+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);
}
}
}
/*Bitmap photoBitmap=null;
if (photo != null) {
BitmapDrawable img = ImageLoader.getInstance().getImageFromMemory(photo, null, "50_50");
if (img != null) {
builder.setLargeIcon(photoBitmap=img.getBitmap());
} else {
try {
float scaleFactor = 160.0f / AndroidUtilities.dp(50);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = scaleFactor < 1 ? 1 : (int) scaleFactor;
Bitmap bitmap = BitmapFactory.decodeFile(FileLoader.getPathToAttach(photo, true).toString(), options);
if (bitmap != null) {
builder.setLargeIcon(photoBitmap=bitmap);
}
} catch (Throwable e) {
FileLog.e(e);
}
}
}*/
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);
subtitleVisible=false;
if(UserConfig.getActivatedAccountsCount()>1){
TLRPC.User self=UserConfig.getInstance(currentAccount).getCurrentUser();
customView.setTextViewText(R.id.title, LocaleController.formatString("VoipInCallBrandingWithName", R.string.VoipInCallBrandingWithName, ContactsController.formatName(self.first_name, self.last_name)));
}else{
customView.setTextViewText(R.id.title, 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);
subtitleVisible=false;
}
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);
/*if(groupUsers==null || groupUsers.size()==0){
customView.setViewVisibility(R.id.group_photos, View.GONE);
}else{
int[] ids={R.id.group_photo1, R.id.group_photo2, R.id.group_photo3};
for(int i=0;i<3;i++){
if(i<groupUsers.size()){
customView.setImageViewBitmap(ids[i], getRoundAvatarBitmap(groupUsers.get(i)));
}else{
customView.setViewVisibility(ids[i], View.GONE);
}
}
if(additionalMemberCount>0){
customView.setTextViewText(R.id.group_more, LocaleController.formatString("VoipGroupMoreMembers", R.string.VoipGroupMoreMembers, additionalMemberCount));
}else{
customView.setViewVisibility(R.id.group_more, View.GONE);
}
int viewCount=Math.min(groupUsers.size(), 3)+(additionalMemberCount>0 ? 1 : 0);
int padding=AndroidUtilities.dp(22*viewCount+4*(viewCount-1));
customView.setViewPadding(R.id.name, LocaleController.isRTL ? padding : 0, 0, LocaleController.isRTL ? 0 : padding, 0);
if(subtitleVisible){
customView.setViewPadding(R.id.title, LocaleController.isRTL ? padding : 0, 0, LocaleController.isRTL ? 0 : padding, 0);
}
}*/
incomingNotification.headsUpContentView=incomingNotification.bigContentView=customView;
}
startForeground(ID_INCOMING_CALL_NOTIFICATION, incomingNotification);
}
protected void callFailed(String error) {
try {
throw new Exception("Call " + getCallID() + " failed with error: " + error);
} catch (Exception x) {
FileLog.e(x);
}
lastError = error;
dispatchStateChanged(STATE_FAILED);
if (TextUtils.equals(error, TgVoip.ERROR_LOCALIZED) && soundPool != null) {
playingSound = true;
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();
}
/*package*/ void callFailedFromConnectionService(){
if(isOutgoing)
callFailed(TgVoip.ERROR_CONNECTION_SERVICE);
else
hangUp();
}
@Override
public void onConnectionStateChanged(int newState) {
if (newState == STATE_FAILED) {
callFailed();
return;
}
if (newState == STATE_ESTABLISHED) {
if(connectingSoundRunnable!=null){
AndroidUtilities.cancelRunOnUIThread(connectingSoundRunnable);
connectingSoundRunnable=null;
}
if (spPlayID != 0) {
soundPool.stop(spPlayID);
spPlayID = 0;
}
if(!wasEstablished){
wasEstablished=true;
if(!isProximityNear){
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){
if(spPlayID!=0)
soundPool.stop(spPlayID);
spPlayID=soundPool.play(spConnectingId, 1, 1, 0, -1, 1);
}
dispatchStateChanged(newState);
}
@Override
public void onSignalBarCountChanged(int newCount){
signalBarCount=newCount;
for (int a = 0; a < stateListeners.size(); a++) {
StateListener l = stateListeners.get(a);
l.onSignalBarsCountChanged(newCount);
}
}
protected void callEnded() {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("Call " + getCallID() + " ended");
}
dispatchStateChanged(STATE_ENDED);
if (needPlayEndSound) {
playingSound = true;
soundPool.play(spEndId, 1, 1, 0, 0, 1);
AndroidUtilities.runOnUIThread(afterSoundRunnable, 700);
}
if(timeoutRunnable!=null){
AndroidUtilities.cancelRunOnUIThread(timeoutRunnable);
timeoutRunnable=null;
}
endConnectionServiceCall(needPlayEndSound ? 700 : 0);
stopSelf();
}
protected void endConnectionServiceCall(long delay){
if(USE_CONNECTION_SERVICE){
Runnable r=new Runnable(){
@Override
public void run(){
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){
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()).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_SINGLE_TOP), 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(VoIPGroupService.getSharedInstance()!=null){
// return VoIPGroupService.getSharedInstance().getCallState()!=VoIPGroupService.STATE_INVITED;
/*}else*/ 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;
}
@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_foreground))
.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 "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 {
void onStateChanged(int state);
void onSignalBarsCountChanged(int count);
void onAudioSettingsChanged();
}
@TargetApi(Build.VERSION_CODES.O)
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
//@Override
public void onSilence(){
if(BuildVars.LOGS_ENABLED)
FileLog.d("onSlience");
stopRinging();
}
}
}