From 5feee7871a5235995923957e2e9976613422b9d0 Mon Sep 17 00:00:00 2001 From: VitaliyDovbnya Date: Mon, 30 May 2022 15:40:31 +0200 Subject: [PATCH] 4.0.5-videochat-java --- sample-videochat-java/app/build.gradle | 35 ++- .../app/google-services.json | 147 +------------ sample-videochat-java/app/proguard-rules.pro | 41 +++- .../app/src/main/AndroidManifest.xml | 19 +- .../quickblox/sample/videochat/java/App.java | 10 - .../java/activities/CallActivity.java | 58 ++--- .../java/activities/LoginActivity.java | 2 +- .../ConversationFragmentCallback.java | 2 + .../java/fragments/PreviewFragment.java | 26 +-- .../fragments/VideoConversationFragment.java | 206 ++++++++---------- .../videochat/java/services/CallService.java | 60 ++--- .../java/util/ChatPingAlarmManager.java | 9 +- .../app/src/main/res/values/strings.xml | 2 +- sample-videochat-java/build.gradle | 31 +-- .../gradle/wrapper/gradle-wrapper.jar | Bin 54329 -> 54417 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- sample-videochat-java/gradlew | 78 ++++--- sample-videochat-java/gradlew.bat | 14 +- sample-videochat-java/proguard-rules.pro | 70 ------ 19 files changed, 296 insertions(+), 516 deletions(-) delete mode 100755 sample-videochat-java/proguard-rules.pro diff --git a/sample-videochat-java/app/build.gradle b/sample-videochat-java/app/build.gradle index 160f10296..3d338fdcc 100755 --- a/sample-videochat-java/app/build.gradle +++ b/sample-videochat-java/app/build.gradle @@ -1,41 +1,33 @@ buildscript { repositories { google() - jcenter() - maven { url 'https://maven.fabric.io/public' } - } - - dependencies { - classpath "io.fabric.tools:gradle:$fabricToolsVersion" + mavenCentral() } } apply plugin: 'com.android.application' -apply plugin: 'io.fabric' repositories { google() - jcenter() + mavenCentral() maven { url "https://github.com/QuickBlox/quickblox-android-sdk-releases/raw/master/" } - maven { url 'https://maven.fabric.io/public' } - flatDir { dirs 'libs' } } android { - def versionQACode = 3 + def versionQACode = 1 - compileSdkVersion 28 - buildToolsVersion "28.0.3" + compileSdkVersion 31 + buildToolsVersion "31.0.0" flavorDimensions dimensionDefault defaultConfig { applicationId "com.quickblox.sample.videochat.java" - minSdkVersion 16 - targetSdkVersion 28 - versionCode 404000 - versionName '4.0.4' + minSdkVersion 21 + targetSdkVersion 31 + versionCode 405000 + versionName '4.0.5' multiDexEnabled true } @@ -59,12 +51,14 @@ android { minifyEnabled false shrinkResources false proguardFile 'proguard-rules.pro' - zipAlignEnabled false resValue "string", "versionName", "QuickBlox Video Chat Java\nBuild version " + defaultConfig.getVersionName() } release { signingConfig signingConfigs.debug + minifyEnabled true + shrinkResources true + proguardFile 'proguard-rules.pro' resValue "string", "versionName", "QuickBlox Video Chat Java\nBuild version " + defaultConfig.getVersionName() } } @@ -94,12 +88,9 @@ dependencies { implementation "com.google.firebase:firebase-core:$firebaseCoreVersion" implementation "com.navercorp.pulltorefresh:library:$pullToRefreshVersion@aar" - implementation("com.crashlytics.sdk.android:crashlytics:$crashlyticsVersion@aar") { - transitive = true - } + implementation "com.google.android.material:material:$materialVersion" implementation "com.github.johnkil.android-robototextview:robototextview:$robotoTextViewVersion" - implementation "com.github.bumptech.glide:glide:$glideVersion" } apply from: "../artifacts.gradle" diff --git a/sample-videochat-java/app/google-services.json b/sample-videochat-java/app/google-services.json index 542e3ac38..97ec23e8e 100644 --- a/sample-videochat-java/app/google-services.json +++ b/sample-videochat-java/app/google-services.json @@ -1,154 +1,29 @@ { "project_info": { - "project_number": "247738611464", - "firebase_url": "https://qb-prod-samples.firebaseio.com", - "project_id": "qb-prod-samples", - "storage_bucket": "qb-prod-samples.appspot.com" + "project_number": "Put here your value", + "firebase_url": "https://qb-samples.firebaseio.com", + "project_id": "qb-samples", + "storage_bucket": "qb-samples.appspot.com" }, "client": [ { "client_info": { - "mobilesdk_app_id": "1:247738611464:android:beb270faa2c3a789", + "mobilesdk_app_id": "Put here your value", "android_client_info": { - "package_name": "com.quickblox.sample.chat.java" - } - }, - "oauth_client": [], - "api_key": [ - { - "current_key": "AIzaSyBFXAfVr6kkFJdDNOm8U-c7iju0qIUkc_A" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "247738611464-v2nvd29bmqum7niosnfuh28oq3beh9f6.apps.googleusercontent.com", - "client_type": 3 - } - ] - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:247738611464:android:1cdc72e9ffd29448", - "android_client_info": { - "package_name": "com.quickblox.sample.chat.kotlin" - } - }, - "oauth_client": [], - "api_key": [ - { - "current_key": "AIzaSyBFXAfVr6kkFJdDNOm8U-c7iju0qIUkc_A" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "247738611464-v2nvd29bmqum7niosnfuh28oq3beh9f6.apps.googleusercontent.com", - "client_type": 3 - } - ] - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:247738611464:android:06cb0de4c719ad84", - "android_client_info": { - "package_name": "com.quickblox.sample.pushnotifications.java" - } - }, - "oauth_client": [], - "api_key": [ - { - "current_key": "AIzaSyBFXAfVr6kkFJdDNOm8U-c7iju0qIUkc_A" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "247738611464-v2nvd29bmqum7niosnfuh28oq3beh9f6.apps.googleusercontent.com", - "client_type": 3 - } - ] - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:247738611464:android:c2749661061637f0", - "android_client_info": { - "package_name": "com.quickblox.sample.pushnotifications.kotlin" - } - }, - "oauth_client": [], - "api_key": [ - { - "current_key": "AIzaSyBFXAfVr6kkFJdDNOm8U-c7iju0qIUkc_A" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "247738611464-v2nvd29bmqum7niosnfuh28oq3beh9f6.apps.googleusercontent.com", - "client_type": 3 - } - ] - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:247738611464:android:99e5b55a490c901c", - "android_client_info": { - "package_name": "com.quickblox.sample.videochat.java" + "package_name": "com.quickblox.sample.videochat.kotlin" } }, - "oauth_client": [], - "api_key": [ + "oauth_client": [ { - "current_key": "AIzaSyBFXAfVr6kkFJdDNOm8U-c7iju0qIUkc_A" + "client_id": "Put here your value", + "client_type": 3 } ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "247738611464-v2nvd29bmqum7niosnfuh28oq3beh9f6.apps.googleusercontent.com", - "client_type": 3 - } - ] - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:247738611464:android:ac22e0d1b3a3e86b", - "android_client_info": { - "package_name": "com.quickblox.sample.videochat.kotlin" - } - }, - "oauth_client": [], "api_key": [ { - "current_key": "AIzaSyBFXAfVr6kkFJdDNOm8U-c7iju0qIUkc_A" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "247738611464-v2nvd29bmqum7niosnfuh28oq3beh9f6.apps.googleusercontent.com", - "client_type": 3 - } - ] + "current_key": "Put here your value" } - } + ] } ], "configuration_version": "1" diff --git a/sample-videochat-java/app/proguard-rules.pro b/sample-videochat-java/app/proguard-rules.pro index f1b424510..79cb26c49 100644 --- a/sample-videochat-java/app/proguard-rules.pro +++ b/sample-videochat-java/app/proguard-rules.pro @@ -1,9 +1,11 @@ # Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# By default, the flags in this file are appended to flags specified + +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. # # For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html +# http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface @@ -11,11 +13,32 @@ #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} +#-dontusemixedcaseclassnames +#-dontskipnonpubliclibraryclasses +#-verbose +# + +##---------------Begin: proguard configuration for Gson ---------- +# Gson uses generic type information stored in a class file when working with fields. Proguard +# removes such information by default, so configure it to keep all of it. +-keepattributes EnclosingMethod +-keepattributes InnerClasses +-keepattributes Signature +-keepattributes Exceptions + +# For using GSON @Expose annotation +-keepattributes *Annotation* + +#quickblox sdk +-keep class com.quickblox.** { *; } + +#smack xmpp library +-keep class org.jxmpp.** { *; } +-keep class org.jivesoftware.** { *; } +-dontwarn org.jivesoftware.** -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable +#webrtc +-keep class org.webrtc.** { *; } -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile +#google gms +-keep class com.google.android.gms.** { *; } \ 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 df84427f5..efe74e078 100755 --- a/sample-videochat-java/app/src/main/AndroidManifest.xml +++ b/sample-videochat-java/app/src/main/AndroidManifest.xml @@ -3,7 +3,6 @@ xmlns:tools="http://schemas.android.com/tools" package="com.quickblox.sample.videochat.java"> - @@ -12,7 +11,7 @@ - + @@ -32,6 +31,7 @@ @@ -50,7 +50,8 @@ android:launchMode="singleTask" android:screenOrientation="portrait" /> - - + - + - + diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/App.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/App.java index 6931009f2..9c2aa8ca3 100644 --- a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/App.java +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/App.java @@ -2,12 +2,9 @@ import android.app.Application; -import com.crashlytics.android.Crashlytics; import com.quickblox.auth.session.QBSettings; import com.quickblox.sample.videochat.java.util.QBResRequestExecutor; -import io.fabric.sdk.android.Fabric; - public class App extends Application { //App credentials private static final String APPLICATION_ID = ""; @@ -24,17 +21,10 @@ public class App extends Application { public void onCreate() { super.onCreate(); instance = this; - initFabric(); checkAppCredentials(); initCredentials(); } - private void initFabric() { - if (!BuildConfig.DEBUG) { - Fabric.with(this, new Crashlytics()); - } - } - private void checkAppCredentials() { if (APPLICATION_ID.isEmpty() || AUTH_KEY.isEmpty() || AUTH_SECRET.isEmpty() || ACCOUNT_KEY.isEmpty()) { throw new AssertionError(getString(R.string.error_credentials_empty)); diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/activities/CallActivity.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/activities/CallActivity.java index 441d425bb..4a8b526eb 100755 --- a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/activities/CallActivity.java +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/activities/CallActivity.java @@ -1,6 +1,5 @@ package com.quickblox.sample.videochat.java.activities; -import android.annotation.TargetApi; import android.app.Activity; import android.app.PendingIntent; import android.content.ComponentName; @@ -9,7 +8,6 @@ import android.content.ServiceConnection; import android.content.SharedPreferences; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -23,6 +21,8 @@ import android.widget.LinearLayout; import android.widget.TextView; +import androidx.fragment.app.Fragment; + import com.quickblox.chat.QBChatService; import com.quickblox.sample.videochat.java.R; import com.quickblox.sample.videochat.java.db.QbUsersDbManager; @@ -66,7 +66,8 @@ import java.util.List; import java.util.Map; -import androidx.fragment.app.Fragment; +import static com.quickblox.sample.videochat.java.services.CallService.ONE_OPPONENT; +import static com.quickblox.videochat.webrtc.BaseSession.QBRTCSessionState.QB_RTC_SESSION_PENDING; /** * QuickBlox team @@ -119,6 +120,12 @@ private void initScreen() { initSettingsStrategy(); addListeners(); + if (getIntent() != null && getIntent().getExtras() != null) { + isInComingCall = getIntent().getExtras().getBoolean(Consts.EXTRA_IS_INCOMING_CALL, false); + } else { + isInComingCall = sharedPrefsHelper.get(Consts.EXTRA_IS_INCOMING_CALL, false); + } + if (callService.isCallMode()) { checkPermission(); if (callService.isSharingScreenState()) { @@ -127,12 +134,6 @@ private void initScreen() { } addConversationFragment(isInComingCall); } else { - if (getIntent() != null && getIntent().getExtras() != null) { - isInComingCall = getIntent().getExtras().getBoolean(Consts.EXTRA_IS_INCOMING_CALL, false); - } else { - isInComingCall = sharedPrefsHelper.get(Consts.EXTRA_IS_INCOMING_CALL, false); - } - if (!isInComingCall) { callService.playRingtone(); } @@ -163,6 +164,7 @@ private void bindCallService() { @Override protected void onActivityResult(int requestCode, int resultCode, final Intent data) { + super.onActivityResult(requestCode, resultCode, data); Log.i(TAG, "onActivityResult requestCode=" + requestCode + ", resultCode= " + resultCode); if (resultCode == Consts.EXTRA_LOGIN_RESULT_CODE) { if (data != null) { @@ -288,16 +290,6 @@ private void initIncomingCallTask() { showIncomingCallWindowTask = new Runnable() { @Override public void run() { - /*if (callService.currentSessionExist()) { - BaseSession.QBRTCSessionState currentSessionState = callService.getCurrentSessionState(); - if (QBRTCSession.QBRTCSessionState.QB_RTC_SESSION_NEW.equals(currentSessionState)) { - callService.rejectCurrentSession(new HashMap<>()); - } else { - callService.stopRingtone(); - hangUpCurrentSession(); - } - }*/ - // This is a fix to prevent call stop in case calling to user with more then one device logged in. ToastUtils.longToast("Call was stopped by UserNoActions timer"); callService.clearCallState(); callService.clearButtonsState(); @@ -316,7 +308,6 @@ public void hangUpCurrentSession() { } } - private void startIncomeCallTimer(long time) { Log.d(TAG, "startIncomeCallTimer"); showIncomingCallWindowTaskHandler.postAtTime(showIncomingCallWindowTask, SystemClock.uptimeMillis() + time); @@ -344,7 +335,6 @@ protected void onPause() { @Override public void finish() { - //Fix bug when user returns to call from service and the backstack doesn't have any screens CallService.stop(this); OpponentsActivity.start(this); super.finish(); @@ -391,7 +381,6 @@ public void run() { }); } - ////////////////////////////// ConnectionListener ////////////////////////////// private class ConnectionListenerImpl extends AbstractConnectionListener { @Override public void connectionClosedOnError(Exception e) { @@ -404,7 +393,6 @@ public void reconnectionSuccessful() { } } - ////////////////////////////// QBRTCSessionStateCallbackListener /////////////////////////// @Override public void onDisconnectedFromUser(QBRTCSession session, Integer userID) { Log.d(TAG, "Disconnected from user: " + userID); @@ -429,7 +417,6 @@ public void onStateChanged(QBRTCSession qbrtcSession, BaseSession.QBRTCSessionSt } - ////////////////////////////// QBRTCClientSessionCallbacks ////////////////////////////// @Override public void onUserNotAnswer(QBRTCSession session, Integer userID) { if (callService.isCurrentSession(session)) { @@ -448,10 +435,16 @@ public void onSessionStartClose(final QBRTCSession session) { @Override public void onReceiveHangUpFromUser(final QBRTCSession session, final Integer userID, Map map) { if (callService.isCurrentSession(session)) { - if (userID.equals(session.getCallerID())) { + int numberOpponents = session.getOpponents().size(); + if (numberOpponents == ONE_OPPONENT) { hangUpCurrentSession(); Log.d(TAG, "Initiator hung up the call"); + } else { + if (session.getState() == QB_RTC_SESSION_PENDING && userID.equals(session.getCallerID())) { + hangUpCurrentSession(); + } } + QBUser participant = dbManager.getUserById(userID); final String participantName = participant != null ? participant.getFullName() : String.valueOf(userID); ToastUtils.shortToast("User " + participantName + " " + getString(R.string.text_status_hang_up) + " conversation"); @@ -492,7 +485,6 @@ public void onCallRejectByUser(QBRTCSession session, Integer userID, Map()); } - ////////////////////////////// ConversationFragmentCallback //////////////////////////// @Override public void addConnectionListener(ConnectionListener connectionCallback) { callService.addConnectionListener(connectionCallback); @@ -538,12 +529,8 @@ public void onHangUpCurrentSession() { hangUpCurrentSession(); } - @TargetApi(21) @Override public void onStartScreenSharing() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - return; - } QBRTCScreenCapturer.requestPermissions(this); } @@ -604,6 +591,11 @@ public void startCall(Map userInfo) { callService.startCall(userInfo); } + @Override + public boolean isCameraFront() { + return callService.isCameraFront(); + } + @Override public boolean currentSessionExist() { return callService.currentSessionExist(); @@ -662,10 +654,9 @@ public QBRTCVideoTrack getVideoTrack(Integer userId) { @Override public void onStopPreview() { callService.stopScreenSharing(); - addConversationFragment(false); + addConversationFragment(isInComingCall); } - private void notifyCallStateListenersCallStarted() { for (CurrentCallStateCallback callback : currentCallStateCallbackList) { callback.onCallStarted(); @@ -701,7 +692,6 @@ public void onServiceConnected(ComponentName name, IBinder service) { CallService.CallServiceBinder binder = (CallService.CallServiceBinder) service; callService = binder.getService(); if (callService.currentSessionExist()) { - //we have already currentSession == null, so it's no reason to do further initialization if (QBChatService.getInstance().isLoggedIn()) { initScreen(); } else { diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/activities/LoginActivity.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/activities/LoginActivity.java index ed998fed8..4b659c839 100644 --- a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/activities/LoginActivity.java +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/activities/LoginActivity.java @@ -209,7 +209,7 @@ private void startLoginService(QBUser qbUser) { } private String getCurrentDeviceId() { - return Utils.generateDeviceId(this); + return Utils.generateDeviceId(); } private class LoginEditTextWatcher implements TextWatcher { 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 a5329d1b7..3cc458d55 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 @@ -59,6 +59,8 @@ public interface ConversationFragmentCallback { void startCall(Map userInfo); + boolean isCameraFront(); + boolean currentSessionExist(); List getOpponents(); diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/fragments/PreviewFragment.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/fragments/PreviewFragment.java index 065ccf0d1..dec8e388f 100644 --- a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/fragments/PreviewFragment.java +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/fragments/PreviewFragment.java @@ -1,23 +1,20 @@ package com.quickblox.sample.videochat.java.fragments; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.quickblox.sample.videochat.java.App; -import com.quickblox.sample.videochat.java.R; - -import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; -public class PreviewFragment extends Fragment { +import com.quickblox.sample.videochat.java.R; +public class PreviewFragment extends Fragment { public static final String PREVIEW_IMAGE = "preview_image"; public static Fragment newInstance(int imageResourceId) { @@ -26,20 +23,19 @@ public static Fragment newInstance(int imageResourceId) { bundle.putInt(PREVIEW_IMAGE, imageResourceId); previewFragment.setArguments(bundle); return previewFragment; - } @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_item_screen_share, container, false); - Glide.with(getActivity()) - .load(getArguments().getInt(PREVIEW_IMAGE)) - .diskCacheStrategy(DiskCacheStrategy.ALL) - .override((int) getResources().getDimension(R.dimen.pager_image_width), - (int) getResources().getDimension(R.dimen.pager_image_height)) - .into((ImageView) view.findViewById(R.id.image_preview)); + + if (getContext() != null && getArguments() != null) { + ImageView ivPreview = view.findViewById(R.id.image_preview); + int imageDrawable = getArguments().getInt(PREVIEW_IMAGE); + Drawable image = ContextCompat.getDrawable(getContext(), imageDrawable); + ivPreview.setImageDrawable(image); + } return view; } } \ 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 32e93a7a7..f1f718ef6 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 @@ -21,7 +21,14 @@ import android.widget.TextView; import android.widget.ToggleButton; +import androidx.annotation.DimenRes; +import androidx.annotation.NonNull; +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.OpponentsFromCallAdapter; import com.quickblox.sample.videochat.java.services.CallService; import com.quickblox.sample.videochat.java.utils.SharedPrefsHelper; @@ -38,6 +45,7 @@ import org.webrtc.CameraVideoCapturer; import org.webrtc.RendererCommon; import org.webrtc.SurfaceViewRenderer; +import org.webrtc.VideoSink; import java.io.Serializable; import java.util.ArrayList; @@ -47,22 +55,13 @@ import java.util.List; import java.util.Map; -import androidx.annotation.DimenRes; -import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import static android.widget.LinearLayout.HORIZONTAL; - - /** * QuickBlox team */ public class VideoConversationFragment extends BaseConversationFragment implements Serializable, QBRTCClientVideoTracksCallbacks, QBRTCSessionStateCallback, QBRTCSessionEventsCallback, OpponentsFromCallAdapter.OnAdapterEventListener { - private String TAG = VideoConversationFragment.class.getSimpleName(); + private final String TAG = VideoConversationFragment.class.getSimpleName(); public static final String CAMERA_ENABLED = "is_camera_enabled"; public static final String IS_CURRENT_CAMERA_FRONT = "is_camera_front"; @@ -94,12 +93,59 @@ public class VideoConversationFragment extends BaseConversationFragment implemen private boolean isCurrentCameraFront; private boolean isLocalVideoFullScreen; + @Override + int getFragmentLayout() { + return R.layout.fragment_video_conversation; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.i(TAG, "onCreate"); + setHasOptionsMenu(true); + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { parentView = super.onCreateView(inflater, container, savedInstanceState); return parentView; } + @Override + public void onStart() { + super.onStart(); + Log.i(TAG, "onStart"); + if (!allCallbacksInit) { + addListeners(); + allCallbacksInit = true; + } + } + + @Override + public void onResume() { + super.onResume(); + Log.d(TAG, "onResume"); + toggleCamera(cameraToggle.isChecked()); + } + + @Override + public void onPause() { + toggleCamera(false); + + if (connectionEstablished) { + allCallbacksInit = false; + } else { + Log.d(TAG, "We are in dialing process yet!"); + } + + releaseViewHolders(); + removeListeners(); + releaseViews(); + + SharedPrefsHelper.getInstance().save(IS_CURRENT_CAMERA_FRONT, isCurrentCameraFront); + super.onPause(); + } + @Override protected void configureOutgoingScreen() { Context context = getActivity(); @@ -121,11 +167,6 @@ protected void configureToolbar() { toolbar.setSubtitleTextColor(ContextCompat.getColor(getActivity(), R.color.white)); } - @Override - int getFragmentLayout() { - return R.layout.fragment_video_conversation; - } - @Override protected void initFields() { super.initFields(); @@ -171,34 +212,13 @@ private void removeListeners() { protected void actionButtonsEnabled(boolean enabled) { super.actionButtonsEnabled(enabled); cameraToggle.setEnabled(enabled); - // inactivate toggle buttons cameraToggle.setActivated(enabled); } - @Override - public void onStart() { - super.onStart(); - Log.i(TAG, "onStart"); - if (!allCallbacksInit) { - addListeners(); - allCallbacksInit = true; - } - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Log.i(TAG, "onCreate"); - setHasOptionsMenu(true); - } - @Override protected void initViews(View view) { super.initViews(view); Log.i(TAG, "initViews"); - if (view == null) { - return; - } opponentViewHolders = new SparseArray<>(opponents.size()); isRemoteShown = false; @@ -215,10 +235,9 @@ protected void initViews(View view) { recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), R.dimen.grid_item_divider)); recyclerView.setHasFixedSize(true); final int columnsCount = defineColumnsCount(); - LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), HORIZONTAL, false); + LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), RecyclerView.HORIZONTAL, false); recyclerView.setLayoutManager(layoutManager); - //for correct removing item in adapter recyclerView.setItemAnimator(null); recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override @@ -237,7 +256,8 @@ public void onGlobalLayout() { actionVideoButtonsLayout = (LinearLayout) view.findViewById(R.id.element_set_video_buttons); isCurrentCameraFront = SharedPrefsHelper.getInstance().get(IS_CURRENT_CAMERA_FRONT, true); - if (!isCurrentCameraFront) { + + if (conversationFragmentCallback.isCameraFront() != isCurrentCameraFront) { switchCamera(null); } @@ -315,40 +335,18 @@ private int defineColumnsCount() { return opponents.size() - 1; } - @Override - public void onResume() { - super.onResume(); - Log.d(TAG, "onResume"); - toggleCamera(cameraToggle.isChecked()); - } - - @Override - public void onPause() { - toggleCamera(false); - - if (connectionEstablished) { - allCallbacksInit = false; - } else { - Log.d(TAG, "We are in dialing process yet!"); - } - - releaseViewHolders(); - removeListeners(); - releaseViews(); - super.onPause(); - } - - @Override - public void onDetach() { - super.onDetach(); - Log.d(TAG, "onDetach"); - } - private void releaseViewHolders() { opponentViewHolders.clear(); } private void releaseViews() { + if (conversationFragmentCallback.getCurrentSessionState() != BaseSession.QBRTCSessionState.QB_RTC_SESSION_CLOSED) { + Map videoTrackMap = ((CallActivity) requireActivity()).getVideoTrackMap(); + for (QBRTCVideoTrack item : videoTrackMap.values()) { + VideoSink renderer = item.getRenderer(); + item.removeRenderer(renderer); + } + } if (localVideoView != null) { localVideoView.release(); } @@ -357,7 +355,7 @@ private void releaseViews() { } remoteFullScreenVideoView = null; if (!isPeerToPeerCall) { - releseOpponentsViews(); + releaseOpponentsViews(); } } @@ -376,7 +374,6 @@ protected void initButtonsListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { SharedPrefsHelper.getInstance().save(CAMERA_ENABLED, isChecked); toggleCamera(isChecked); - } }); } @@ -387,7 +384,6 @@ private void switchCamera(final MenuItem item) { conversationFragmentCallback.onSwitchCamera(new CameraVideoCapturer.CameraSwitchHandler() { @Override public void onCameraSwitchDone(boolean b) { - Log.d(TAG, "Camera Switched, isCameraFront = " + b); isCurrentCameraFront = b; SharedPrefsHelper.getInstance().save(IS_CURRENT_CAMERA_FRONT, b); if (item != null) { @@ -408,7 +404,7 @@ public void onCameraSwitchError(String s) { } private void updateSwitchCameraIcon(final MenuItem item) { - if (isCurrentCameraFront) { + if (conversationFragmentCallback.isCameraFront()) { Log.d(TAG, "CameraFront now!"); item.setIcon(R.drawable.ic_camera_front); } else { @@ -437,7 +433,6 @@ private void toggleCamera(boolean isNeedEnableCam) { } } - //////////////////////////// callbacks from QBRTCClientVideoTracksCallbacks /////////////////// @Override public void onLocalVideoTrackReceive(QBRTCSession qbrtcSession, final QBRTCVideoTrack videoTrack) { Log.d(TAG, "onLocalVideoTrackReceive() run"); @@ -471,13 +466,10 @@ public void run() { }, LOCAL_TRACK_INITIALIZE_DELAY); } } - ///////////////////////////////////////// end //////////////////////////////////////////// - //last opponent view is bind @Override public void OnBindLastViewHolder(final OpponentsFromCallAdapter.ViewHolder holder, final int position) { Log.i(TAG, "OnBindLastViewHolder position=" + position); - } @Override @@ -514,10 +506,8 @@ private void updateViewHolders(int position) { @SuppressWarnings("ConstantConditions") private void swapUsersFullscreenToPreview(int userId) { -// get opponentVideoTrack - opponent's video track from recyclerView QBRTCVideoTrack opponentVideoTrack = conversationFragmentCallback.getVideoTrackMap().get(userId); -// get mainVideoTrack - opponent's video track from full screen QBRTCVideoTrack mainVideoTrack = conversationFragmentCallback.getVideoTrackMap().get(userIDFullScreen); QBRTCSurfaceView remoteVideoView = findHolder(userId).getOpponentView(); @@ -532,7 +522,6 @@ private void swapUsersFullscreenToPreview(int userId) { } } - private void setRemoteViewMultiCall(int userID, QBRTCVideoTrack videoTrack) { Log.d(TAG, "setRemoteViewMultiCall fillVideoView"); final OpponentsFromCallAdapter.ViewHolder itemHolder = getViewHolderForOpponent(userID); @@ -541,17 +530,16 @@ private void setRemoteViewMultiCall(int userID, QBRTCVideoTrack videoTrack) { return; } final QBRTCSurfaceView remoteVideoView = itemHolder.getOpponentView(); - if (remoteVideoView != null) { remoteVideoView.setZOrderMediaOverlay(true); updateVideoView(remoteVideoView); - Log.d(TAG, "onRemoteVideoTrackReceive fillVideoView"); if (isRemoteShown) { Log.d(TAG, "onRemoteVideoTrackReceive User = " + userID); fillVideoView(remoteVideoView, videoTrack, true); } else { isRemoteShown = true; + itemHolder.getOpponentView().release(); opponentsAdapter.removeItem(itemHolder.getAdapterPosition()); setDuringCallActionBar(); setRecyclerViewVisibleState(); @@ -595,14 +583,13 @@ private OpponentsFromCallAdapter.ViewHolder findHolder(Integer userID) { return null; } - - private void releseOpponentsViews() { + private void releaseOpponentsViews() { RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); int childCount = layoutManager.getChildCount(); - Log.d(TAG, " releseOpponentsViews for " + childCount + " views"); + Log.d(TAG, " releaseOpponentsViews for " + childCount + " views"); for (int i = 0; i < childCount; i++) { View childView = layoutManager.getChildAt(i); - Log.d(TAG, " relese View for " + i + ", " + childView); + Log.d(TAG, " release View for " + i + ", " + childView); OpponentsFromCallAdapter.ViewHolder childViewHolder = (OpponentsFromCallAdapter.ViewHolder) recyclerView.getChildViewHolder(childView); childViewHolder.getOpponentView().release(); } @@ -692,11 +679,9 @@ private void setBackgroundOpponentView(final Integer userId) { } } - /////////////////////////////// QBRTCSessionConnectionCallbacks /////////////////////////// - @Override public void onStateChanged(QBRTCSession qbrtcSession, BaseSession.QBRTCSessionState qbrtcSessionState) { - + // empty } @Override @@ -722,9 +707,6 @@ public void onDisconnectedFromUser(QBRTCSession qbrtcSession, Integer integer) { } } - ////////////////////////////////// end ////////////////////////////////////////// - - /////////////////// Callbacks from CallActivity.QBRTCSessionUserCallback ////////////////////// @Override public void onUserNotAnswer(QBRTCSession session, Integer userId) { setProgressBarForOpponentGone(userId); @@ -755,31 +737,33 @@ public void onReceiveHangUpFromUser(QBRTCSession session, Integer userId, Map newUsers) { } private void updateAllOpponentsList(ArrayList newUsers) { - for (int i = 0; i < allOpponents.size(); i++) { for (QBUser updatedUser : newUsers) { if (updatedUser.equals(allOpponents.get(i))) { @@ -836,7 +820,6 @@ public void onCallTimeUpdate(String time) { } private void runUpdateUsersNames(final ArrayList newUsers) { - //need delayed for synchronization with recycler view initialization mainHandler.postDelayed(new Runnable() { @Override public void run() { @@ -849,8 +832,7 @@ public void run() { } class DividerItemDecoration extends RecyclerView.ItemDecoration { - - private int space; + private final int space; public DividerItemDecoration(@NonNull Context context, @DimenRes int dimensionDivider) { this.space = context.getResources().getDimensionPixelSize(dimensionDivider); diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/services/CallService.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/services/CallService.java index 9fd9d9ef6..617792328 100644 --- a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/services/CallService.java +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/services/CallService.java @@ -17,6 +17,10 @@ import android.text.TextUtils; import android.util.Log; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.core.app.NotificationCompat; + import com.quickblox.chat.QBChatService; import com.quickblox.sample.videochat.java.R; import com.quickblox.sample.videochat.java.activities.CallActivity; @@ -56,17 +60,12 @@ import org.webrtc.VideoSink; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.TimerTask; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.core.app.NotificationCompat; - import static com.quickblox.sample.videochat.java.utils.Consts.MAX_OPPONENTS_COUNT; public class CallService extends Service { @@ -75,6 +74,7 @@ public class CallService extends Service { private static final int SERVICE_ID = 787; private static final String CHANNEL_ID = "Quickblox channel"; private static final String CHANNEL_NAME = "Quickblox background service"; + public static final int ONE_OPPONENT = 1; private HashMap videoTrackMap = new HashMap<>(); private CallServiceBinder callServiceBinder = new CallServiceBinder(); @@ -132,7 +132,6 @@ public void onDestroy() { super.onDestroy(); networkConnectionChecker.unregisterListener(networkConnectionListener); removeConnectionListener(connectionListener); - removeVideoTrackRenders(); releaseCurrentSession(); releaseAudioManager(); @@ -157,8 +156,12 @@ private Notification initNotification() { Intent notifyIntent = new Intent(this, CallActivity.class); notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + int intentFlag = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + intentFlag = PendingIntent.FLAG_IMMUTABLE; + } PendingIntent notifyPendingIntent = PendingIntent.getActivity(this, 0, - notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT); + notifyIntent, intentFlag); String notificationTitle = getString(R.string.notification_title); String notificationText = getString(R.string.notification_text, ""); @@ -480,6 +483,14 @@ public void switchCamera(CameraVideoCapturer.CameraSwitchHandler cameraSwitchHan } } + public boolean isCameraFront() { + if (currentSession != null && currentSession.getMediaStreamManager() != null) { + QBRTCCameraVideoCapturer videoCapturer = (QBRTCCameraVideoCapturer) currentSession.getMediaStreamManager().getVideoCapturer(); + return videoCapturer.getCameraName().contains("front"); + } + return true; + } + public void switchAudio() { Log.v(TAG, "onSwitchAudio(), SelectedAudioDevice() = " + appRTCAudioManager.getSelectedAudioDevice()); if (appRTCAudioManager.getSelectedAudioDevice() != AppRTCAudioManager.AudioDevice.SPEAKER_PHONE) { @@ -516,27 +527,12 @@ public QBRTCVideoTrack getVideoTrack(Integer userId) { } private void removeVideoTrack(int userId) { - videoTrackMap.remove(userId); - } - - private void removeVideoTrackRenders() { - Log.d(TAG, "removeVideoTrackRenders"); - if (!videoTrackMap.isEmpty()) { - Iterator> entryIterator = videoTrackMap.entrySet().iterator(); - while (entryIterator.hasNext()) { - Map.Entry entry = entryIterator.next(); - Integer userId = (Integer) entry.getKey(); - QBRTCVideoTrack videoTrack = (QBRTCVideoTrack) entry.getValue(); - QBUser qbUser = QBChatService.getInstance().getUser(); - boolean remoteVideoTrack = !userId.equals(qbUser.getId()); - if (remoteVideoTrack) { - VideoSink renderer = videoTrack.getRenderer(); - if (renderer != null) { - videoTrack.removeRenderer(renderer); - } - } - } + QBRTCVideoTrack videoTrack = getVideoTrack(userId); + if (videoTrack != null) { + VideoSink renderer = videoTrack.getRenderer(); + videoTrack.removeRenderer(renderer); } + videoTrackMap.remove(userId); } public void setCallTimerCallback(CallTimerListener callback) { @@ -648,8 +644,14 @@ public void onReceiveHangUpFromUser(QBRTCSession session, Integer userID, Map()); + + int numberOpponents = session.getOpponents().size(); + if (numberOpponents == ONE_OPPONENT) { + if (userID.equals(session.getCallerID()) && currentSession != null) { + currentSession.hangUp(new HashMap<>()); + } + } else { + removeVideoTrack(userID); } QBUser participant = QbUsersDbManager.getInstance(getApplicationContext()).getUserById(userID); diff --git a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/util/ChatPingAlarmManager.java b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/util/ChatPingAlarmManager.java index cb5cfe6fb..e1438a3b9 100644 --- a/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/util/ChatPingAlarmManager.java +++ b/sample-videochat-java/app/src/main/java/com/quickblox/sample/videochat/java/util/ChatPingAlarmManager.java @@ -7,6 +7,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.Build; import android.os.Bundle; import android.os.SystemClock; import android.util.Log; @@ -94,7 +95,13 @@ public static void onCreate(Context context) { sContext = context; context.registerReceiver(ALARM_BROADCAST_RECEIVER, new IntentFilter(PING_ALARM_ACTION)); sAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - sPendingIntent = PendingIntent.getBroadcast(context, 0, new Intent(PING_ALARM_ACTION), 0); + + int intentFlag = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + intentFlag = PendingIntent.FLAG_IMMUTABLE; + } + + sPendingIntent = PendingIntent.getBroadcast(context, 0, new Intent(PING_ALARM_ACTION), intentFlag); sAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + PING_INTERVAL, PING_INTERVAL, sPendingIntent); 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 640827caf..cebb036b0 100755 --- a/sample-videochat-java/app/src/main/res/values/strings.xml +++ b/sample-videochat-java/app/src/main/res/values/strings.xml @@ -172,7 +172,7 @@ answer_time_interval Answer Time Interval (15–90s) - 60 + 30 15 90 5 diff --git a/sample-videochat-java/build.gradle b/sample-videochat-java/build.gradle index 3f6ada2f2..a29dafa35 100644 --- a/sample-videochat-java/build.gradle +++ b/sample-videochat-java/build.gradle @@ -1,22 +1,19 @@ buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.0' - classpath 'com.google.gms:google-services:4.1.0' + classpath 'com.android.tools.build:gradle:4.2.2' + classpath 'com.google.gms:google-services:4.3.10' } } allprojects { repositories { google() - jcenter() - maven { - url "https://github.com/QuickBlox/quickblox-android-sdk-releases/raw/master/" - } + mavenCentral() } } @@ -26,31 +23,17 @@ ext { lintAbortOnError = false // QuickBlox SDK version - qbSdkVersion = '3.9.2' + qbSdkVersion = '3.9.15' //Firebase - firebaseCoreVersion = '16.0.8' - - //Glide - glideVersion = '3.6.1' + firebaseCoreVersion = '21.0.0' //Material - materialVersion = '1.0.0' + materialVersion = '1.6.0' //RobotoTextView robotoTextViewVersion = '4.0.0' //Pull to refresh pullToRefreshVersion = '3.2.3' - - //Android X - fragmentAndroidXVersion = '1.0.0' - lifecycleViewmodelAndroidXVersion = '1.0.0' - coreKtxVersion = '1.0.1' - - //Crashlytics - crashlyticsVersion = '2.9.5' - - //Fabric - fabricToolsVersion = '1.27.0' } \ No newline at end of file diff --git a/sample-videochat-java/gradle/wrapper/gradle-wrapper.jar b/sample-videochat-java/gradle/wrapper/gradle-wrapper.jar index f6b961fd5a86aa5fbfe90f707c3138408be7c718..758de960ec7947253b058ff79c88ce51f3abe08a 100644 GIT binary patch delta 7592 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 delta 7527 zcmZ9R1x#F9_qSnicXulc?yf}(6dk0vyZc~+d$GaY-3n72TD-Wng<{36#a;jXnwxvy zd?z`{^Q_;>$vQhHImzBB)d(F`2+2ZdEN_QzrQqP;Kq=DNDA{KoXCCly$>bke%{kEW zYe{bxkm2A2p|qUrkN`qGvS9UV-f^1{Tmv^lyIF-rb}Woy4YW{nG-ugNX^Pi~mfp=` zPROtLj()Lc)?7ukwK~-5mOJ!-;(e=AnFyVa>VMqFzl40c*SoDc5o*a@b;>~91z+ch ztOsV^1g?v%i+~^28+(z>D4ts}4Nu!KY0@@ic}aOyN0Zg*A@O0ze6fgX4lJ)y8u7V^f&}%mO4uz`^vA6Lw-Qo5uGH1}vU$a+^|3w-WK=eF_MX>O$GLV)wcAcp zh*T6(L7XBKYUMaiM49U?hWO;vi-q?5hn!~l&|9-5j>yVW53HH{!By|ludBatTh1%I zB<5SDlSLkLXE_TG{URmGqsQ8OhUpwH)igC2r{Pupf>5+__=jg$-USeGmSt)gWkvYZ z-kZ4kYsYKeP;19vcOcmWSx| z+-U`!P#CBplekzlnu)n_x@Iuiyc^K#h`e_RucIjtwccf#Yv!rlCk-Ad{T>ugl(U)K zcNe#pomfa^@rF;Nj;3tB|G|i){vJ0%uGh6cD z2FMo4ZF!>UMst*&*CY9-S$Cus(VF%ebF~qH?wXCx#Pzts_0RRM+ zYv5Sj6g+ByZ#7_5Adc?4B8=U)D=~kH5C>uV<*=;qLoh_%)P#|P$_zYvnM4X;V@w{t zs^YP1ookyt9BdnCtmj&-sAR|gdMh&!aS}xz88G+T-@wuH5H>b&HHYMO2Og>Si1B1{ zKh9cyP>h=J)WnrLcY9Tl!|2?1o*i|FZvjWf|hn zJA~1%+!E@OTjXpyEc&CBS#OGiI2^{wB3mELt`oI@mCrNMI)1pa*3dLUp8SAx{&tqRVFF1ZJ{z$z>2gi} zTXE63p5N1T$3t2kHa}RBa`yJ=4-p~SRJ%cdmJ?kwkEC3eW{imSw-;xNUMxcTHA0OP zGd`#=MOT0UpQp>NN+Zf55Z>oyU9Z$e?`GYDyqqkm$9qAXjo#^Jkihuzlu08}ES|vm^XWsf4)4S3EMyPmjw~ano-X zU9?8uhiY_7Z9;jciUT!{C0Ku zM)M%VYP7tV-{aX%1+kEQPPE;irmt&z0_?tCgmy-iiVx!3o&u(fL2|eB zT%0gk7fXpV#$KfR8bs+Ra?S8o?Kgd)ht(V-7(^RgF

>O|5`!Y@WVeHnjeM=H)Z) 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 \(.*\)$'` + 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-videochat-java/gradlew.bat b/sample-videochat-java/gradlew.bat index aec99730b..e95643d6a 100644 --- a/sample-videochat-java/gradlew.bat +++ b/sample-videochat-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 diff --git a/sample-videochat-java/proguard-rules.pro b/sample-videochat-java/proguard-rules.pro deleted file mode 100755 index f1dd7da4b..000000000 --- a/sample-videochat-java/proguard-rules.pro +++ /dev/null @@ -1,70 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /home/tereha/Android/sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} -#-dontusemixedcaseclassnames -#-dontskipnonpubliclibraryclasses -#-verbose -# - -##---------------Begin: proguard configuration for Gson ---------- -# Gson uses generic type information stored in a class file when working with fields. Proguard -# removes such information by default, so configure it to keep all of it. --keepattributes Signature - -# For using GSON @Expose annotation --keepattributes *Annotation* - -# Gson specific classes --keep class sun.misc.Unsafe { *; } -#-keep class com.google.gson.stream.** { *; } - -# Application classes that will be serialized/deserialized over Gson - -keep class com.quickblox.core.account.model.** { *; } - - -##---------------End: proguard configuration for Gson ---------- -#quickblox sample chat - --keep class com.quickblox.auth.parsers.** { *; } --keep class com.quickblox.auth.model.** { *; } --keep class com.quickblox.core.parser.** { *; } --keep class com.quickblox.core.model.** { *; } --keep class com.quickblox.core.server.** { *; } --keep class com.quickblox.core.rest.** { *; } --keep class com.quickblox.core.error.** { *; } --keep class com.quickblox.core.Query { *; } - --keep class com.quickblox.users.parsers.** { *; } --keep class com.quickblox.users.model.** { *; } - --keep class com.quickblox.chat.parser.** { *; } --keep class com.quickblox.chat.model.** { *; } - --keep class com.quickblox.messages.parsers.** { *; } --keep class com.quickblox.messages.model.** { *; } - --keep class com.quickblox.content.parsers.** { *; } --keep class com.quickblox.content.model.** { *; } - --keep class org.jivesoftware.** { *; } - -#sample chat --keep class android.support.v7.** { *; } --keep class com.bumptech.** { *; } - --dontwarn org.jivesoftware.smackx.** --dontwarn android.support.v4.app.** \ No newline at end of file