From fb6296774d9e1677f390d3aaac208ccd67011a91 Mon Sep 17 00:00:00 2001 From: Ergyun Syuleyman Date: Mon, 8 Jun 2020 13:13:38 +0300 Subject: [PATCH] NY-10522: [AN]: Handle accept and end call commands from BT device --- .../nynja/mobile/communicator/NynjaApp.java | 75 ++++++++++++ .../HeadphoneEventsBroadcastReceiver.java | 18 ++- .../HeadphoneMonitoringService.java | 10 ++ .../audio/headphones/data/HeadphoneState.kt | 6 + .../communicator/data/sdk/BaseSDKModule.kt | 5 + .../data/sdk/calls/ConferenceSDKModule.java | 108 ++++++++++++++++-- .../presenters/IncomeConferencePresenter.java | 13 ++- .../communicator/mvp/view/IncomeCallView.java | 2 + .../ui/activities/MainActivity.java | 19 --- .../activities/calls/IncomeCallActivity.java | 7 ++ .../utils/NotificationHelper.java | 3 +- 11 files changed, 234 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/nynja/mobile/communicator/NynjaApp.java b/app/src/main/java/com/nynja/mobile/communicator/NynjaApp.java index 3c0a94f275..a069547200 100644 --- a/app/src/main/java/com/nynja/mobile/communicator/NynjaApp.java +++ b/app/src/main/java/com/nynja/mobile/communicator/NynjaApp.java @@ -1,11 +1,17 @@ package com.nynja.mobile.communicator; import android.app.Application; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; import android.content.res.Configuration; +import android.media.AudioManager; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; import android.os.StrictMode; import android.support.multidex.MultiDex; import android.support.v7.app.AppCompatDelegate; @@ -13,11 +19,21 @@ import android.util.Base64; import com.clevertap.android.sdk.ActivityLifecycleCallback; import com.facebook.stetho.Stetho; +import com.nynja.mobile.communicator.data.RxBus; +import com.nynja.mobile.communicator.data.audio.headphones.HeadphoneEventsBroadcastReceiver; +import com.nynja.mobile.communicator.data.audio.headphones.HeadphoneMonitoringService; +import com.nynja.mobile.communicator.data.audio.headphones.data.HeadphoneState; +import com.nynja.mobile.communicator.data.conference.ActiveCallBase; +import com.nynja.mobile.communicator.data.models.events.local.Event; +import com.nynja.mobile.communicator.data.models.events.local.LocalEvent; +import com.nynja.mobile.communicator.data.sdk.calls.ActiveConferenceCall; import com.nynja.mobile.communicator.injection.ApplicationComponent; import com.nynja.mobile.communicator.injection.ApplicationModule; import com.nynja.mobile.communicator.injection.DaggerApplicationComponent; +import com.nynja.mobile.communicator.ui.activities.MainActivity; import com.nynja.mobile.communicator.utils.AndroidUtilities; import com.nynja.mobile.communicator.utils.Consts; +import com.nynja.mobile.communicator.utils.QrUtils; import com.nynja.mobile.communicator.utils.Utils; import com.nynja.mobile.communicator.utils.container.EmojiManager; import com.nynja.mobile.communicator.utils.container.data.NynjaEmojiProvider; @@ -32,6 +48,10 @@ import org.webrtc.ContextUtils; import java.security.MessageDigest; import io.intercom.android.sdk.Intercom; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.PublishSubject; import timber.log.Timber; /** @@ -46,6 +66,10 @@ public class NynjaApp extends Application { private static final String PROD_FLAVOR = "prod"; private static ApplicationComponent sApplicationComponent; + AudioManager mAudioManager; + ComponentName mReceiverComponent; + private RxBus rxBus; + private Disposable mDisposable; @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); @@ -115,6 +139,57 @@ public class NynjaApp extends Application { // Intercom support Intercom.initialize(this, API_KEY_INTERCOM, APP_ID_INTERCOM); + + // register for media buttons + registerForMediaButtons(); + + registerForMediaDevices(); + } + + @Override + public void onTerminate() { + mAudioManager.unregisterMediaButtonEventReceiver(mReceiverComponent); + mDisposable = null; + super.onTerminate(); + } + + // register for media buttons + private void registerForMediaButtons() { + rxBus = sApplicationComponent.dataManager().getBus(); + registerForMediaButtonsLocalInputEvents(); + mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + mReceiverComponent = new ComponentName(this, HeadphoneEventsBroadcastReceiver.class); + mAudioManager.registerMediaButtonEventReceiver(mReceiverComponent); + + } + + private boolean filterMediaButtonsLocalEvent(Event.Type type) { + if (type == Event.Type.HeadphoneEvent ) return true; + return false; + } + + private void registerForMediaDevices() { + try { + if (HeadphoneMonitoringService.isBluetoothHeadsetConnected()) { + rxBus.localEvent(HeadphoneState.Companion.connected()); + } + new Handler(Looper.getMainLooper()).postDelayed(() -> { + startService(new Intent(this, HeadphoneMonitoringService.class)); + }, Consts.DELAY_100); + } catch (Exception ex) { + Timber.e(ex); + } + } + + private void registerForMediaButtonsLocalInputEvents() { + if (mDisposable != null) return; + mDisposable = rxBus.getLocalInputEvent() + .filter(localEvent -> filterMediaButtonsLocalEvent(localEvent.getType())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(localEvent -> { + sApplicationComponent.dataManager().getConferenceSDK().onMediaButtonsLocalEvent(this, localEvent); + }, Timber::e); } // Timber apps logger diff --git a/app/src/main/java/com/nynja/mobile/communicator/data/audio/headphones/HeadphoneEventsBroadcastReceiver.java b/app/src/main/java/com/nynja/mobile/communicator/data/audio/headphones/HeadphoneEventsBroadcastReceiver.java index c95ee54e1d..4e60648479 100644 --- a/app/src/main/java/com/nynja/mobile/communicator/data/audio/headphones/HeadphoneEventsBroadcastReceiver.java +++ b/app/src/main/java/com/nynja/mobile/communicator/data/audio/headphones/HeadphoneEventsBroadcastReceiver.java @@ -9,6 +9,8 @@ import com.nynja.mobile.communicator.NynjaApp; import com.nynja.mobile.communicator.data.RxBus; import com.nynja.mobile.communicator.data.audio.headphones.data.HeadphoneState; +import timber.log.Timber; + public class HeadphoneEventsBroadcastReceiver extends BroadcastReceiver { private RxBus mBus; @@ -21,9 +23,11 @@ public class HeadphoneEventsBroadcastReceiver extends BroadcastReceiver { abortBroadcast(); KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); + int keyCode = keyEvent.getKeyCode(); + Timber.d("HeadphoneEventsBroadcastReceiver::onReceive(): " + + "KeyEvent.ACTION=%d, code=%d", keyEvent.getAction(), keyCode); - if (keyEvent != null && keyEvent.getAction() == KeyEvent.ACTION_UP) { - int keyCode = keyEvent.getKeyCode(); + if (keyEvent != null && keyEvent.getAction() == KeyEvent.ACTION_DOWN) { switch (keyCode) { case KeyEvent.KEYCODE_MEDIA_NEXT: mBus.localEvent(HeadphoneState.Companion.next()); @@ -32,7 +36,17 @@ public class HeadphoneEventsBroadcastReceiver extends BroadcastReceiver { mBus.localEvent(HeadphoneState.Companion.prev()); break; case KeyEvent.KEYCODE_MEDIA_PLAY: + mBus.localEvent(HeadphoneState.Companion.play()); + break; + case KeyEvent.KEYCODE_MEDIA_STOP: + mBus.localEvent(HeadphoneState.Companion.stop()); + break; + case KeyEvent.KEYCODE_MEDIA_PAUSE: + mBus.localEvent(HeadphoneState.Companion.pause()); + break; + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_ENDCALL: mBus.localEvent(HeadphoneState.Companion.playPause()); break; default: diff --git a/app/src/main/java/com/nynja/mobile/communicator/data/audio/headphones/HeadphoneMonitoringService.java b/app/src/main/java/com/nynja/mobile/communicator/data/audio/headphones/HeadphoneMonitoringService.java index 57bb4d1f80..1518aef0f2 100644 --- a/app/src/main/java/com/nynja/mobile/communicator/data/audio/headphones/HeadphoneMonitoringService.java +++ b/app/src/main/java/com/nynja/mobile/communicator/data/audio/headphones/HeadphoneMonitoringService.java @@ -1,7 +1,9 @@ package com.nynja.mobile.communicator.data.audio.headphones; import android.app.Service; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; import android.content.Intent; import android.content.IntentFilter; import android.os.IBinder; @@ -11,6 +13,13 @@ public class HeadphoneMonitoringService extends Service { private HeadphoneStateBroadcastReceiver headsetStateReceiver; + public static boolean isBluetoothHeadsetConnected() { + BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + return mBluetoothAdapter != null && + mBluetoothAdapter.isEnabled() && + mBluetoothAdapter.getProfileConnectionState(BluetoothHeadset.HEADSET) == BluetoothHeadset.STATE_CONNECTED; + } + @Override public void onCreate() { @@ -31,6 +40,7 @@ public class HeadphoneMonitoringService extends Service { @Override public void onDestroy() { unregisterReceiver(headsetStateReceiver); + super.onDestroy(); } @Nullable @Override public IBinder onBind(Intent intent) { diff --git a/app/src/main/java/com/nynja/mobile/communicator/data/audio/headphones/data/HeadphoneState.kt b/app/src/main/java/com/nynja/mobile/communicator/data/audio/headphones/data/HeadphoneState.kt index 0f102dadb3..fc0a85ac73 100644 --- a/app/src/main/java/com/nynja/mobile/communicator/data/audio/headphones/data/HeadphoneState.kt +++ b/app/src/main/java/com/nynja/mobile/communicator/data/audio/headphones/data/HeadphoneState.kt @@ -9,6 +9,9 @@ class HeadphoneState private constructor(val state: States) : SimpleEvent(Event. companion object { fun connected() = HeadphoneState(States.Connected) fun disconnected() = HeadphoneState(States.Disconnected) + fun play() = HeadphoneState(States.Play) + fun pause() = HeadphoneState(States.Pause) + fun stop() = HeadphoneState(States.Stop) fun playPause() = HeadphoneState(States.PlayPause) fun next() = HeadphoneState(States.Next) fun prev() = HeadphoneState(States.Prev) @@ -17,6 +20,9 @@ class HeadphoneState private constructor(val state: States) : SimpleEvent(Event. enum class States { Connected, Disconnected, + Play, + Pause, + Stop, PlayPause, Next, Prev diff --git a/app/src/main/java/com/nynja/mobile/communicator/data/sdk/BaseSDKModule.kt b/app/src/main/java/com/nynja/mobile/communicator/data/sdk/BaseSDKModule.kt index 7225e28441..14df473af0 100644 --- a/app/src/main/java/com/nynja/mobile/communicator/data/sdk/BaseSDKModule.kt +++ b/app/src/main/java/com/nynja/mobile/communicator/data/sdk/BaseSDKModule.kt @@ -3,6 +3,7 @@ package com.nynja.mobile.communicator.data.sdk import android.content.Context import android.os.Handler import android.os.Looper +import com.nynja.mobile.communicator.data.models.events.local.LocalEvent import com.nynja.sdk.NynjaCommunicator /** @@ -20,4 +21,8 @@ abstract class BaseSDKModule(protected val context: Context, protected var mobil } open fun initSdkSubModules() {} + + open fun onMediaButtonsLocalEvent(context: Context, event: LocalEvent): Boolean { + return false; + } } diff --git a/app/src/main/java/com/nynja/mobile/communicator/data/sdk/calls/ConferenceSDKModule.java b/app/src/main/java/com/nynja/mobile/communicator/data/sdk/calls/ConferenceSDKModule.java index 791a9ce7c1..ad8ff305ed 100644 --- a/app/src/main/java/com/nynja/mobile/communicator/data/sdk/calls/ConferenceSDKModule.java +++ b/app/src/main/java/com/nynja/mobile/communicator/data/sdk/calls/ConferenceSDKModule.java @@ -25,11 +25,14 @@ import com.nynja.mobile.communicator.NynjaApp; import com.nynja.mobile.communicator.R; import com.nynja.mobile.communicator.data.ProfileSyncManager; import com.nynja.mobile.communicator.data.audio.NynjaSoundManager; +import com.nynja.mobile.communicator.data.audio.headphones.data.HeadphoneState; import com.nynja.mobile.communicator.data.conference.ActiveCallBase; import com.nynja.mobile.communicator.data.conference.ConferenceVideoModule; import com.nynja.mobile.communicator.data.db.DbHelper; import com.nynja.mobile.communicator.data.models.SettingNotifications; import com.nynja.mobile.communicator.data.models.StateDevice; +import com.nynja.mobile.communicator.data.models.events.local.Event; +import com.nynja.mobile.communicator.data.models.events.local.LocalEvent; import com.nynja.mobile.communicator.data.models.mqtt.Conference; import com.nynja.mobile.communicator.data.models.nynjamodels.ContactModel; import com.nynja.mobile.communicator.data.models.nynjamodels.MemberModel; @@ -130,6 +133,7 @@ public class ConferenceSDKModule extends BaseSDKModule { private StateDevice mStateDevice; private NynjaSoundManager mNynjaSoundManager; private AppRTCAudioManager mConferenceAudioManager; + private AudioManager mAudioManager; private BroadcastReceiver incomingCallReceiver; private ProfileSyncManager mProfileSyncManager; @@ -446,8 +450,8 @@ public class ConferenceSDKModule extends BaseSDKModule { new Handler(Looper.getMainLooper()).post(() -> { try { if (hasCreatedActiveCall() && mActiveConference.mConference != null) { - sendDeclineConference(mActiveConference.mConference); mActiveConference.mConference.setListener(null); + sendDeclineConference(mActiveConference.mConference); } } catch (Exception e) { Timber.e("declineConference: exception on reject Conference "); @@ -581,6 +585,16 @@ public class ConferenceSDKModule extends BaseSDKModule { setAudioRoute(routeType, true); } + public void setAudioRoute(ActiveCallBase.AudioRouteType routeType, boolean updateWith, boolean delayed) { + if (delayed) { + mHandler.postDelayed(() -> { + setAudioRoute(routeType, true); + },Consts.DELAY_500); + } else { + setAudioRoute(routeType, true); + } + } + public void setAudioRoute(ActiveCallBase.AudioRouteType routeType, boolean updateWith) { if (mConferenceAudioManager == null) { mConferenceAudioManager = AppRTCAudioManager.create(getContext()); @@ -642,6 +656,7 @@ public class ConferenceSDKModule extends BaseSDKModule { //mCallHistoryManager = mConferenceCommunicator.getCallHistoryManager(); //addCallHistoryListener(); //////////////////////////////////////////////////////////////////////////////////////////////////////// + mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); mConferenceAudioManager = AppRTCAudioManager.create(getContext()); //ConferenceVideoModule.getInstance(); @@ -2679,16 +2694,21 @@ public class ConferenceSDKModule extends BaseSDKModule { mConferenceDetails.mCallId = mActiveConference.mConference.callId(); mActiveConference.mConference.start(); } else if (mActiveConference.mConference != null) { - mActiveConference.isCallInProgress = true; - mActiveConference.isRinging = false; final String callId = mActiveConference.mConference.callId(); - new Handler(Looper.getMainLooper()).post(() -> { - mCallManager.answerCallFromDialer(callId); - }); + answerCall(callId); } } } + public void answerCall(final String callId) { + mActiveConference.isCallInProgress = true; + mActiveConference.isRinging = false; + new Handler(Looper.getMainLooper()).post(() -> { + mCallManager.answerCallFromDialer(callId); + }); + + } + private boolean tryUpdateConferenceCallName(ActiveConferenceCall activeConference, String conferenceName) { if (!hasCreatedActiveCall()) return false; @@ -3456,9 +3476,15 @@ public class ConferenceSDKModule extends BaseSDKModule { } public void endActiveCall() { - if (!mActiveConference.isCallInProgress() && mActiveConference.mConference != null + if (mActiveConference != null && mActiveConference.mConference != null && !mActiveConference.mConference.isOutgoing()) { - declineConference(); + if (mActiveConference.isCallRinging()) { + declineConference(); + } else if (!mActiveConference.isCallInProgress()) { + declineConference(); + } else { + hangUp(); + } } else { hangUp(); } @@ -3700,6 +3726,72 @@ public class ConferenceSDKModule extends BaseSDKModule { } } + private void switchToBTDevice() { + setSpeakerState(false, true, false); + setAudioRoute(ActiveCallBase.AudioRouteType.BLUETOOTH, true, true); + } + + @Override + public boolean onMediaButtonsLocalEvent(Context context, LocalEvent event) { + if (event instanceof HeadphoneState) { + HeadphoneState hs = (HeadphoneState)event; + switch (hs.getState()) { + case Connected: + switchToBTDevice(); + break; + case Disconnected: + setAudioRoute(ActiveCallBase.AudioRouteType.EARPIECE, true, true); + break; + case Play: + case Pause: + case Stop: + case PlayPause: + if (isCallRinging()) { + String callId = getActiveConference().mConference.callId(); + Intent intent = new Intent(context, MainActivity.class); +// if (hs.getState() == HeadphoneState.States.Pause || hs.getState() == HeadphoneState.States.Stop) { +// intent.setAction(MainActivity.INTENT_NOTIFICATION_CALL_REJECT_ID); +// } else { + intent.setAction(MainActivity.INTENT_NOTIFICATION_CALL_ANSWER_ID); +// } + intent.putExtra(MainActivity.INTENT_NOTIFICATION_CALL_ID, callId); + intent.putExtra(MainActivity.INTENT_CALL_NOTIFICATION_ID, ActiveConferenceCall.ANDROID_10_PUSH_CALL_NTFN_ID); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + answerCall(callId); + mNotificationHelper.clearCallPush(ActiveConferenceCall.ANDROID_10_PUSH_CALL_NTFN_ID); +// .setAudioRoute(ActiveCallBase.AudioRouteType.BLUETOOTH, true, true); + } else { + context.startActivity(intent); + //getContext().startActivity(intent); + } + switchToBTDevice(); + } else if (hasCallInProgress()) { + hangUp(); + } + break; + case Next: + int currentVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); + int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + float percent = 0.3f; + currentVolume += (int) (currentVolume*percent); + int seventyVolume = (currentVolume > maxVolume) ? maxVolume : currentVolume; + mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, seventyVolume, 0); + break; + case Prev: + currentVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); + percent = 0.3f; + currentVolume -= (int) (currentVolume*percent); + seventyVolume = (currentVolume < 0) ? 0 : currentVolume; + mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, seventyVolume, 0); + break; + default: + break; + } + return true; + } + return false; + } + private class AsyncJoinToConferenceByLink extends AsyncTask { @Override protected Void doInBackground(String... strings) { diff --git a/app/src/main/java/com/nynja/mobile/communicator/mvp/presenters/IncomeConferencePresenter.java b/app/src/main/java/com/nynja/mobile/communicator/mvp/presenters/IncomeConferencePresenter.java index f84654797c..19ccc3b75f 100644 --- a/app/src/main/java/com/nynja/mobile/communicator/mvp/presenters/IncomeConferencePresenter.java +++ b/app/src/main/java/com/nynja/mobile/communicator/mvp/presenters/IncomeConferencePresenter.java @@ -2,6 +2,8 @@ package com.nynja.mobile.communicator.mvp.presenters; import com.arellomobile.mvp.InjectViewState; import com.nynja.mobile.communicator.data.audio.NynjaSoundManager; +import com.nynja.mobile.communicator.data.audio.headphones.HeadphoneMonitoringService; +import com.nynja.mobile.communicator.data.audio.headphones.data.HeadphoneState; import com.nynja.mobile.communicator.data.sdk.calls.ActiveConferenceCall; import com.nynja.mobile.communicator.data.sdk.ConferenceSDKPresenter; import com.nynja.mobile.communicator.data.models.nynjamodels.ContactModel; @@ -22,6 +24,11 @@ public class IncomeConferencePresenter extends ConferenceSDKPresenter