From 8ca8994cf34feb705ad2c991dcc438834b3e9263 Mon Sep 17 00:00:00 2001 From: VitaliyDovbnya Date: Wed, 4 Sep 2024 17:22:12 +0200 Subject: [PATCH] 4.2.0-videochat-java --- sample-videochat-java/app/build.gradle | 23 ++--- sample-videochat-java/app/proguard-rules.pro | 5 +- .../app/src/main/AndroidManifest.xml | 13 ++- .../java/activities/CallActivity.java | 70 ++++++++++++-- .../java/activities/PermissionsActivity.java | 1 + .../java/activities/SplashActivity.java | 34 ++++++- .../java/adapters/AudioCallAdapter.java | 85 +++++++++++++++++ .../adapters/OpponentsFromCallAdapter.java | 8 +- .../java/adapters/ReconnectingUserModel.java | 29 ++++++ .../videochat/java/adapters/UsersAdapter.java | 3 +- .../fragments/AudioConversationFragment.java | 91 +++++++++++++++++- .../ConversationFragmentCallback.java | 2 + .../fragments/VideoConversationFragment.java | 91 ++++++++++++------ .../videochat/java/services/CallService.java | 93 +++++++++++++++---- .../services/fcm/PushListenerService.java | 2 +- .../java/utils/PermissionsChecker.java | 4 +- .../java/utils/ReconnectionState.java | 8 ++ .../videochat/java/utils/SettingsManager.java | 4 +- .../src/main/res/layout/audio_call_item.xml | 46 +++++++++ .../layout/fragment_audio_conversation.xml | 11 +++ .../layout/fragment_video_conversation.xml | 32 +++++++ .../layout/list_item_opponent_from_call.xml | 1 + .../app/src/main/res/values/strings.xml | 6 +- sample-videochat-java/build.gradle | 4 +- sample-videochat-java/gradle.properties | 5 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- 26 files changed, 581 insertions(+), 92 deletions(-) create mode 100644 sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/adapters/AudioCallAdapter.java create mode 100644 sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/adapters/ReconnectingUserModel.java create mode 100644 sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/utils/ReconnectionState.java create mode 100644 sample-videochat-java/app/src/main/res/layout/audio_call_item.xml diff --git a/sample-videochat-java/app/build.gradle b/sample-videochat-java/app/build.gradle index 61be5accc..1e4465e3b 100755 --- a/sample-videochat-java/app/build.gradle +++ b/sample-videochat-java/app/build.gradle @@ -18,28 +18,24 @@ repositories { android { def versionQACode = 1 - compileSdkVersion 31 - buildToolsVersion "31.0.0" - flavorDimensions "default" - defaultConfig { applicationId "com.quickblox.sample.videochat.java" minSdkVersion 21 - targetSdkVersion 31 - versionCode 410010 - versionName '4.1.1' + targetSdkVersion 34 + compileSdk 34 + versionCode 420000 + versionName '4.2.0' multiDexEnabled true } + flavorDimensions += "dimension" productFlavors { dev { - dimension "default" buildConfigField('boolean', "IS_QA", "false") buildConfigField("int", "VERSION_QA_CODE", versionQACode.toString()) } qa { - dimension "default" buildConfigField("boolean", "IS_QA", "true") buildConfigField("int", "VERSION_QA_CODE", versionQACode.toString()) } @@ -80,16 +76,17 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + namespace 'com.quickblox.sample.videochat.java' } dependencies { - implementation 'com.quickblox:quickblox-android-sdk-videochat-webrtc:3.10.1' - implementation 'com.quickblox:quickblox-android-sdk-messages:3.10.1' + implementation 'com.quickblox:quickblox-android-sdk-videochat-webrtc:4.1.3' + implementation 'com.quickblox:quickblox-android-sdk-messages:4.1.3' - implementation 'com.google.firebase:firebase-core:21.1.0' + implementation 'com.google.firebase:firebase-core:21.1.1' implementation 'com.navercorp.pulltorefresh:library:3.2.3@aar' - implementation 'com.google.android.material:material:1.6.1' + implementation 'com.google.android.material:material:1.12.0' implementation 'com.github.johnkil.android-robototextview:robototextview:4.0.0' } diff --git a/sample-videochat-java/app/proguard-rules.pro b/sample-videochat-java/app/proguard-rules.pro index 2f1f26c83..6967ecf3e 100644 --- a/sample-videochat-java/app/proguard-rules.pro +++ b/sample-videochat-java/app/proguard-rules.pro @@ -44,4 +44,7 @@ -keep class com.google.android.gms.** { *; } #json --keep class org.json.** { *; } \ No newline at end of file +-keep class org.json.** { *; } +-keep class com.google.gson.reflect.TypeToken +-keep class * extends com.google.gson.reflect.TypeToken +-keep public class * implements java.lang.reflect.Type \ No newline at end of file diff --git a/sample-videochat-java/app/src/main/AndroidManifest.xml b/sample-videochat-java/app/src/main/AndroidManifest.xml index 9e74c6b56..acfc5676b 100755 --- a/sample-videochat-java/app/src/main/AndroidManifest.xml +++ b/sample-videochat-java/app/src/main/AndroidManifest.xml @@ -1,10 +1,10 @@ + xmlns:tools="http://schemas.android.com/tools"> + @@ -14,6 +14,10 @@ + + + + @@ -70,7 +74,8 @@ + android:exported="false" + android:foregroundServiceType="camera|microphone|mediaProjection" /> + android:value="FCM" /> currentCallStateCallbackList = new ArrayList<>(); private final UsersDbManager dbManager = UsersDbManager.getInstance(); @@ -97,7 +100,6 @@ public static void start(Context context, boolean isIncomingCall) { intent.putExtra(Consts.EXTRA_IS_INCOMING_CALL, isIncomingCall); SharedPrefsHelper.getInstance().save(Consts.EXTRA_IS_INCOMING_CALL, isIncomingCall); context.startActivity(intent); - CallService.start(context); } @Override @@ -107,6 +109,38 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); checker = new PermissionsChecker(this); connectionView = (LinearLayout) View.inflate(CallActivity.this, R.layout.connection_popup, null); + + boolean isVideoCall = isVideoSession(); + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU && isVideoCall && isNewSession()) { + requestSharingPermissions(this); + return; + } + + CallService.start(this); + } + + private boolean isNewSession() { + QBRTCSession currentSession = WebRtcSessionManager.getInstance(getApplicationContext()).getCurrentSession(); + if (currentSession == null) { + return false; + } + + BaseSession.QBRTCSessionState sessionState = currentSession.getState(); + if (sessionState == null) { + return false; + } + + return currentSession.getState().equals(QB_RTC_SESSION_NEW) || currentSession.getState().equals(QB_RTC_SESSION_PENDING); + } + + private boolean isVideoSession() { + QBRTCSession currentSession = WebRtcSessionManager.getInstance(getApplicationContext()).getCurrentSession(); + return currentSession != null && currentSession.getConferenceType().equals(QBRTCTypes.QBConferenceType.QB_CONFERENCE_TYPE_VIDEO); + } + + public void requestSharingPermissions(Activity context) { + MediaProjectionManager mMediaProjectionManager = (MediaProjectionManager) context.getSystemService(MEDIA_PROJECTION_SERVICE); + context.startActivityForResult(mMediaProjectionManager.createScreenCaptureIntent(), REQUEST_SHARING_MEDIA_PROJECTION); } @Override @@ -134,6 +168,7 @@ public void finish() { @Override public void onBackPressed() { // to prevent returning from Call Fragment + super.onBackPressed(); } private void allowOnLockScreen() { @@ -211,8 +246,17 @@ protected void onActivityResult(int requestCode, int resultCode, final Intent da startScreenSharing(data); Log.i(TAG, "Starting Screen Capture"); } + + if (requestCode == REQUEST_SHARING_MEDIA_PROJECTION) { + if (resultCode == Activity.RESULT_OK) { + CallService.start(this); + } else { + finish(); + } + } } + private void startScreenSharing(final Intent data) { Fragment fragmentByTag = getSupportFragmentManager().findFragmentByTag(ScreenShareFragment.class.getSimpleName()); if (!(fragmentByTag instanceof ScreenShareFragment)) { @@ -341,7 +385,9 @@ private void startIncomeCallTimer(long time) { private void stopIncomeCallTimer() { Log.d(TAG, "stopIncomeCallTimer"); - showIncomingCallWindowTaskHandler.removeCallbacks(showIncomingCallWindowTask); + if (showIncomingCallWindowTask != null) { + showIncomingCallWindowTaskHandler.removeCallbacks(showIncomingCallWindowTask); + } } private void addIncomeCallFragment() { @@ -448,6 +494,11 @@ public void onReceiveHangUpFromUser(final QBRTCSession session, final Integer us } } + @Override + public void onChangeReconnectionState(QBRTCSession qbrtcSession, Integer userId, QBRTCTypes.QBRTCReconnectionState qbrtcReconnectionState) { + // empty + } + @Override public void onCallAcceptByUser(QBRTCSession session, Integer userId, Map userInfo) { if (callService.isCurrentSession(session)) { @@ -646,6 +697,11 @@ public QBRTCVideoTrack getVideoTrack(Integer userId) { return callService.getVideoTrack(userId); } + @Override + public QBRTCTypes.QBRTCReconnectionState getState(Integer userId) { + return callService.getState(userId); + } + @Override public void onStopPreview() { callService.stopScreenSharing(); @@ -707,7 +763,7 @@ public void run() { } public interface OnChangeAudioDevice { - void audioDeviceChanged(AppRTCAudioManager.AudioDevice newAudioDevice); + void audioDeviceChanged(QBAudioManager.AudioDevice newAudioDevice); } public interface CurrentCallStateCallback { diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/activities/PermissionsActivity.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/activities/PermissionsActivity.java index 3c2197c5b..752d216be 100644 --- a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/activities/PermissionsActivity.java +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/activities/PermissionsActivity.java @@ -103,6 +103,7 @@ private void allPermissionsGranted() { @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSION_REQUEST_CODE && hasAllPermissionsGranted(grantResults)) { requiresCheck = true; allPermissionsGranted(); diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/activities/SplashActivity.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/activities/SplashActivity.java index 265f42c09..993589ee4 100644 --- a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/activities/SplashActivity.java +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/activities/SplashActivity.java @@ -1,5 +1,6 @@ package com.quickblox.sample.videochat.java.activities; +import android.Manifest; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; @@ -11,17 +12,22 @@ import android.util.Log; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.appcompat.app.AlertDialog; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + import com.quickblox.sample.videochat.java.R; import com.quickblox.sample.videochat.java.services.LoginService; import com.quickblox.sample.videochat.java.utils.PermissionsChecker; import com.quickblox.sample.videochat.java.utils.SharedPrefsHelper; import com.quickblox.sample.videochat.java.utils.ToastUtils; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; - public class SplashActivity extends BaseActivity { private static final String TAG = SplashActivity.class.getSimpleName(); + private static final int NOTIFICATION_PERMISSION_REQUEST_CODE = 1010; private static final int SPLASH_DELAY = 1500; @@ -32,6 +38,7 @@ public class SplashActivity extends BaseActivity { private SharedPrefsHelper sharedPrefsHelper; + @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -44,11 +51,32 @@ protected void onCreate(Bundle savedInstanceState) { getSupportActionBar().hide(); } + checkNotificationPermission(); + if (checkOverlayPermissions()) { runNextScreen(); } } + private void checkNotificationPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + boolean isNotificationPermissionDenied = ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_DENIED; + + if (isNotificationPermissionDenied) { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.POST_NOTIFICATIONS}, NOTIFICATION_PERMISSION_REQUEST_CODE); + } + } + } + + @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE && grantResults.length > 0 && grantResults[0] != 0) { + ToastUtils.longToast(getString(R.string.permission_unavailable, Manifest.permission.POST_NOTIFICATIONS)); + } + } + private void runNextScreen() { if (sharedPrefsHelper.hasUser()) { LoginService.start(SplashActivity.this, sharedPrefsHelper.getUser()); diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/adapters/AudioCallAdapter.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/adapters/AudioCallAdapter.java new file mode 100644 index 000000000..5e5de3927 --- /dev/null +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/adapters/AudioCallAdapter.java @@ -0,0 +1,85 @@ +package com.quickblox.sample.videochat.java.adapters; + +import android.content.Context; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.quickblox.sample.videochat.java.R; +import com.quickblox.users.model.QBUser; + +import java.util.List; + +public class AudioCallAdapter extends RecyclerView.Adapter { + private List usersList; + private final LayoutInflater inflater; + + public AudioCallAdapter(Context context, List usersList) { + this.usersList = usersList; + this.inflater = LayoutInflater.from(context); + } + + public void updateList(List usersList) { + this.usersList = usersList; + notifyDataSetChanged(); + } + + public ReconnectingUserModel getItemByUserId(int userId) { + for (ReconnectingUserModel item : usersList) { + if (item.getUser().getId().equals(userId)) { + return item; + } + } + return null; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = inflater.inflate(R.layout.audio_call_item, parent, false); + return new ViewHolder(v); + } + + @Override + public void onBindViewHolder(AudioCallAdapter.ViewHolder holder, int position) { + QBUser user = usersList.get(position).getUser(); + String name; + if (TextUtils.isEmpty(user.getFullName())) { + name = user.getLogin(); + } else { + name = user.getFullName(); + } + holder.nameView.setText(name); + + if(!TextUtils.isEmpty(usersList.get(position).getReconnectingState())){ + holder.statusView.setText(usersList.get(position).getReconnectingState()); + } + } + + @Override + public int getItemCount() { + return usersList.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + private final TextView nameView; + private final TextView statusView; + + public ViewHolder(View itemView) { + super(itemView); + nameView = (TextView) itemView.findViewById(R.id.name); + statusView = (TextView) itemView.findViewById(R.id.status); + } + + public void setStatus(String status) { + statusView.setText(status); + } + + public void setName(String name) { + nameView.setText(name); + } + } +} \ No newline at end of file diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/adapters/OpponentsFromCallAdapter.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/adapters/OpponentsFromCallAdapter.java index b837a3cfd..8abc12020 100755 --- a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/adapters/OpponentsFromCallAdapter.java +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/adapters/OpponentsFromCallAdapter.java @@ -161,13 +161,19 @@ public int getUserId() { public ProgressBar getProgressBar() { return progressBar; } + public void showProgressBar() { + progressBar.setVisibility(View.VISIBLE); + } + + public void hideProgressBar() { + progressBar.setVisibility(View.GONE); + } public QBRTCSurfaceView getOpponentView() { return opponentView; } public void showOpponentView(boolean show) { - Log.d("UsersAdapter", "show? " + show); opponentView.setVisibility(show ? View.VISIBLE : View.GONE); } diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/adapters/ReconnectingUserModel.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/adapters/ReconnectingUserModel.java new file mode 100644 index 000000000..e387718f3 --- /dev/null +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/adapters/ReconnectingUserModel.java @@ -0,0 +1,29 @@ +package com.quickblox.sample.videochat.java.adapters; + +import com.quickblox.users.model.QBUser; + +public class ReconnectingUserModel { + private QBUser user; + private String reconnectingState; + + public ReconnectingUserModel(QBUser user, String reconnectingState) { + this.user = user; + this.reconnectingState = reconnectingState; + } + + public QBUser getUser() { + return user; + } + + public String getReconnectingState() { + return reconnectingState; + } + + public void setUser(QBUser user) { + this.user = user; + } + + public void setReconnectingState(String reconnectingState) { + this.reconnectingState = reconnectingState; + } +} diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/adapters/UsersAdapter.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/adapters/UsersAdapter.java index 500b87b36..1e2bd3cdb 100644 --- a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/adapters/UsersAdapter.java +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/adapters/UsersAdapter.java @@ -52,7 +52,8 @@ public void onBindViewHolder(@NonNull UsersAdapter.ViewHolder holder, int positi holder.opponentName.setText(getOpponentNameFrom(user)); if (selectedUsers.contains(user)) { holder.rootLayout.setBackgroundResource(R.color.background_color_selected_user_item); - holder.opponentIcon.setBackgroundDrawable(UiUtils.getColoredCircleDrawable(context.getResources().getColor(R.color.icon_background_color_selected_user))); + holder.opponentIcon.setBackgroundDrawable( + UiUtils.getColoredCircleDrawable(context.getResources().getColor(R.color.icon_background_color_selected_user))); holder.opponentIcon.setImageResource(R.drawable.ic_checkmark); } else { holder.rootLayout.setBackgroundResource(R.color.background_color_normal_user_item); diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/fragments/AudioConversationFragment.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/fragments/AudioConversationFragment.java index 2a64291cb..a3c404dcc 100644 --- a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/fragments/AudioConversationFragment.java +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/fragments/AudioConversationFragment.java @@ -8,19 +8,27 @@ import android.widget.ToggleButton; import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import com.quickblox.sample.videochat.java.R; import com.quickblox.sample.videochat.java.activities.CallActivity; +import com.quickblox.sample.videochat.java.adapters.AudioCallAdapter; +import com.quickblox.sample.videochat.java.adapters.ReconnectingUserModel; import com.quickblox.sample.videochat.java.utils.CollectionsUtils; import com.quickblox.sample.videochat.java.utils.SharedPrefsHelper; import com.quickblox.sample.videochat.java.utils.UiUtils; import com.quickblox.users.model.QBUser; -import com.quickblox.videochat.webrtc.AppRTCAudioManager; +import com.quickblox.videochat.webrtc.QBRTCSession; +import com.quickblox.videochat.webrtc.QBRTCTypes; +import com.quickblox.videochat.webrtc.audio.QBAudioManager; +import com.quickblox.videochat.webrtc.callbacks.QBRTCSessionEventsCallback; import java.util.ArrayList; +import java.util.Map; -public class AudioConversationFragment extends BaseConversationFragment implements CallActivity.OnChangeAudioDevice { +public class AudioConversationFragment extends BaseConversationFragment implements CallActivity.OnChangeAudioDevice, QBRTCSessionEventsCallback { private static final String TAG = AudioConversationFragment.class.getSimpleName(); public static final String SPEAKER_ENABLED = "is_speaker_enabled"; @@ -29,15 +37,23 @@ public class AudioConversationFragment extends BaseConversationFragment implemen private TextView alsoOnCallText; private TextView firstOpponentNameTextView; private TextView otherOpponentsTextView; + private AudioCallAdapter adapter; @Override public void onStart() { super.onStart(); if (conversationFragmentCallback != null) { conversationFragmentCallback.addOnChangeAudioDeviceListener(this); + conversationFragmentCallback.addSessionEventsListener(this); } } + @Override + public void onPause() { + super.onPause(); + conversationFragmentCallback.removeSessionEventsListener(this); + } + @Override protected void configureOutgoingScreen() { outgoingOpponentsRelativeLayout.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.white)); @@ -101,6 +117,29 @@ protected void initViews(View view) { if (conversationFragmentCallback != null && conversationFragmentCallback.isCallState()) { onCallStarted(); } + RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.rvUsers); + ArrayList users = new ArrayList<>(); + for (QBUser item : opponents) { + QBRTCTypes.QBRTCReconnectionState state = conversationFragmentCallback.getState(item.getId()); + if (state != null) { + switch (state) { + case QB_RTC_RECONNECTION_STATE_RECONNECTING: + users.add(new ReconnectingUserModel(item, "Reconnecting")); + break; + case QB_RTC_RECONNECTION_STATE_RECONNECTED: + users.add(new ReconnectingUserModel(item, "Reconnected")); + break; + case QB_RTC_RECONNECTION_STATE_FAILED: + users.add(new ReconnectingUserModel(item, "Reconnection failed")); + break; + } + } else { + users.add(new ReconnectingUserModel(item, "")); + } + } + adapter = new AudioCallAdapter(getContext(), users); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + recyclerView.setAdapter(adapter); } private void setVisibilityAlsoOnCallTextView() { @@ -169,7 +208,51 @@ public void onCallTimeUpdate(String time) { } @Override - public void audioDeviceChanged(AppRTCAudioManager.AudioDevice newAudioDevice) { - audioSwitchToggleButton.setChecked(newAudioDevice != AppRTCAudioManager.AudioDevice.SPEAKER_PHONE); + public void audioDeviceChanged(QBAudioManager.AudioDevice newAudioDevice) { + audioSwitchToggleButton.setChecked(newAudioDevice != QBAudioManager.AudioDevice.SPEAKER_PHONE); + } + + @Override + public void onUserNotAnswer(QBRTCSession qbrtcSession, Integer integer) { + + } + + @Override + public void onCallRejectByUser(QBRTCSession qbrtcSession, Integer integer, Map map) { + + } + + @Override + public void onCallAcceptByUser(QBRTCSession qbrtcSession, Integer integer, Map map) { + + } + + @Override + public void onReceiveHangUpFromUser(QBRTCSession qbrtcSession, Integer integer, Map map) { + + } + + @Override + public void onChangeReconnectionState(QBRTCSession qbrtcSession, Integer integer, QBRTCTypes.QBRTCReconnectionState qbrtcReconnectionState) { + ReconnectingUserModel user = adapter.getItemByUserId(integer); + if (user != null) { + switch (qbrtcReconnectionState) { + case QB_RTC_RECONNECTION_STATE_RECONNECTING: + user.setReconnectingState("Reconnecting"); + break; + case QB_RTC_RECONNECTION_STATE_RECONNECTED: + user.setReconnectingState("Reconnected"); + break; + case QB_RTC_RECONNECTION_STATE_FAILED: + user.setReconnectingState("Reconnection failed"); + break; + } + adapter.notifyDataSetChanged(); + } + } + + @Override + public void onSessionClosed(QBRTCSession qbrtcSession) { + } } \ No newline at end of file diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/fragments/ConversationFragmentCallback.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/fragments/ConversationFragmentCallback.java index 3cc458d55..70fea4ee3 100644 --- a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/fragments/ConversationFragmentCallback.java +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/fragments/ConversationFragmentCallback.java @@ -1,6 +1,7 @@ package com.quickblox.sample.videochat.java.fragments; import com.quickblox.sample.videochat.java.activities.CallActivity; +import com.quickblox.sample.videochat.java.utils.ReconnectionState; import com.quickblox.videochat.webrtc.BaseSession; import com.quickblox.videochat.webrtc.QBRTCSession; import com.quickblox.videochat.webrtc.QBRTCTypes; @@ -78,4 +79,5 @@ public interface ConversationFragmentCallback { HashMap getVideoTrackMap(); QBRTCVideoTrack getVideoTrack(Integer userId); + QBRTCTypes.QBRTCReconnectionState getState(Integer userId); } \ No newline at end of file diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/fragments/VideoConversationFragment.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/fragments/VideoConversationFragment.java index 1b0d382c3..cbf137e1a 100755 --- a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/fragments/VideoConversationFragment.java +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/fragments/VideoConversationFragment.java @@ -57,6 +57,8 @@ import java.util.List; import java.util.Map; +import static com.quickblox.videochat.webrtc.QBRTCTypes.QBRTCReconnectionState.QB_RTC_RECONNECTION_STATE_RECONNECTING; + /** * QuickBlox team */ @@ -79,6 +81,7 @@ public class VideoConversationFragment extends BaseConversationFragment implemen private RecyclerView recyclerView; private QBRTCSurfaceView localVideoView; private FrameLayout flLocalVideoView; + private LinearLayout reconnectionProgress; private QBRTCSurfaceView remoteFullScreenVideoView; private SparseArray opponentViewHolders; @@ -88,7 +91,6 @@ public class VideoConversationFragment extends BaseConversationFragment implemen private boolean isPeerToPeerCall; private QBRTCVideoTrack localVideoTrack; private Menu optionsMenu; - private boolean isRemoteShown; private int userIdFullScreen; private boolean connectionEstablished; private boolean allCallbacksInit; @@ -230,10 +232,10 @@ protected void initViews(View view) { super.initViews(view); Log.i(TAG, "initViews"); opponentViewHolders = new SparseArray<>(opponents.size()); - isRemoteShown = false; localVideoView = (QBRTCSurfaceView) view.findViewById(R.id.local_video_view); flLocalVideoView = (FrameLayout) view.findViewById(R.id.fl_local_video_view); + reconnectionProgress = view.findViewById(R.id.reconnection_progress); initCorrectSizeForLocalView(); localVideoView.setZOrderMediaOverlay(true); @@ -334,6 +336,7 @@ private void setGrid(int columnsCount) { float itemMargin = getResources().getDimension(R.dimen.grid_item_divider); int cellSizeWidth = defineSize(gridWidth, columnsCount, itemMargin); Log.i(TAG, "onGlobalLayout : cellSize=" + cellSizeWidth); + opponents.remove(0); opponentsAdapter = new OpponentsFromCallAdapter(getContext(), this, opponents, cellSizeWidth, (int) getResources().getDimension(R.dimen.item_height)); opponentsAdapter.setAdapterListener(this); @@ -463,11 +466,10 @@ public void onRemoteVideoTrackReceive(QBRTCSession session, final QBRTCVideoTrac if (userID == null) { return; } - Log.d(TAG, "onRemoteVideoTrackReceive for opponent= " + userID); if (isPeerToPeerCall) { setDuringCallActionBar(); if (remoteFullScreenVideoView != null) { - fillVideoView(remoteFullScreenVideoView, videoTrack, true); + fillVideoView(userID,remoteFullScreenVideoView, videoTrack); updateVideoView(remoteFullScreenVideoView); } } else { @@ -495,7 +497,8 @@ public void onItemClick(int position) { OpponentsFromCallAdapter.ViewHolder holder = findHolder(userId); boolean isNotExistVideoTrack = videoTrackMap != null && !videoTrackMap.containsKey(userId); - boolean isConnectionStateClosed = connectionState.ordinal() == QBRTCTypes.QBRTCConnectionState.QB_RTC_CONNECTION_CLOSED.ordinal(); + boolean isConnectionStateClosed = connectionState != null && + connectionState.ordinal() == QBRTCTypes.QBRTCConnectionState.QB_RTC_CONNECTION_CLOSED.ordinal(); if (isNotExistVideoTrack || isConnectionStateClosed || holder == null) { return; @@ -534,7 +537,7 @@ private void swapUsersFullscreenToPreview(OpponentsFromCallAdapter.ViewHolder ho } if (mainVideoTrack != null) { - fillVideoView(0, remoteVideoView, mainVideoTrack); + fillVideoView( remoteVideoView, mainVideoTrack,true); } else { holder.getOpponentView().setBackgroundColor(Color.BLACK); remoteFullScreenVideoView.setBackgroundColor(Color.TRANSPARENT); @@ -542,30 +545,28 @@ private void swapUsersFullscreenToPreview(OpponentsFromCallAdapter.ViewHolder ho } private void setRemoteViewMultiCall(int userId, QBRTCVideoTrack videoTrack) { - Log.d(TAG, "setRemoteViewMultiCall fillVideoView"); final OpponentsFromCallAdapter.ViewHolder itemHolder = getViewHolderForOpponent(userId); - if (itemHolder == null) { - Log.d(TAG, "itemHolder == null - true"); - return; - } - final QBRTCSurfaceView remoteVideoView = itemHolder.getOpponentView(); - if (remoteVideoView != null) { + + if (itemHolder == null || userIdFullScreen == userId) { + if (remoteFullScreenVideoView != null) { + fillVideoView(userId, remoteFullScreenVideoView, videoTrack); + updateVideoView(remoteFullScreenVideoView); + QBRTCTypes.QBRTCReconnectionState state = conversationFragmentCallback.getState(userId); + if(state == QB_RTC_RECONNECTION_STATE_RECONNECTING){ + reconnectionProgress.setVisibility(View.VISIBLE); + } + } + setDuringCallActionBar(); + } else { + final QBRTCSurfaceView remoteVideoView = itemHolder.getOpponentView(); remoteVideoView.setZOrderMediaOverlay(true); updateVideoView(remoteVideoView); - Log.d(TAG, "onRemoteVideoTrackReceive fillVideoView"); - if (isRemoteShown) { - Log.d(TAG, "onRemoteVideoTrackReceive User = " + userId); - fillVideoView(remoteVideoView, videoTrack, true); - setRecyclerViewVisibleState(); - } else { - isRemoteShown = true; - itemHolder.getOpponentView().release(); - opponentsAdapter.removeItem(itemHolder.getAdapterPosition()); - if (remoteFullScreenVideoView != null) { - fillVideoView(userId, remoteFullScreenVideoView, videoTrack); - updateVideoView(remoteFullScreenVideoView); - } - setDuringCallActionBar(); + fillVideoView(remoteVideoView, videoTrack, true); + setRecyclerViewVisibleState(); + QBRTCTypes.QBRTCReconnectionState state = conversationFragmentCallback.getState(userId); + if(state == QB_RTC_RECONNECTION_STATE_RECONNECTING){ + itemHolder.showProgressBar(); + itemHolder.setStatus(getString(R.string.reconnecting_status)); } } } @@ -640,9 +641,7 @@ private void updateVideoView(SurfaceViewRenderer videoView) { * @param userId set userId if it from fullscreen videoTrack */ private void fillVideoView(int userId, QBRTCSurfaceView videoView, QBRTCVideoTrack videoTrack) { - if (userId != 0) { userIdFullScreen = userId; - } fillVideoView(videoView, videoTrack, true); } @@ -683,7 +682,7 @@ private void setProgressBarForOpponentGone(int userId) { return; } - holder.getProgressBar().setVisibility(View.GONE); + holder.hideProgressBar(); } private void setBackgroundOpponentView(final Integer userId) { @@ -755,6 +754,38 @@ public void onReceiveHangUpFromUser(QBRTCSession session, Integer userId, Map future; private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); + private QBUser currentUser; + private final Map reconnections = new HashMap(); public static void start(Context context) { Intent intent = new Intent(context, CallService.class); @@ -114,6 +116,7 @@ public void onCreate() { initRTCClient(); initListeners(); initAudioManager(); + initCurrentUser(); ringtonePlayer = new RingtonePlayer(this, R.raw.beep); super.onCreate(); } @@ -121,10 +124,34 @@ public void onCreate() { @Override public int onStartCommand(Intent intent, int flags, int startId) { Notification notification = buildNotification(); - startForeground(SERVICE_ID, notification); + + try { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU) { + int foregroundServiceType = getServiceType(isVideoSession(currentSession)); + startForeground(SERVICE_ID, notification, foregroundServiceType); + } else { + startForeground(SERVICE_ID, notification); + } + } catch (RuntimeException exception) { + // handle exception. + } + return super.onStartCommand(intent, flags, startId); } + @RequiresApi(api = Build.VERSION_CODES.R) + private int getServiceType(boolean isVideoSession) { + if (isVideoSession) { + return ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA | ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION; + } else { + return ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; + } + } + + private boolean isVideoSession(QBRTCSession session) { + return session != null && session.getConferenceType().equals(QBRTCTypes.QBConferenceType.QB_CONFERENCE_TYPE_VIDEO); + } + @Override public void onDestroy() { super.onDestroy(); @@ -150,6 +177,13 @@ public IBinder onBind(Intent intent) { return callServiceBinder; } + private void initCurrentUser() { + currentUser = QBChatService.getInstance().getUser(); + if (currentUser == null) { + currentUser = SharedPrefsHelper.getInstance().getUser(); + } + } + private Notification buildNotification() { Intent notifyIntent = new Intent(this, CallActivity.class); notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); @@ -232,6 +266,14 @@ public void stopRingtone() { ringtonePlayer.stop(); } + public QBRTCTypes.QBRTCReconnectionState getState(Integer userId) { + return reconnections.get(userId); + } + + public QBUser getCurrentUser() { + return currentUser; + } + private void initNetworkChecker() { networkConnectionChecker = new NetworkConnectionChecker(getApplication()); networkConnectionListener = new NetworkConnectionListener(); @@ -265,31 +307,31 @@ private void initListeners() { } public void initAudioManager() { - appRTCAudioManager = AppRTCAudioManager.create(this); + appRTCAudioManager = QBAudioManager.create(this); - appRTCAudioManager.setOnWiredHeadsetStateListener(new AppRTCAudioManager.OnWiredHeadsetStateListener() { + appRTCAudioManager.setOnWiredHeadsetStateListener(new QBAudioManager.OnWiredHeadsetStateListener() { @Override public void onWiredHeadsetStateChanged(boolean plugged, boolean hasMicrophone) { ToastUtils.shortToast("Headset " + (plugged ? "Plugged" : "Unplugged")); } }); - appRTCAudioManager.setBluetoothAudioDeviceStateListener(new AppRTCAudioManager.BluetoothAudioDeviceStateListener() { + appRTCAudioManager.setBluetoothAudioDeviceStateListener(new QBAudioManager.BluetoothAudioDeviceStateListener() { @Override public void onStateChanged(boolean connected) { ToastUtils.shortToast("Bluetooth " + (connected ? "Connected" : "Disconnected")); } }); - appRTCAudioManager.start(new AppRTCAudioManager.AudioManagerEvents() { + appRTCAudioManager.start(new QBAudioManager.AudioManagerEvents() { @Override - public void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice audioDevice, Set set) { + public void onAudioDeviceChanged(QBAudioManager.AudioDevice audioDevice, Set set) { ToastUtils.shortToast("Audio Device Switched to " + audioDevice); } }); if (currentSessionExist() && currentSession.getConferenceType() == QBRTCTypes.QBConferenceType.QB_CONFERENCE_TYPE_AUDIO) { - appRTCAudioManager.selectAudioDevice(AppRTCAudioManager.AudioDevice.EARPIECE); + appRTCAudioManager.selectAudioDevice(QBAudioManager.AudioDevice.EARPIECE); } } @@ -484,15 +526,15 @@ public boolean isCameraFront() { public void switchAudio() { Log.v(TAG, "onSwitchAudio(), SelectedAudioDevice() = " + appRTCAudioManager.getSelectedAudioDevice()); - if (appRTCAudioManager.getSelectedAudioDevice() != AppRTCAudioManager.AudioDevice.SPEAKER_PHONE) { - appRTCAudioManager.selectAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE); + if (appRTCAudioManager.getSelectedAudioDevice() != QBAudioManager.AudioDevice.SPEAKER_PHONE) { + appRTCAudioManager.selectAudioDevice(QBAudioManager.AudioDevice.SPEAKER_PHONE); } else { - if (appRTCAudioManager.getAudioDevices().contains(AppRTCAudioManager.AudioDevice.BLUETOOTH)) { - appRTCAudioManager.selectAudioDevice(AppRTCAudioManager.AudioDevice.BLUETOOTH); - } else if (appRTCAudioManager.getAudioDevices().contains(AppRTCAudioManager.AudioDevice.WIRED_HEADSET)) { - appRTCAudioManager.selectAudioDevice(AppRTCAudioManager.AudioDevice.WIRED_HEADSET); + if (appRTCAudioManager.getAudioDevices().contains(QBAudioManager.AudioDevice.BLUETOOTH)) { + appRTCAudioManager.selectAudioDevice(QBAudioManager.AudioDevice.BLUETOOTH); + } else if (appRTCAudioManager.getAudioDevices().contains(QBAudioManager.AudioDevice.WIRED_HEADSET)) { + appRTCAudioManager.selectAudioDevice(QBAudioManager.AudioDevice.WIRED_HEADSET); } else { - appRTCAudioManager.selectAudioDevice(AppRTCAudioManager.AudioDevice.EARPIECE); + appRTCAudioManager.selectAudioDevice(QBAudioManager.AudioDevice.EARPIECE); } } } @@ -639,6 +681,23 @@ public void onReceiveHangUpFromUser(QBRTCSession session, Integer userID, Map map) { stopRingtone(); diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/services/fcm/PushListenerService.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/services/fcm/PushListenerService.java index f1d658196..09dbf6e6f 100644 --- a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/services/fcm/PushListenerService.java +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/services/fcm/PushListenerService.java @@ -26,7 +26,7 @@ public void onMessageReceived(RemoteMessage remoteMessage) { } @Override - protected void sendPushMessage(Map data, String from, String message) { + public void sendPushMessage(Map data, String from, String message) { super.sendPushMessage(data, from, message); Log.v(TAG, "From: " + from); Log.v(TAG, "Message: " + message); diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/utils/PermissionsChecker.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/utils/PermissionsChecker.java index dc90e9125..7708b143c 100644 --- a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/utils/PermissionsChecker.java +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/utils/PermissionsChecker.java @@ -4,12 +4,12 @@ import android.content.pm.PackageManager; import android.text.TextUtils; +import androidx.core.content.ContextCompat; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -import androidx.core.content.ContextCompat; - public class PermissionsChecker { private final Context context; diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/utils/ReconnectionState.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/utils/ReconnectionState.java new file mode 100644 index 000000000..e587c84e7 --- /dev/null +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/utils/ReconnectionState.java @@ -0,0 +1,8 @@ +package com.quickblox.sample.videochat.java.utils; + +public enum ReconnectionState { + DEFAULT, + RECONNECTING, + RECONNECTED, + FAILED; +} \ No newline at end of file diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/utils/SettingsManager.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/utils/SettingsManager.java index b97a2fa74..3ce478dd6 100755 --- a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/utils/SettingsManager.java +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/utils/SettingsManager.java @@ -35,8 +35,8 @@ public static void applyRTCSettings() { int ANSWER_TIME_INTERVAL = 30; QBRTCConfig.setAnswerTimeInterval(ANSWER_TIME_INTERVAL); - int DISCONNECT_TIME_10_SECONDS = 10; - QBRTCConfig.setDisconnectTime(DISCONNECT_TIME_10_SECONDS); + int DISCONNECT_TIME_30_SECONDS = 30; + QBRTCConfig.setDialingTimeInterval(DISCONNECT_TIME_30_SECONDS); int DIALING_TIME_INTERVAL = 5; QBRTCConfig.setDialingTimeInterval(DIALING_TIME_INTERVAL); diff --git a/sample-videochat-java/app/src/main/res/layout/audio_call_item.xml b/sample-videochat-java/app/src/main/res/layout/audio_call_item.xml new file mode 100644 index 000000000..0a2debe47 --- /dev/null +++ b/sample-videochat-java/app/src/main/res/layout/audio_call_item.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/sample-videochat-java/app/src/main/res/layout/fragment_audio_conversation.xml b/sample-videochat-java/app/src/main/res/layout/fragment_audio_conversation.xml index 3bf5345b7..6777f9ba6 100644 --- a/sample-videochat-java/app/src/main/res/layout/fragment_audio_conversation.xml +++ b/sample-videochat-java/app/src/main/res/layout/fragment_audio_conversation.xml @@ -53,6 +53,17 @@ android:textColor="@color/text_color_other_inc_users" android:textSize="@dimen/other_inc_users_text_size" tools:text="@string/user_list_on_call" /> + + diff --git a/sample-videochat-java/app/src/main/res/layout/fragment_video_conversation.xml b/sample-videochat-java/app/src/main/res/layout/fragment_video_conversation.xml index 3c1efec9f..7fd299869 100755 --- a/sample-videochat-java/app/src/main/res/layout/fragment_video_conversation.xml +++ b/sample-videochat-java/app/src/main/res/layout/fragment_video_conversation.xml @@ -11,6 +11,38 @@ android:layout_height="wrap_content" android:layout_above="@+id/grid_opponents" /> + + + + + + + + + + + + + + diff --git a/sample-videochat-java/app/src/main/res/values/strings.xml b/sample-videochat-java/app/src/main/res/values/strings.xml index 5f117972f..00d51d5df 100755 --- a/sample-videochat-java/app/src/main/res/values/strings.xml +++ b/sample-videochat-java/app/src/main/res/values/strings.xml @@ -1,6 +1,5 @@ - Sample Video Chat Java @@ -117,6 +116,9 @@ Stop \"Failed switch camera\" You + User %1$d reconnecting + User %1$d reconnected + User %1$d reconnection failed Application version: @@ -129,5 +131,5 @@ Chat domain: App Info QA version: - + Reconnecting \ No newline at end of file diff --git a/sample-videochat-java/build.gradle b/sample-videochat-java/build.gradle index 370971703..f9e9f9fb0 100644 --- a/sample-videochat-java/build.gradle +++ b/sample-videochat-java/build.gradle @@ -5,8 +5,8 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.2.2' - classpath 'com.google.gms:google-services:4.3.13' + classpath 'com.android.tools.build:gradle:8.1.1' + classpath 'com.google.gms:google-services:4.4.2' } } diff --git a/sample-videochat-java/gradle.properties b/sample-videochat-java/gradle.properties index 3f56b1941..21715c8df 100644 --- a/sample-videochat-java/gradle.properties +++ b/sample-videochat-java/gradle.properties @@ -19,4 +19,7 @@ org.gradle.daemon=true org.gradle.parallel=true org.gradle.configureondemand=true android.useAndroidX=true -android.enableJetifier=true \ No newline at end of file +android.enableJetifier=true +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false \ No newline at end of file diff --git a/sample-videochat-java/gradle/wrapper/gradle-wrapper.properties b/sample-videochat-java/gradle/wrapper/gradle-wrapper.properties index 4d9ca1649..da1db5f04 100644 --- a/sample-videochat-java/gradle/wrapper/gradle-wrapper.properties +++ b/sample-videochat-java/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists