diff --git a/sample-conference-java/app/build.gradle b/sample-conference-java/app/build.gradle index 3da0f1fe4..92218ec4b 100644 --- a/sample-conference-java/app/build.gradle +++ b/sample-conference-java/app/build.gradle @@ -34,8 +34,8 @@ android { applicationId "com.quickblox.sample.videochat.conference.java" minSdkVersion 16 targetSdkVersion 29 - versionCode 433000 - versionName '4.3.3-java' + versionCode 440000 + versionName '4.4.0' multiDexEnabled true } diff --git a/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/App.java b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/App.java index 5573a8114..c783fda10 100644 --- a/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/App.java +++ b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/App.java @@ -6,7 +6,6 @@ import com.crashlytics.android.Crashlytics; import com.quickblox.auth.session.QBSettings; import com.quickblox.conference.ConferenceConfig; -import com.quickblox.core.ServiceZone; import com.quickblox.sample.videochat.conference.java.managers.ActivityLifecycle; import com.quickblox.sample.videochat.conference.java.managers.BackgroundListener; import com.quickblox.sample.videochat.conference.java.managers.ChatHelper; @@ -43,10 +42,7 @@ public class App extends Application { private static final String AUTH_KEY = ""; private static final String AUTH_SECRET = ""; private static final String ACCOUNT_KEY = ""; - - private static final String JANUS_SERVER_URL = ""; - - // TODO Firebase keys in - google-services.json + private static final String SERVER_URL = ""; public static final String USER_DEFAULT_PASSWORD = "quickblox"; @@ -145,8 +141,8 @@ private void initCredentials() { } private void initConferenceConfig() { - if (!TextUtils.isEmpty(JANUS_SERVER_URL)) { - ConferenceConfig.setUrl(JANUS_SERVER_URL); + if (!TextUtils.isEmpty(SERVER_URL)) { + ConferenceConfig.setUrl(SERVER_URL); } else { throw new AssertionError(getString(R.string.error_server_url_null)); } diff --git a/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/activities/BaseActivity.java b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/activities/BaseActivity.java index 8cf782369..baa674a6a 100644 --- a/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/activities/BaseActivity.java +++ b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/activities/BaseActivity.java @@ -11,6 +11,10 @@ import android.view.MenuItem; import android.view.View; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AppCompatActivity; + import com.google.android.material.snackbar.Snackbar; import com.quickblox.chat.QBChatService; import com.quickblox.core.QBEntityCallback; @@ -26,10 +30,6 @@ import com.quickblox.users.QBUsers; import com.quickblox.users.model.QBUser; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.appcompat.app.AppCompatActivity; - public abstract class BaseActivity extends AppCompatActivity { private static final String TAG = BaseActivity.class.getSimpleName(); @@ -131,6 +131,7 @@ protected void hideProgressDialog() { if (progressDialog != null && progressDialog.isShowing()) { try { progressDialog.dismiss(); + progressDialog = null; } catch (IllegalArgumentException e) { e.printStackTrace(); } diff --git a/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/activities/CallActivity.java b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/activities/CallActivity.java index 216c17f30..281128421 100644 --- a/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/activities/CallActivity.java +++ b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/activities/CallActivity.java @@ -11,26 +11,28 @@ import android.os.IBinder; import android.preference.PreferenceManager; import android.util.Log; +import android.view.View; import android.view.Window; +import android.widget.LinearLayout; + +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import com.quickblox.conference.ConferenceSession; import com.quickblox.conference.WsException; import com.quickblox.conference.WsHangUpException; import com.quickblox.conference.WsNoResponseException; import com.quickblox.conference.callbacks.ConferenceSessionCallbacks; -import com.quickblox.core.QBEntityCallback; -import com.quickblox.core.exception.QBResponseException; import com.quickblox.sample.videochat.conference.java.R; import com.quickblox.sample.videochat.conference.java.fragments.ConversationFragment; import com.quickblox.sample.videochat.conference.java.fragments.ConversationFragmentCallback; +import com.quickblox.sample.videochat.conference.java.fragments.ReconnectionCallback; import com.quickblox.sample.videochat.conference.java.fragments.ScreenShareFragment; import com.quickblox.sample.videochat.conference.java.managers.WebRtcSessionManager; import com.quickblox.sample.videochat.conference.java.services.CallService; import com.quickblox.sample.videochat.conference.java.utils.Consts; import com.quickblox.sample.videochat.conference.java.utils.SettingsUtils; import com.quickblox.sample.videochat.conference.java.utils.ToastUtils; -import com.quickblox.users.QBUsers; -import com.quickblox.users.model.QBUser; import com.quickblox.videochat.webrtc.BaseSession; import com.quickblox.videochat.webrtc.QBRTCScreenCapturer; import com.quickblox.videochat.webrtc.callbacks.QBRTCSessionStateCallback; @@ -41,11 +43,9 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; - -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; +import java.util.Set; public class CallActivity extends BaseActivity implements QBRTCSessionStateCallback, ConferenceSessionCallbacks, ConversationFragmentCallback, ScreenShareFragment.OnSharingEvents { @@ -65,6 +65,9 @@ public class CallActivity extends BaseActivity implements QBRTCSessionStateCallb private ArrayList currentCallStateCallbackList = new ArrayList<>(); private volatile boolean connectedToJanus; private CallService callService; + private final Set reconnectionCallbacks = new HashSet<>(); + private final ReconnectionListenerImpl reconnectionListener = new ReconnectionListenerImpl(TAG); + private LinearLayout reconnectingLayout; public static void start(Context context, String roomID, String roomTitle, String dialogID, List occupants, boolean listenerRole) { @@ -84,6 +87,16 @@ public static void start(Context context) { context.startActivity(intent); } + @Override + public void addReconnectionCallback(ReconnectionCallback reconnectionCallback) { + reconnectionCallbacks.add(reconnectionCallback); + } + + @Override + public void removeReconnectionCallback(ReconnectionCallback reconnectionCallback) { + reconnectionCallbacks.remove(reconnectionCallback); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -95,6 +108,8 @@ protected void onCreate(Bundle savedInstanceState) { PreferenceManager.setDefaultValues(this, R.xml.preferences_video, false); PreferenceManager.setDefaultValues(this, R.xml.preferences_audio, false); + reconnectingLayout = findViewById(R.id.llReconnecting); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window w = getWindow(); w.setStatusBarColor(ContextCompat.getColor(this, R.color.color_new_blue)); @@ -116,8 +131,12 @@ private void initScreen() { currentSession = sessionManager.getCurrentSession(); initListeners(currentSession); - if (callService.isSharingScreenState()) { - startScreenSharing(null); + if (callService != null && callService.isSharingScreenState()) { + if (callService.getReconnectionState() == CallService.ReconnectionState.COMPLETED) { + QBRTCScreenCapturer.requestPermissions(this); + } else { + startScreenSharing(null); + } } else { startConversationFragment(); } @@ -128,6 +147,27 @@ protected void onResume() { super.onResume(); settingsSharedPref = PreferenceManager.getDefaultSharedPreferences(this); bindCallService(); + if (callService != null) { + switch (callService.getReconnectionState()) { + case COMPLETED: + reconnectingLayout.setVisibility(View.GONE); + for (ReconnectionCallback reconnectionCallback : reconnectionCallbacks) { + reconnectionCallback.completed(); + } + break; + case IN_PROGRESS: + reconnectingLayout.setVisibility(View.VISIBLE); + for (ReconnectionCallback reconnectionCallback : reconnectionCallbacks) { + reconnectionCallback.inProgress(); + } + break; + case FAILED: + ToastUtils.shortToast(getApplicationContext(), R.string.reconnection_failed); + callService.leaveCurrentSession(); + finish(); + break; + } + } } @Override @@ -137,6 +177,8 @@ protected void onPause() { unbindService(callServiceConnection); callServiceConnection = null; } + + callService.unsubscribeReconnectionListener(reconnectionListener); removeListeners(); } @@ -161,6 +203,7 @@ private void bindCallService() { private void leaveCurrentSession() { callService.leaveCurrentSession(); + finish(); } private void initListeners(ConferenceSession session) { @@ -324,6 +367,9 @@ protected void onActivityResult(int requestCode, int resultCode, @Nullable Inten && resultCode == Activity.RESULT_OK && data != null) { startScreenSharing(data); Log.i(TAG, "Starting Screen Capture"); + } else if (requestCode == QBRTCScreenCapturer.REQUEST_MEDIA_PROJECTION && resultCode == Activity.RESULT_CANCELED) { + callService.stopScreenSharing(); + startConversationFragment(); } if (requestCode == REQUEST_CODE_OPEN_CONVERSATION_CHAT) { Log.d(TAG, "Returning back from ChatActivity"); @@ -331,16 +377,13 @@ protected void onActivityResult(int requestCode, int resultCode, @Nullable Inten } private void startScreenSharing(final Intent data) { - Fragment fragmentByTag = getSupportFragmentManager().findFragmentByTag(ScreenShareFragment.class.getSimpleName()); - if (!(fragmentByTag instanceof ScreenShareFragment)) { - ScreenShareFragment screenShareFragment = ScreenShareFragment.newInstance(); - getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, - screenShareFragment, ScreenShareFragment.class.getSimpleName()) - .commitAllowingStateLoss(); - - callService.setVideoEnabled(true); - callService.startScreenSharing(data); - } + ScreenShareFragment screenShareFragment = ScreenShareFragment.newInstance(); + getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, + screenShareFragment, ScreenShareFragment.class.getSimpleName()) + .commitAllowingStateLoss(); + + callService.setVideoEnabled(true); + callService.startScreenSharing(data); } @Override @@ -408,9 +451,6 @@ public void onError(WsException exception) { @Override public void onSessionClosed(final ConferenceSession session) { Log.d(TAG, "Session " + session.getSessionID() + " start stop session"); - if (session.equals(currentSession)) { - finish(); - } } private class CallServiceConnection implements ServiceConnection { @@ -425,27 +465,66 @@ public void onServiceConnected(ComponentName name, IBinder service) { callService = binder.getService(); if (callService.currentSessionExist()) { currentDialogID = callService.getDialogID(); - login(); + if (callService.getReconnectionState() != CallService.ReconnectionState.IN_PROGRESS){ + initScreen(); + } } else { //we have already currentSession == null, so it's no reason to do further initialization CallService.stop(CallActivity.this); finish(); } + callService.subscribeReconnectionListener(reconnectionListener); } + } - private void login() { - QBUser qbUser = getSharedPrefsHelper().getQbUser(); - QBUsers.signIn(qbUser).performAsync(new QBEntityCallback() { - @Override - public void onSuccess(QBUser qbUser, Bundle bundle) { - initScreen(); - } + private class ReconnectionListenerImpl implements CallService.ReconnectionListener { + private final String tag; - @Override - public void onError(QBResponseException e) { + ReconnectionListenerImpl(String tag) { + this.tag = tag; + } + + @Override + public void onChangedState(CallService.ReconnectionState reconnectionState) { + switch (reconnectionState) { + case COMPLETED: + reconnectingLayout.setVisibility(View.GONE); + for (ReconnectionCallback reconnectionCallback : reconnectionCallbacks) { + reconnectionCallback.completed(); + } + initScreen(); + callService.setReconnectionState(CallService.ReconnectionState.DEFAULT); + break; + case IN_PROGRESS: + reconnectingLayout.setVisibility(View.VISIBLE); + for (ReconnectionCallback reconnectionCallback : reconnectionCallbacks) { + reconnectionCallback.inProgress(); + } + break; + case FAILED: + ToastUtils.shortToast(getApplicationContext(), R.string.reconnection_failed); + callService.leaveCurrentSession(); finish(); - } - }); + break; + } + } + + @Override + public int hashCode() { + int hash = 3; + hash = 53 * hash + tag.hashCode(); + return hash; + } + + @Override + public boolean equals(Object obj) { + boolean equals; + if (obj instanceof ReconnectionListenerImpl) { + equals = TAG.equals(((ReconnectionListenerImpl) obj).tag); + } else { + equals = super.equals(obj); + } + return equals; } } } \ No newline at end of file diff --git a/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/activities/ChatActivity.java b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/activities/ChatActivity.java index 1b900964a..390807e89 100644 --- a/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/activities/ChatActivity.java +++ b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/activities/ChatActivity.java @@ -29,6 +29,13 @@ import android.widget.ProgressBar; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.PopupMenu; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import com.quickblox.chat.QBChatService; import com.quickblox.chat.QBMessageStatusesManager; import com.quickblox.chat.QBRestChatService; @@ -84,13 +91,6 @@ import java.util.Timer; import java.util.TimerTask; -import androidx.annotation.NonNull; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.PopupMenu; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - public class ChatActivity extends BaseActivity implements OnMediaPickedListener, QBMessageStatusListener { private static final String TAG = ChatActivity.class.getSimpleName(); @@ -142,6 +142,7 @@ public class ChatActivity extends BaseActivity implements OnMediaPickedListener, private int skipPagination = 0; private Boolean checkAdapterInit = false; private boolean isOpenFromCall = false; + private String streamID; private ServiceConnection callServiceConnection; public static void startForResultFromCall(Activity activity, int code, String dialogId, boolean isOpenFromCall) { @@ -182,20 +183,36 @@ public void onCreate(Bundle savedInstanceState) { qbChatDialog = getQBDialogsHolder().getDialogById(dialogID); Log.d(TAG, "Deserialized dialog = " + qbChatDialog); + Log.d(TAG, "dialogID = " + dialogID); + + if (qbChatDialog == null) { + QBRestChatService.getChatDialogById(dialogID).performAsync(new QBEntityCallback() { + @Override + public void onSuccess(QBChatDialog dialog, Bundle bundle) { + getQBDialogsHolder().addDialog(dialog); + qbChatDialog = dialog; + } + + @Override + public void onError(QBResponseException e) { + showErrorSnackbar(R.string.select_users_get_dialog_error, e, null); + } + }); + } try { qbChatDialog.initForChat(QBChatService.getInstance()); + qbChatDialog.addMessageListener(chatMessageListener); + setChatNameToActionBar(); } catch (IllegalStateException | NullPointerException e) { Log.d(TAG, "initForChat error. Error message is : " + e.getMessage()); Log.e(TAG, "Finishing " + TAG + ". Unable to init chat"); finish(); } - qbChatDialog.addMessageListener(chatMessageListener); // TODO Typing Status: 1/3 To add Typing Status functionality uncomment this string: //qbChatDialog.addIsTypingListener(new TypingStatusListener()); - setChatNameToActionBar(); initViews(); initMessagesRecyclerView(); initChatConnectionListener(); @@ -225,9 +242,14 @@ protected void onRestoreInstanceState(Bundle savedInstanceState) { } @Override - public void onResumeFinished() { + protected void onResume() { checker = new PermissionsChecker(getApplicationContext()); sessionManager = WebRtcSessionManager.getInstance(); + super.onResume(); + } + + @Override + public void onResumeFinished() { if (getChatHelper().isLogged()) { if (qbChatDialog == null) { String dialogID = getIntent().getStringExtra(EXTRA_DIALOG_ID); @@ -259,7 +281,6 @@ public void onError(QBResponseException e) { } private void reloginToChat() { - showProgressDialog(R.string.dlg_login); getChatHelper().loginToChat(getSharedPrefsHelper().getQbUser(), new QBEntityCallback() { @Override public void onSuccess(Void aVoid, Bundle bundle) { @@ -283,29 +304,31 @@ public void onClick(View v) { private void returnToChat() { Log.e(TAG, "Returning to Chat"); - qbChatDialog.initForChat(QBChatService.getInstance()); - if (!qbChatDialog.isJoined()) { - try { - qbChatDialog.join(new DiscussionHistory()); - } catch (Exception e) { - Log.e(TAG, "Join Dialog Exception: " + e.getMessage()); - showErrorSnackbar(R.string.error_joining_chat, e, new View.OnClickListener() { - @Override - public void onClick(View v) { - returnToChat(); - } - }); + if (qbChatDialog != null) { + qbChatDialog.initForChat(QBChatService.getInstance()); + if (!qbChatDialog.isJoined()) { + try { + qbChatDialog.join(new DiscussionHistory()); + } catch (Exception e) { + Log.e(TAG, "Join Dialog Exception: " + e.getMessage()); + showErrorSnackbar(R.string.error_joining_chat, e, new View.OnClickListener() { + @Override + public void onClick(View v) { + returnToChat(); + } + }); + } } - } - returnListeners(); + returnListeners(); - // Loading unread messages received in background - if (getSharedPrefsHelper().get(IS_IN_BACKGROUND, false)) { - progressBar.setVisibility(View.VISIBLE); - skipPagination = 0; - checkAdapterInit = false; - loadChatHistory(); + // Loading unread messages received in background + if (getSharedPrefsHelper().get(IS_IN_BACKGROUND, false)) { + progressBar.setVisibility(View.VISIBLE); + skipPagination = 0; + checkAdapterInit = false; + loadChatHistory(); + } } } @@ -351,6 +374,7 @@ public void onClick(View v) { @Override protected void onPause() { super.onPause(); + hideProgressDialog(); chatAdapter.removeClickListeners(); qbChatDialog.removeMessageListrener(chatMessageListener); QBChatService.getInstance().removeConnectionListener(chatConnectionListener); @@ -434,9 +458,9 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; case R.id.menu_chat_action_stream: if (getChatHelper().isLogged()) { - String streamID = createNewStreamID(); + streamID = createNewStreamID(); getDialogsManager().sendMessageStartedStream(qbChatDialog, streamID); - checkStreamPermissions(streamID); + checkStreamPermissions(); } else { openAlertDialogWaitChat(); } @@ -519,12 +543,12 @@ public void onClick(View v) { }); } - private void checkStreamPermissions(String newStreamID) { + private void checkStreamPermissions() { boolean needToAskPermissions = checker.missAllPermissions(Consts.PERMISSIONS); if (needToAskPermissions) { PermissionsActivity.startForResult(this, REQUEST_STREAM_PERMISSION_CODE, Consts.PERMISSIONS); } else { - startStream(newStreamID); + startStream(streamID); } } @@ -550,14 +574,14 @@ public void onError(WsException e) { showErrorSnackbar(R.string.join_stream_error, e, new View.OnClickListener() { @Override public void onClick(View v) { - checkStreamPermissions(newStreamID); + checkStreamPermissions(); } }); } }); } - private void joinStream(String streamID, String streamerName) { + private void joinStream(String roomId, String streamerName) { Log.d(TAG, "Join Stream : " + streamID); showProgressDialog(R.string.join_stream); ConferenceClient client = ConferenceClient.getInstance(getApplicationContext()); @@ -569,7 +593,7 @@ public void onSuccess(ConferenceSession session) { hideProgressDialog(); sessionManager.setCurrentSession(session); Log.d(TAG, "Session Created Successfully. \n Session ID = " + session.getSessionID() + "\n Dialog ID = " + session.getDialogID()); - CallActivity.start(ChatActivity.this, streamID, streamerName, qbChatDialog.getDialogId(), qbChatDialog.getOccupants(), true); + CallActivity.start(ChatActivity.this, roomId, streamerName, qbChatDialog.getDialogId(), qbChatDialog.getOccupants(), true); } @Override @@ -818,8 +842,7 @@ public void onError(QBResponseException e) { checkConferencePermissions(); } if (requestCode == REQUEST_STREAM_PERMISSION_CODE) { - String streamID = createNewStreamID(); - checkStreamPermissions(streamID); + checkStreamPermissions(); } } } @@ -1055,21 +1078,21 @@ public void onError(QBResponseException e) { } private void initChat() { - switch (qbChatDialog.getType()) { - case GROUP: - case PUBLIC_GROUP: - joinGroupChat(false); - break; - - case PRIVATE: - loadDialogUsers(); - break; - - default: - ToastUtils.shortToast(getApplicationContext(), String.format("%s %s", getString(R.string.chat_unsupported_type), qbChatDialog.getType().name())); - Log.e(TAG, "Finishing " + TAG + ". Unsupported chat type"); - finish(); - break; + if (qbChatDialog != null) { + switch (qbChatDialog.getType()) { + case GROUP: + case PUBLIC_GROUP: + joinGroupChat(false); + break; + case PRIVATE: + loadDialogUsers(); + break; + default: + ToastUtils.shortToast(getApplicationContext(), String.format("%s %s", getString(R.string.chat_unsupported_type), qbChatDialog.getType().name())); + Log.e(TAG, "Finishing " + TAG + ". Unsupported chat type"); + finish(); + break; + } } } @@ -1293,9 +1316,9 @@ private void startSuitableConversation(QBChatMessage qbChatMessage) { if (isConference) { checkConferencePermissions(); } else if (isStream) { - String streamID = (String) qbChatMessage.getProperty(DialogsManager.PROPERTY_CONVERSATION_ID); + streamID = (String) qbChatMessage.getProperty(DialogsManager.PROPERTY_CONVERSATION_ID); if (currentUser.getId().equals(qbChatMessage.getSenderId())) { - checkStreamPermissions(streamID); + checkStreamPermissions(); } else { QBUser streamer = getQBUsersHolder().getUserById(qbChatMessage.getSenderId()); String streamerName = TextUtils.isEmpty(streamer.getFullName()) ? streamer.getLogin() : streamer.getFullName(); @@ -1599,7 +1622,6 @@ public void onServiceConnected(ComponentName name, IBinder service) { unbindService(callServiceConnection); callService.stopSelf(); callService.stopForeground(true); - } } } diff --git a/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/adapters/OpponentsFromCallAdapter.java b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/adapters/OpponentsFromCallAdapter.java index 55a50801f..76ccd8574 100644 --- a/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/adapters/OpponentsFromCallAdapter.java +++ b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/adapters/OpponentsFromCallAdapter.java @@ -14,6 +14,8 @@ import android.widget.TextView; import android.widget.ToggleButton; +import androidx.recyclerview.widget.RecyclerView; + import com.quickblox.conference.ConferenceSession; import com.quickblox.conference.QBConferencePeerConnection; import com.quickblox.conference.view.QBConferenceSurfaceView; @@ -24,8 +26,6 @@ import java.util.List; -import androidx.recyclerview.widget.RecyclerView; - public class OpponentsFromCallAdapter extends RecyclerView.Adapter { private static final String TAG = OpponentsFromCallAdapter.class.getSimpleName(); @@ -168,8 +168,11 @@ private int getItemHeight() { } public void add(QBUser item) { - opponents.add(item); - notifyItemRangeChanged(0, opponents.size()); + if (session.getCurrentUserID().equals(item.getId())) { + opponents.add(0, item); + } else { + opponents.add(item); + } notifyDataSetChanged(); } diff --git a/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/fragments/ConversationFragment.java b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/fragments/ConversationFragment.java index 02c7d3fed..fef35454f 100644 --- a/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/fragments/ConversationFragment.java +++ b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/fragments/ConversationFragment.java @@ -22,6 +22,12 @@ import android.widget.TextView; import android.widget.ToggleButton; +import androidx.annotation.DimenRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import com.quickblox.conference.ConferenceSession; import com.quickblox.conference.view.QBConferenceSurfaceView; import com.quickblox.core.QBEntityCallback; @@ -55,12 +61,6 @@ import java.util.Timer; import java.util.TimerTask; -import androidx.annotation.DimenRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - public class ConversationFragment extends BaseToolBarFragment implements CallService.CurrentCallStateCallback, QBRTCSessionStateCallback, QBRTCClientVideoTracksCallbacks, OpponentsFromCallAdapter.OnAdapterEventListener, CallService.OnlineParticipantsChangeListener { @@ -102,6 +102,7 @@ public class ConversationFragment extends BaseToolBarFragment implements CallSer private Integer fullScreenUserID; private String roomTitle; private SharedPrefsHelper sharedPrefsHelper; + private final ReconnectionCallbackImpl reconnectionCallback = new ReconnectionCallbackImpl(TAG); private boolean asListenerRole; private Map onlineParticipants = new HashMap<>(); private SparseArray opponentViewHolders; @@ -124,7 +125,7 @@ public void onStart() { Log.d(TAG, "currentSession = null onStart"); return; } - + conversationFragmentCallback.addReconnectionCallback(reconnectionCallback); if (!allCallbacksInit) { conversationFragmentCallback.addClientConnectionCallback(this); initTrackListeners(); @@ -190,6 +191,7 @@ public void onPause() { if (cameraState != CameraState.DISABLED_FROM_USER && !conversationFragmentCallback.isListenerRole() && !conversationFragmentCallback.isScreenSharingState()) { toggleCamera(false); } + conversationFragmentCallback.removeReconnectionCallback(reconnectionCallback); releaseViews(); super.onPause(); } @@ -218,7 +220,6 @@ private void initFields() { } private void initViews(View view) { - Log.i(TAG, "initViews"); conversationPlaceholder = view.findViewById(R.id.conversation_placeholder); micToggle = view.findViewById(R.id.tb_switch_mic); cameraToggle = view.findViewById(R.id.tb_switch_cam); @@ -280,13 +281,11 @@ private void initViews(View view) { recyclerView.setLayoutManager(gridLayoutManager); - opponentsAdapter = new OpponentsFromCallAdapter(getActivity(), currentSession, new ArrayList<>(), recyclerView.getHeight()); - //for correct removing item in adapter - recyclerView.setItemAnimator(null); recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { setGrid(recyclerView.getHeight()); + recyclerView.setItemAnimator(null); recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this); if (currentSession.getState() != BaseSession.QBRTCSessionState.QB_RTC_SESSION_CONNECTED) { startJoinConference(); @@ -308,14 +307,32 @@ public void onGlobalLayout() { } }); - if (conversationFragmentCallback.isListenerRole()) { - actionButtonsEnabled(true); - } else { - actionButtonsEnabled(false); - } + actionButtonsEnabled(conversationFragmentCallback.isListenerRole()); setActionButtonsVisibility(); } + private void reconnectState() { + if (!asListenerRole) { + micToggle.setVisibility(View.GONE); + cameraToggle.setVisibility(View.GONE); + screenSharingToggle.setVisibility(View.GONE); + swapCamToggle.setVisibility(View.GONE); + ibGoToChat.setVisibility(View.INVISIBLE); + ibManageGroup.setVisibility(View.INVISIBLE); + } + } + + private void conversationState() { + if (!asListenerRole) { + micToggle.setVisibility(View.VISIBLE); + cameraToggle.setVisibility(View.VISIBLE); + screenSharingToggle.setVisibility(View.VISIBLE); + swapCamToggle.setVisibility(View.VISIBLE); + ibGoToChat.setVisibility(View.VISIBLE); + ibManageGroup.setVisibility(View.VISIBLE); + } + } + private void updateToolbar() { if (onlineParticipants != null && getContext() != null) { String membersTitle; @@ -333,10 +350,6 @@ private void updateToolbar() { } } - private void startJoinConference() { - conversationFragmentCallback.onStartJoinConference(); - } - private void switchCamera() { if (cameraState == CameraState.DISABLED_FROM_USER) { return; @@ -452,8 +465,12 @@ private void setActionButtonsInvisible() { swapCamToggle.setVisibility(View.INVISIBLE); } + private void startJoinConference() { + conversationFragmentCallback.onStartJoinConference(); + } + private void setGrid(int itemHeight) { - opponentsAdapter = new OpponentsFromCallAdapter(getActivity(), currentSession, new ArrayList<>(), itemHeight); + opponentsAdapter = new OpponentsFromCallAdapter(getContext(), currentSession, new ArrayList<>(), itemHeight); opponentsAdapter.setAdapterListener(this); recyclerView.setAdapter(opponentsAdapter); } @@ -859,7 +876,9 @@ private void setOpponentToAdapter(Integer userID) { qbUser.setFullName(getString(R.string.load_user)); loadUserById(userID); } - opponentsAdapter.add(qbUser); + if (opponentsAdapter != null) { + opponentsAdapter.add(qbUser); + } } ////////////////////////////////////////// @@ -916,7 +935,7 @@ public void onConnectionClosedForUser(ConferenceSession qbrtcSession, Integer us @Override public void onDisconnectedFromUser(ConferenceSession qbrtcSession, Integer userID) { - + // empty } @Override @@ -936,7 +955,6 @@ public void onStateChanged(ConferenceSession session, BaseSession.QBRTCSessionSt @Override public void onLocalVideoTrackReceive(ConferenceSession session, QBRTCVideoTrack videoTrack) { - Log.d(TAG, "onLocalVideoTrackReceive"); cameraState = CameraState.NONE; actionButtonsEnabled(true); @@ -950,7 +968,7 @@ public void onLocalVideoTrackReceive(ConferenceSession session, QBRTCVideoTrack @Override public void onRemoteVideoTrackReceive(ConferenceSession session, final QBRTCVideoTrack videoTrack, final Integer userID) { - Log.d(TAG, "onRemoteVideoTrackReceive for opponent= " + userID); + onConnectedToUser(session, userID); } private void setRemoteViewMultiCall(int userID) { @@ -966,7 +984,6 @@ private void setRemoteViewMultiCall(int userID) { return; } final QBConferenceSurfaceView remoteVideoView = itemHolder.getSurfaceView(); - if (remoteVideoView != null) { remoteVideoView.setZOrderMediaOverlay(true); updateVideoView(remoteVideoView); @@ -1011,7 +1028,6 @@ public void onParticipantsCountChanged(Map onlineParticipants) this.onlineParticipants = onlineParticipants; if (getActivity() != null) { getActivity().runOnUiThread(new Runnable() { - @Override public void run() { updateToolbar(); } @@ -1125,4 +1141,40 @@ private enum CameraState { DISABLED_FROM_USER, ENABLED_FROM_USER } + + private class ReconnectionCallbackImpl implements ReconnectionCallback { + private final String tag; + + ReconnectionCallbackImpl(String tag) { + this.tag = tag; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 53 * hash + tag.hashCode(); + return hash; + } + + @Override + public boolean equals(Object obj) { + boolean equals; + if (obj instanceof ReconnectionCallbackImpl) { + equals = TAG.equals(((ReconnectionCallbackImpl) obj).tag); + } else { + equals = super.equals(obj); + } + return equals; + } + + @Override + public void completed() { + conversationState(); + } + + @Override + public void inProgress() { + reconnectState(); + } + } } \ No newline at end of file diff --git a/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/fragments/ConversationFragmentCallback.java b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/fragments/ConversationFragmentCallback.java index 7e7578052..356095619 100644 --- a/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/fragments/ConversationFragmentCallback.java +++ b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/fragments/ConversationFragmentCallback.java @@ -20,6 +20,10 @@ public interface ConversationFragmentCallback { void removeCurrentCallStateCallback(CallService.CurrentCallStateCallback currentCallStateCallback); + void addReconnectionCallback(ReconnectionCallback reconnectionCallback); + + void removeReconnectionCallback(ReconnectionCallback reconnectionCallback); + void onSetAudioEnabled(boolean isAudioEnabled); void onSetVideoEnabled(boolean isNeedEnableCam); diff --git a/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/fragments/ReconnectionCallback.java b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/fragments/ReconnectionCallback.java new file mode 100644 index 000000000..369cca7df --- /dev/null +++ b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/fragments/ReconnectionCallback.java @@ -0,0 +1,6 @@ +package com.quickblox.sample.videochat.conference.java.fragments; + +public interface ReconnectionCallback { + void completed(); + void inProgress(); +} \ No newline at end of file diff --git a/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/services/CallService.java b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/services/CallService.java index a2acd4a92..43aba6869 100644 --- a/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/services/CallService.java +++ b/sample-conference-java/app/src/main/java/com/quickblox/sample/videochat/conference/java/services/CallService.java @@ -14,8 +14,13 @@ import android.os.CountDownTimer; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.util.Log; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.core.app.NotificationCompat; + import com.quickblox.conference.ConferenceClient; import com.quickblox.conference.ConferenceSession; import com.quickblox.conference.QBConferenceRole; @@ -27,8 +32,6 @@ import com.quickblox.sample.videochat.conference.java.activities.CallActivity; import com.quickblox.sample.videochat.conference.java.managers.WebRtcSessionManager; import com.quickblox.sample.videochat.conference.java.utils.Consts; -import com.quickblox.sample.videochat.conference.java.utils.NetworkConnectionChecker; -import com.quickblox.sample.videochat.conference.java.utils.ToastUtils; import com.quickblox.videochat.webrtc.AppRTCAudioManager; import com.quickblox.videochat.webrtc.BaseSession; import com.quickblox.videochat.webrtc.QBMediaStreamManager; @@ -48,34 +51,30 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; - -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.core.app.NotificationCompat; +import java.util.Timer; +import java.util.TimerTask; public class CallService extends Service { private static final String TAG = CallService.class.getSimpleName(); private static final int SERVICE_ID = 646; + private static final long SECONDS_13 = 13000; private static final String CHANNEL_ID = "Quickblox Conference Channel"; private static final String CHANNEL_NAME = "Quickblox Background Conference service"; private static final String ICE_FAILED_REASON = "ICE failed"; - private HashMap videoTrackMap = new HashMap<>(); - private CallServiceBinder callServiceBinder = new CallServiceBinder(); - private NetworkConnectionListener networkConnectionListener; - private NetworkConnectionChecker networkConnectionChecker; + private final HashMap videoTrackMap = new HashMap<>(); + private final CallServiceBinder callServiceBinder = new CallServiceBinder(); private SessionStateListener sessionStateListener; private VideoTrackListener videoTrackListener; private AudioTrackListener audioTrackListener; private ConferenceSessionListener conferenceSessionListener; - private ArrayList currentCallStateCallbackList = new ArrayList<>(); - private Set subscribedPublishers = new CopyOnWriteArraySet<>(); + private final ArrayList currentCallStateCallbackList = new ArrayList<>(); private ArrayList opponentsIDsList = new ArrayList<>(); private Map onlineParticipants = new HashMap<>(); private OnlineParticipantsChangeListener onlineParticipantsChangeListener; @@ -83,8 +82,6 @@ public class CallService extends Service { private UsersConnectDisconnectCallback usersConnectDisconnectCallback; private AppRTCAudioManager audioManager; private boolean sharingScreenState = false; - private boolean isCallState = false; - private volatile boolean connectedToJanus; private String roomID; private String roomTitle; private String dialogID; @@ -92,6 +89,8 @@ public class CallService extends Service { private boolean previousDeviceEarPiece; private ConferenceSession currentSession; private ConferenceClient conferenceClient; + private ReconnectionState reconnectionState = ReconnectionState.DEFAULT; + private final Set reconnectionListeners = new HashSet<>(); public static void start(Context context, String roomID, String roomTitle, String dialogID, List occupants, boolean listenerRole) { Intent intent = new Intent(context, CallService.class); @@ -109,11 +108,18 @@ public static void stop(Context context) { context.stopService(intent); } + public ReconnectionState getReconnectionState() { + return reconnectionState; + } + + public void setReconnectionState(ReconnectionState reconnectionState) { + this.reconnectionState = reconnectionState; + } + @Override public void onCreate() { currentSession = WebRtcSessionManager.getInstance().getCurrentSession(); initConferenceClient(); - initNetworkChecker(); initListeners(); initAudioManager(); super.onCreate(); @@ -129,10 +135,11 @@ public int onStartCommand(Intent intent, int flags, int startId) { dialogID = intent.getStringExtra(Consts.EXTRA_DIALOG_ID); opponentsIDsList = (ArrayList) intent.getSerializableExtra(Consts.EXTRA_DIALOG_OCCUPANTS); asListenerRole = intent.getBooleanExtra(Consts.EXTRA_AS_LISTENER, false); - } - if (!isListenerRole()) { - onlineParticipantsCheckerCountdown = new OnlineParticipantsCheckerCountdown(Long.MAX_VALUE, 3000); - onlineParticipantsCheckerCountdown.start(); + + if (!isListenerRole() && !roomID.equals(dialogID)) { + onlineParticipantsCheckerCountdown = new OnlineParticipantsCheckerCountdown(Long.MAX_VALUE, 3000); + onlineParticipantsCheckerCountdown.start(); + } } return super.onStartCommand(intent, flags, startId); } @@ -140,8 +147,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { @Override public void onDestroy() { super.onDestroy(); - networkConnectionChecker.unregisterListener(networkConnectionListener); - if (!isListenerRole()) { + if (onlineParticipantsCheckerCountdown != null && !isListenerRole()) { onlineParticipantsCheckerCountdown.cancel(); } removeVideoTrackRenders(); @@ -220,10 +226,12 @@ public void setOnlineParticipantsChangeListener(OnlineParticipantsChangeListener this.onlineParticipantsChangeListener = onlineParticipantsChangeListener; } - private void initNetworkChecker() { - networkConnectionChecker = new NetworkConnectionChecker(getApplication()); - networkConnectionListener = new NetworkConnectionListener(); - networkConnectionChecker.registerListener(networkConnectionListener); + public void subscribeReconnectionListener(ReconnectionListener reconnectionListener) { + reconnectionListeners.add(reconnectionListener); + } + + public void unsubscribeReconnectionListener(ReconnectionListener reconnectionListener) { + reconnectionListeners.remove(reconnectionListener); } private void initConferenceClient() { @@ -247,31 +255,26 @@ private void initListeners() { } private void initAudioManager() { - audioManager = AppRTCAudioManager.create(this); - audioManager.selectAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE); - previousDeviceEarPiece = false; - Log.d(TAG, "AppRTCAudioManager.AudioDevice.SPEAKER_PHONE"); - - audioManager.setOnWiredHeadsetStateListener((plugged, hasMicrophone) -> { - if (isCallState) { - ToastUtils.shortToast(getApplicationContext(), "Headset " + (hasMicrophone ? "with microphone" : "without microphone") + (plugged ? "plugged" : "unplugged")); - } - if (!plugged) { - if (previousDeviceEarPiece) { - setAudioDeviceDelayed(AppRTCAudioManager.AudioDevice.EARPIECE); - } else { - setAudioDeviceDelayed(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE); + if (audioManager == null) { + audioManager = AppRTCAudioManager.create(this); + audioManager.selectAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE); + previousDeviceEarPiece = false; + Log.d(TAG, "AppRTCAudioManager.AudioDevice.SPEAKER_PHONE"); + + audioManager.setOnWiredHeadsetStateListener((plugged, hasMicrophone) -> { + + if (!plugged) { + if (previousDeviceEarPiece) { + setAudioDeviceDelayed(AppRTCAudioManager.AudioDevice.EARPIECE); + } else { + setAudioDeviceDelayed(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE); + } } - } - }); - - audioManager.setBluetoothAudioDeviceStateListener(connected -> { - if (isCallState) { - ToastUtils.shortToast(getApplicationContext(), "Bluetooth " + (connected ? "connected" : "disconnected")); - } - }); - - audioManager.start((audioDevice, set) -> ToastUtils.shortToast(getApplicationContext(), "Audio Device Switched to " + audioDevice)); + }); + audioManager.start((audioDevice, set) -> + Log.d(TAG, "Audio Device Switched to " + audioDevice) + ); + } } private void setAudioDeviceDelayed(final AppRTCAudioManager.AudioDevice audioDevice) { @@ -279,7 +282,9 @@ private void setAudioDeviceDelayed(final AppRTCAudioManager.AudioDevice audioDev } private void releaseAudioManager() { - audioManager.stop(); + if (audioManager != null) { + audioManager.stop(); + } } public boolean currentSessionExist() { @@ -288,6 +293,7 @@ public boolean currentSessionExist() { public void leaveCurrentSession() { currentSession.leave(); + releaseCurrentSession(); } private void releaseCurrentSession() { @@ -349,10 +355,6 @@ private void removeConferenceSessionListener(ConferenceSessionListener listener) } } - private void notifyFragmentParticipantsChanged() { - onlineParticipantsChangeListener.onParticipantsCountChanged(onlineParticipants); - } - // Common methods public ArrayList getOpponentsIDsList() { @@ -364,18 +366,20 @@ public ArrayList getActivePublishers() { } public void getOnlineParticipants(ConferenceEntityCallback> callback) { - currentSession.getOnlineParticipants(new ConferenceEntityCallback>() { - @Override - public void onSuccess(Map integerBooleanMap) { - onlineParticipants = integerBooleanMap; - callback.onSuccess(integerBooleanMap); - } + if (currentSession != null) { + currentSession.getOnlineParticipants(new ConferenceEntityCallback>() { + @Override + public void onSuccess(Map integerBooleanMap) { + onlineParticipants = integerBooleanMap; + callback.onSuccess(integerBooleanMap); + } - @Override - public void onError(WsException e) { - callback.onError(e); - } - }); + @Override + public void onError(WsException e) { + callback.onError(e); + } + }); + } } public String getRoomID() { @@ -485,35 +489,18 @@ public void stopScreenSharing() { } } - private void subscribeToPublishers(ArrayList publishersList) { - subscribedPublishers.addAll(currentSession.getActivePublishers()); - for (Integer publisher : publishersList) { - currentSession.subscribeToPublisher(publisher); - } - } - public void joinConference() { - Log.d(TAG, "Start Join Conference"); - int userID = currentSession.getCurrentUserID(); QBConferenceRole conferenceRole = asListenerRole ? QBConferenceRole.LISTENER : QBConferenceRole.PUBLISHER; currentSession.joinDialog(roomID, conferenceRole, new ConferenceEntityCallback>() { @Override public void onSuccess(ArrayList publishers) { - Log.d(TAG, "onSuccess joinDialog sessionUserID= " + userID + ", publishers= " + publishers); - if (conferenceClient.isAutoSubscribeAfterJoin()) { - subscribedPublishers.addAll(publishers); - } - if (asListenerRole) { - connectedToJanus = true; - } + // empty } @Override public void onError(WsException exception) { Log.d(TAG, "onError joinDialog exception= " + exception); - ToastUtils.shortToast(getApplicationContext(), "Join exception: " + exception.getMessage()); - releaseCurrentSession(); } }); } @@ -549,15 +536,12 @@ public CallService getService() { private class ConferenceSessionListener implements ConferenceSessionCallbacks { @Override public void onPublishersReceived(ArrayList publishersList) { - Log.d(TAG, "OnPublishersReceived connectedToJanus " + connectedToJanus); - subscribedPublishers.addAll(publishersList); - subscribeToPublishers(publishersList); + currentSession.subscribeToPublisher(publishersList.get(0)); } @Override public void onPublisherLeft(Integer userID) { Log.d(TAG, "OnPublisherLeft userID" + userID); - subscribedPublishers.remove(userID); } @Override @@ -580,15 +564,63 @@ public void onError(WsException exception) { @Override public void onSessionClosed(ConferenceSession session) { - Log.d(TAG, "Session " + session.getSessionID() + " start stop session"); + if (session.equals(currentSession) && reconnectionState == ReconnectionState.IN_PROGRESS) { + new ReconnectionTimer().reconnect(); + } + } + } - if (session.equals(currentSession)) { - Log.d(TAG, "Stop session"); - if (audioManager != null) { - audioManager.stop(); + private class ReconnectionTimer { + private Timer timer = new Timer(); + + private long lastDelay = 0; + private long delay = 1000; + private long newDelay = 0; + + void reconnect() { + if (newDelay >= SECONDS_13) { + reconnectionState = ReconnectionState.FAILED; + for (ReconnectionListener reconnectionListener : reconnectionListeners) { + reconnectionListener.onChangedState(reconnectionState); } - releaseCurrentSession(); + leaveCurrentSession(); + return; } + newDelay = lastDelay + delay; + lastDelay = delay; + delay = newDelay; + + timer.cancel(); + timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + ConferenceClient client = ConferenceClient.getInstance(getApplicationContext()); + QBRTCTypes.QBConferenceType conferenceType = QBRTCTypes.QBConferenceType.QB_CONFERENCE_TYPE_VIDEO; + client.createSession(currentSession.getCurrentUserID(), conferenceType, new ConferenceEntityCallback() { + @Override + public void onSuccess(ConferenceSession conferenceSession) { + WebRtcSessionManager.getInstance().setCurrentSession(conferenceSession); + currentSession = conferenceSession; + initListeners(); + timer.purge(); + timer.cancel(); + timer = null; + reconnectionState = ReconnectionState.COMPLETED; + for (ReconnectionListener reconnectionListener : reconnectionListeners) { + new Handler(Looper.getMainLooper()).post(() -> { + reconnectionListener.onChangedState(reconnectionState); + }); + } + } + + @Override + public void onError(WsException exception) { + reconnect(); + } + }); + } + }, newDelay); } } @@ -599,36 +631,37 @@ public void onSessionClosed(ConferenceSession session) { private class SessionStateListener implements QBRTCSessionStateCallback { @Override public void onStateChanged(ConferenceSession conferenceSession, BaseSession.QBRTCSessionState qbrtcSessionState) { - if (BaseSession.QBRTCSessionState.QB_RTC_SESSION_CONNECTED.equals(qbrtcSessionState)) { - connectedToJanus = true; - Log.d(TAG, "onStateChanged and begin subscribeToPublishersIfNeed"); - subscribeToPublishersIfNeed(); - } else { - connectedToJanus = false; - } - } - - private void subscribeToPublishersIfNeed() { - Set notSubscribedPublishers = new CopyOnWriteArraySet<>(currentSession.getActivePublishers()); - notSubscribedPublishers.removeAll(subscribedPublishers); - if (!notSubscribedPublishers.isEmpty()) { - subscribeToPublishers(new ArrayList<>(notSubscribedPublishers)); - } + // empty } @Override public void onConnectedToUser(ConferenceSession conferenceSession, Integer userID) { Log.d(TAG, "onConnectedToUser userID= " + userID + " sessionID= " + conferenceSession.getSessionID()); - isCallState = true; notifyCallStateListenersCallStarted(); if (usersConnectDisconnectCallback != null) { usersConnectDisconnectCallback.onUserConnected(userID); } + if (userID.equals(currentSession.getCurrentUserID()) && reconnectionState == ReconnectionState.IN_PROGRESS) { + reconnectionState = ReconnectionState.COMPLETED; + for (ReconnectionListener reconnectionListener : reconnectionListeners) { + new Handler(Looper.getMainLooper()).post(() -> { + reconnectionListener.onChangedState(reconnectionState); + }); + } + } } @Override public void onDisconnectedFromUser(ConferenceSession conferenceSession, Integer userID) { - Log.d(TAG, "QBRTCSessionStateCallbackImpl onDisconnectedFromUser userID=" + userID); + if (userID.equals(currentSession.getCurrentUserID()) || conferenceSession.getConferenceRole() == QBConferenceRole.LISTENER) { + reconnectionState = ReconnectionState.IN_PROGRESS; + for (ReconnectionListener reconnectionListener : reconnectionListeners) { + new Handler(Looper.getMainLooper()).post(() -> { + reconnectionListener.onChangedState(reconnectionState); + }); + } + currentSession.leave(); + } } @Override @@ -641,18 +674,6 @@ public void onConnectionClosedForUser(ConferenceSession conferenceSession, Integ } } - ////////////////////////////////////////// - // Network Connection Checker - ////////////////////////////////////////// - - private class NetworkConnectionListener implements NetworkConnectionChecker.OnConnectivityChangedListener { - - @Override - public void connectivityChanged(boolean availableNow) { - ToastUtils.shortToast(getApplicationContext(), "Internet Connection " + (availableNow ? "Available" : "Unavailable")); - } - } - ////////////////////////////////////////// // Camera Events Handler ////////////////////////////////////////// @@ -660,17 +681,17 @@ public void connectivityChanged(boolean availableNow) { private class CameraEventsListener implements CameraVideoCapturer.CameraEventsHandler { @Override public void onCameraError(String s) { - ToastUtils.shortToast(getApplicationContext(), "Camera Error: " + s); + // empty } @Override public void onCameraDisconnected() { - ToastUtils.shortToast(getApplicationContext(), "Camera Disconnected"); + // empty } @Override public void onCameraFreezed(String s) { - ToastUtils.shortToast(getApplicationContext(), "Camera Freezed"); +// ToastUtils.shortToast(getApplicationContext(), "Camera Freezed"); // TODO: Need to make switching camera OFF and then Switching it ON /*if (currentSession != null) { try { @@ -697,17 +718,17 @@ public void run() { @Override public void onCameraOpening(String s) { - ToastUtils.shortToast(getApplicationContext(), "Camera Opening"); + // empty } @Override public void onFirstFrameAvailable() { - ToastUtils.shortToast(getApplicationContext(), "Camera onFirstFrameAvailable"); + // empty } @Override public void onCameraClosed() { - ToastUtils.shortToast(getApplicationContext(), "Camera Closed"); + // empty } } @@ -760,18 +781,16 @@ public void onTick(long millisUntilFinished) { getOnlineParticipants(new ConferenceEntityCallback>() { @Override public void onSuccess(Map integerBooleanMap) { - boolean onlineParticipantsCountChanged = onlineParticipants != null && integerBooleanMap.size() != onlineParticipants.size(); - if (onlineParticipantsCountChanged) { - Log.d(TAG, "Participants count changed. Now online is : " + integerBooleanMap.size()); + if (onlineParticipantsChangeListener != null) { + onlineParticipantsChangeListener.onParticipantsCountChanged(onlineParticipants); } - notifyFragmentParticipantsChanged(); onlineParticipants = integerBooleanMap; } @Override - public void onError(WsException e) { - if (e != null) { - Log.d(TAG, "Error Getting Online Participants - " + e.getMessage()); + public void onError(WsException exception) { + if (exception != null) { + Log.d(TAG, "Error Getting Online Participants - " + exception.getMessage()); } } }); @@ -783,25 +802,28 @@ public void onFinish() { } } - public interface OnChangeDynamicToggle { - - void enableDynamicToggle(boolean plugged, boolean wasEarpiece); - } - public interface CurrentCallStateCallback { - void onCallStarted(); } public interface UsersConnectDisconnectCallback { - void onUserConnected(Integer userID); void onUserDisconnected(Integer userID); } public interface OnlineParticipantsChangeListener { - void onParticipantsCountChanged(Map onlineParticipants); } + + public interface ReconnectionListener { + void onChangedState(ReconnectionState reconnectionState); + } + + public enum ReconnectionState { + COMPLETED, + IN_PROGRESS, + FAILED, + DEFAULT + } } \ No newline at end of file diff --git a/sample-conference-java/app/src/main/res/layout/activity_conversation.xml b/sample-conference-java/app/src/main/res/layout/activity_conversation.xml index 5cf2cdc8b..9112b99c8 100644 --- a/sample-conference-java/app/src/main/res/layout/activity_conversation.xml +++ b/sample-conference-java/app/src/main/res/layout/activity_conversation.xml @@ -1,10 +1,41 @@ - + + android:layout_height="match_parent"> - \ No newline at end of file + + + + + + + + + \ No newline at end of file diff --git a/sample-conference-java/app/src/main/res/layout/list_item_conference_opponent.xml b/sample-conference-java/app/src/main/res/layout/list_item_conference_opponent.xml index 3167ac49b..c5455455a 100644 --- a/sample-conference-java/app/src/main/res/layout/list_item_conference_opponent.xml +++ b/sample-conference-java/app/src/main/res/layout/list_item_conference_opponent.xml @@ -63,5 +63,5 @@ android:id="@+id/progress_bar_adapter" style="@style/MatchWidth" android:layout_gravity="center" - android:visibility="visible" /> + android:visibility="gone" /> \ No newline at end of file diff --git a/sample-conference-java/app/src/main/res/values/strings.xml b/sample-conference-java/app/src/main/res/values/strings.xml index 594aaad8c..c530da28f 100644 --- a/sample-conference-java/app/src/main/res/values/strings.xml +++ b/sample-conference-java/app/src/main/res/values/strings.xml @@ -205,7 +205,7 @@ Starting stream… "%s members" "%s member" - "Publishers are offline" + "Publisher is offline" Join to conference error Join to stream error @@ -296,5 +296,7 @@ Janus server: App Info QA version: + Reconnecting... + Reconnection failed \ No newline at end of file diff --git a/sample-conference-java/artifacts.gradle b/sample-conference-java/artifacts.gradle index 99476f6da..95d0ba8e5 100644 --- a/sample-conference-java/artifacts.gradle +++ b/sample-conference-java/artifacts.gradle @@ -18,7 +18,7 @@ android.applicationVariants.all { } else { apkEndName = "-unaligned.apk" } - newApkName = "${appName}-${variant.buildType.name}-${getDate()}-code-${variant.versionCode}-version-${variant.versionName}" + apkEndName + newApkName = "${appName}-${variant.buildType.name}-${getDate()}-code-${variant.versionCode}-version-${variant.versionName}-java" + apkEndName output.outputFileName = newApkName } } diff --git a/sample-conference-java/gradle/wrapper/gradle-wrapper.jar b/sample-conference-java/gradle/wrapper/gradle-wrapper.jar index f6b961fd5..758de960e 100644 Binary files a/sample-conference-java/gradle/wrapper/gradle-wrapper.jar and b/sample-conference-java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/sample-conference-java/gradle/wrapper/gradle-wrapper.properties b/sample-conference-java/gradle/wrapper/gradle-wrapper.properties index 9a4163a4f..2d80b69a7 100644 --- a/sample-conference-java/gradle/wrapper/gradle-wrapper.properties +++ b/sample-conference-java/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/sample-conference-java/gradlew b/sample-conference-java/gradlew index 91a7e269e..cccdd3d51 100755 --- a/sample-conference-java/gradlew +++ b/sample-conference-java/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,20 +6,38 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` @@ -154,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/sample-conference-java/gradlew.bat b/sample-conference-java/gradlew.bat index aec99730b..e95643d6a 100644 --- a/sample-conference-java/gradlew.bat +++ b/sample-conference-java/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line