From f5468657d81dfd441ded6ad1b15a8243be168125 Mon Sep 17 00:00:00 2001 From: Mario Danic Date: Mon, 28 Oct 2019 12:25:07 +0100 Subject: [PATCH] Fix notifications Signed-off-by: Mario Danic --- .../talk/activities/MagicCallActivity.kt | 2 +- .../nextcloud/talk/activities/MainActivity.kt | 2 +- .../talk/controllers/CallController.java | 5 + .../CallNotificationController.java | 495 ------------------ .../controllers/CallNotificationController.kt | 473 +++++++++++++++++ .../talk/dagger/modules/RestModule.java | 2 +- .../nextcloud/talk/jobs/NotificationWorker.kt | 254 +++++---- .../talk/newarch/di/module/NetworkModule.kt | 3 +- .../com/nextcloud/talk/utils/DisplayUtils.kt | 2 +- .../layout/controller_call_notification.xml | 3 +- 10 files changed, 611 insertions(+), 630 deletions(-) delete mode 100644 app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java create mode 100644 app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.kt diff --git a/app/src/main/java/com/nextcloud/talk/activities/MagicCallActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MagicCallActivity.kt index 2fb0a27e9..a76854c10 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/MagicCallActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/MagicCallActivity.kt @@ -70,7 +70,7 @@ class MagicCallActivity : BaseActivity() { if (!router!!.hasRootController()) { if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) { router!!.setRoot( - RouterTransaction.with(CallNotificationController(intent.extras)) + RouterTransaction.with(CallNotificationController(intent.extras!!)) .pushChangeHandler(HorizontalChangeHandler()) .popChangeHandler(HorizontalChangeHandler()) ) diff --git a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt index 269d83c1c..b3150ff9b 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt @@ -154,7 +154,7 @@ class MainActivity : BaseActivity(), ActionBarProvider { if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) { if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) { router!!.pushController( - RouterTransaction.with(CallNotificationController(intent.extras)) + RouterTransaction.with(CallNotificationController(intent.extras!!)) .pushChangeHandler(HorizontalChangeHandler()) .popChangeHandler(HorizontalChangeHandler()) ) diff --git a/app/src/main/java/com/nextcloud/talk/controllers/CallController.java b/app/src/main/java/com/nextcloud/talk/controllers/CallController.java index 0eb991dfb..b3218d1ad 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/CallController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/CallController.java @@ -106,6 +106,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import java.io.IOException; +import java.net.CookieManager; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -203,6 +204,8 @@ public class CallController extends BaseController { @Inject AppPreferences appPreferences; @Inject + CookieManager cookieManager; + @Inject EventBus eventBus; private PeerConnectionFactory peerConnectionFactory; @@ -1747,6 +1750,8 @@ public class CallController extends BaseController { } setPipVideoViewDimensions(); + + cookieManager.getCookieStore().removeAll(); } private void setPipVideoViewDimensions() { diff --git a/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java b/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java deleted file mode 100644 index 93c277f22..000000000 --- a/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java +++ /dev/null @@ -1,495 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017-2018 Mario Danic - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.controllers; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; -import android.media.AudioAttributes; -import android.media.MediaPlayer; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.VibrationEffect; -import android.os.Vibrator; -import android.renderscript.RenderScript; -import android.text.TextUtils; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.RelativeLayout; -import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.constraintlayout.widget.ConstraintLayout; -import autodagger.AutoInjector; -import butterknife.BindView; -import butterknife.OnClick; -import com.bluelinelabs.conductor.RouterTransaction; -import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler; -import com.bluelinelabs.logansquare.LoganSquare; -import com.facebook.common.executors.UiThreadImmediateExecutorService; -import com.facebook.common.references.CloseableReference; -import com.facebook.datasource.DataSource; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.drawee.view.SimpleDraweeView; -import com.facebook.imagepipeline.core.ImagePipeline; -import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber; -import com.facebook.imagepipeline.image.CloseableImage; -import com.facebook.imagepipeline.postprocessors.BlurPostProcessor; -import com.facebook.imagepipeline.request.ImageRequest; -import com.nextcloud.talk.R; -import com.nextcloud.talk.api.NcApi; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.controllers.base.BaseController; -import com.nextcloud.talk.events.ConfigurationChangeEvent; -import com.nextcloud.talk.models.RingtoneSettings; -import com.nextcloud.talk.models.database.UserEntity; -import com.nextcloud.talk.models.json.conversations.Conversation; -import com.nextcloud.talk.models.json.conversations.RoomsOverall; -import com.nextcloud.talk.models.json.participants.Participant; -import com.nextcloud.talk.models.json.participants.ParticipantsOverall; -import com.nextcloud.talk.utils.ApiUtils; -import com.nextcloud.talk.utils.DisplayUtils; -import com.nextcloud.talk.utils.DoNotDisturbUtils; -import com.nextcloud.talk.utils.bundle.BundleKeys; -import com.nextcloud.talk.utils.preferences.AppPreferences; -import com.nextcloud.talk.utils.singletons.AvatarStatusCodeHolder; -import com.uber.autodispose.AutoDispose; -import io.reactivex.Observer; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; -import java.io.IOException; -import java.util.List; -import javax.annotation.Nullable; -import javax.inject.Inject; -import okhttp3.Cache; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; -import org.michaelevans.colorart.library.ColorArt; -import org.parceler.Parcels; - -@AutoInjector(NextcloudTalkApplication.class) -public class CallNotificationController extends BaseController { - - private static final String TAG = "CallNotificationController"; - - @Inject - NcApi ncApi; - - @Inject - AppPreferences appPreferences; - - @Inject - EventBus eventBus; - - @Inject - Context context; - - @BindView(R.id.conversationNameTextView) - TextView conversationNameTextView; - - @BindView(R.id.avatarImageView) - SimpleDraweeView avatarImageView; - - @BindView(R.id.callAnswerVoiceOnlyView) - SimpleDraweeView callAnswerVoiceOnlyView; - - @BindView(R.id.callAnswerCameraView) - SimpleDraweeView callAnswerCameraView; - - @BindView(R.id.backgroundImageView) - ImageView backgroundImageView; - - @BindView(R.id.incomingTextRelativeLayout) - RelativeLayout incomingTextRelativeLayout; - - private Bundle originalBundle; - private String roomId; - private UserEntity userBeingCalled; - private String credentials; - private Conversation currentConversation; - private MediaPlayer mediaPlayer; - private boolean leavingScreen = false; - private Vibrator vibrator; - private Handler handler; - - public CallNotificationController(Bundle args) { - super(); - NextcloudTalkApplication.Companion.getSharedApplication() - .getComponentApplication() - .inject(this); - - this.roomId = args.getString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), ""); - this.currentConversation = - Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_ROOM())); - this.userBeingCalled = args.getParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY()); - - this.originalBundle = args; - credentials = - ApiUtils.getCredentials(userBeingCalled.getUsername(), userBeingCalled.getToken()); - } - - @Override - protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { - return inflater.inflate(R.layout.controller_call_notification, container, false); - } - - @Override - protected void onDetach(@NonNull View view) { - eventBus.unregister(this); - super.onDetach(view); - } - - @Override - protected void onAttach(@NonNull View view) { - super.onAttach(view); - eventBus.register(this); - } - - private void showAnswerControls() { - callAnswerCameraView.setVisibility(View.VISIBLE); - callAnswerVoiceOnlyView.setVisibility(View.VISIBLE); - } - - @OnClick(R.id.callControlHangupView) - void hangup() { - leavingScreen = true; - - if (getActivity() != null) { - getActivity().finish(); - } - } - - @OnClick(R.id.callAnswerCameraView) - void answerWithCamera() { - originalBundle.putBoolean(BundleKeys.INSTANCE.getKEY_CALL_VOICE_ONLY(), false); - proceedToCall(); - } - - @OnClick(R.id.callAnswerVoiceOnlyView) - void answerVoiceOnly() { - originalBundle.putBoolean(BundleKeys.INSTANCE.getKEY_CALL_VOICE_ONLY(), true); - proceedToCall(); - } - - private void proceedToCall() { - originalBundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), - currentConversation.getToken()); - - getRouter().replaceTopController(RouterTransaction.with(new CallController(originalBundle)) - .popChangeHandler(new HorizontalChangeHandler()) - .pushChangeHandler(new HorizontalChangeHandler())); - } - - private void checkIfAnyParticipantsRemainInRoom() { - ncApi.getPeersForCall(credentials, ApiUtils.getUrlForParticipants(userBeingCalled.getBaseUrl(), - currentConversation.getToken())) - .subscribeOn(Schedulers.io()) - .takeWhile(observable -> !leavingScreen) - .retry(3) - .as(AutoDispose.autoDisposable(getScopeProvider())) - .subscribe(new Observer() { - @Override - public void onSubscribe(Disposable d) { - } - - @Override - public void onNext(ParticipantsOverall participantsOverall) { - boolean hasParticipantsInCall = false; - boolean inCallOnDifferentDevice = false; - List participantList = participantsOverall.getOcs().getData(); - for (Participant participant : participantList) { - if (participant.getParticipantFlags() != Participant.ParticipantFlags.NOT_IN_CALL) { - hasParticipantsInCall = true; - - if (participant.getUserId().equals(userBeingCalled.getUserId())) { - inCallOnDifferentDevice = true; - break; - } - } - } - - if (!hasParticipantsInCall || inCallOnDifferentDevice) { - if (getActivity() != null) { - getActivity().runOnUiThread(() -> hangup()); - } - } - } - - @Override - public void onError(Throwable e) { - - } - - @Override - public void onComplete() { - if (!leavingScreen) { - checkIfAnyParticipantsRemainInRoom(); - } - } - }); - } - - private void handleFromNotification() { - ncApi.getRooms(credentials, ApiUtils.getUrlForGetRooms(userBeingCalled.getBaseUrl())) - .subscribeOn(Schedulers.io()) - .retry(3) - .observeOn(AndroidSchedulers.mainThread()) - .as(AutoDispose.autoDisposable(getScopeProvider())) - .subscribe(new Observer() { - @Override - public void onSubscribe(Disposable d) { - } - - @Override - public void onNext(RoomsOverall roomsOverall) { - for (Conversation conversation : roomsOverall.getOcs().getData()) { - if (roomId.equals(conversation.getConversationId())) { - currentConversation = conversation; - runAllThings(); - break; - } - } - } - - @Override - public void onError(Throwable e) { - - } - - @Override - public void onComplete() { - - } - }); - } - - private void runAllThings() { - if (conversationNameTextView != null) { - conversationNameTextView.setText(currentConversation.getDisplayName()); - } - - loadAvatar(); - checkIfAnyParticipantsRemainInRoom(); - showAnswerControls(); - } - - @SuppressLint("LongLogTag") - @Override - protected void onViewBound(@NonNull View view) { - super.onViewBound(view); - - if (handler == null) { - handler = new Handler(); - } - - if (currentConversation == null) { - handleFromNotification(); - } else { - runAllThings(); - } - - if (DoNotDisturbUtils.INSTANCE.shouldPlaySound()) { - String callRingtonePreferenceString = appPreferences.getCallRingtoneUri(); - Uri ringtoneUri; - - if (TextUtils.isEmpty(callRingtonePreferenceString)) { - // play default sound - ringtoneUri = Uri.parse("android.resource://" + getApplicationContext().getPackageName() + - "/raw/librem_by_feandesign_call"); - } else { - try { - RingtoneSettings ringtoneSettings = - LoganSquare.parse(callRingtonePreferenceString, RingtoneSettings.class); - ringtoneUri = ringtoneSettings.getRingtoneUri(); - } catch (IOException e) { - Log.e(TAG, "Failed to parse ringtone settings"); - ringtoneUri = Uri.parse("android.resource://" + getApplicationContext().getPackageName() + - "/raw/librem_by_feandesign_call"); - } - } - - if (ringtoneUri != null && getActivity() != null) { - mediaPlayer = new MediaPlayer(); - try { - mediaPlayer.setDataSource(getActivity(), ringtoneUri); - - mediaPlayer.setLooping(true); - AudioAttributes audioAttributes = - new AudioAttributes.Builder().setContentType(AudioAttributes - .CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) - .build(); - mediaPlayer.setAudioAttributes(audioAttributes); - - mediaPlayer.setOnPreparedListener(mp -> mediaPlayer.start()); - - mediaPlayer.prepareAsync(); - } catch (IOException e) { - Log.e(TAG, "Failed to set data source"); - } - } - } - - if (DoNotDisturbUtils.INSTANCE.shouldVibrate(appPreferences.getShouldVibrateSetting())) { - vibrator = (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE); - - if (vibrator != null) { - long[] vibratePattern = new long[] { 0, 400, 800, 600, 800, 800, 800, 1000 }; - int[] amplitudes = new int[] { 0, 255, 0, 255, 0, 255, 0, 255 }; - - VibrationEffect vibrationEffect; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - if (vibrator.hasAmplitudeControl()) { - vibrationEffect = VibrationEffect.createWaveform(vibratePattern, amplitudes, -1); - vibrator.vibrate(vibrationEffect); - } else { - vibrationEffect = VibrationEffect.createWaveform(vibratePattern, -1); - vibrator.vibrate(vibrationEffect); - } - } else { - vibrator.vibrate(vibratePattern, -1); - } - } - - handler.postDelayed(() -> { - if (vibrator != null) { - vibrator.cancel(); - } - }, 10000); - } - } - - @Subscribe(threadMode = ThreadMode.MAIN) - public void onMessageEvent(ConfigurationChangeEvent configurationChangeEvent) { - ConstraintLayout.LayoutParams layoutParams = - (ConstraintLayout.LayoutParams) avatarImageView.getLayoutParams(); - int dimen = (int) getResources().getDimension(R.dimen.avatar_size_very_big); - - layoutParams.width = dimen; - layoutParams.height = dimen; - avatarImageView.setLayoutParams(layoutParams); - } - - private void loadAvatar() { - switch (currentConversation.getType()) { - case ONE_TO_ONE_CONVERSATION: - avatarImageView.setVisibility(View.VISIBLE); - - ImageRequest imageRequest = - DisplayUtils.INSTANCE.getImageRequestForUrl( - ApiUtils.getUrlForAvatarWithName(userBeingCalled.getBaseUrl(), - currentConversation.getName(), R.dimen.avatar_size_very_big), null); - - ImagePipeline imagePipeline = Fresco.getImagePipeline(); - DataSource> dataSource = - imagePipeline.fetchDecodedImage(imageRequest, null); - - dataSource.subscribe(new BaseBitmapDataSubscriber() { - @Override - protected void onNewResultImpl(@Nullable Bitmap bitmap) { - if (avatarImageView != null) { - avatarImageView.getHierarchy().setImage(new BitmapDrawable(bitmap), 100, - true); - - if (getResources() != null) { - incomingTextRelativeLayout.setBackground(getResources().getDrawable(R.drawable - .incoming_gradient)); - } - - if ((AvatarStatusCodeHolder.getInstance().getStatusCode() == 200 - || AvatarStatusCodeHolder.getInstance().getStatusCode() == 0) && - userBeingCalled.hasSpreedFeatureCapability("no-ping")) { - if (getActivity() != null) { - Bitmap backgroundBitmap = bitmap.copy(bitmap.getConfig(), true); - new BlurPostProcessor(5, getActivity()).process(backgroundBitmap); - backgroundImageView.setImageDrawable(new BitmapDrawable(backgroundBitmap)); - } - } else if (AvatarStatusCodeHolder.getInstance().getStatusCode() == 201) { - ColorArt colorArt = new ColorArt(bitmap); - int color = colorArt.getBackgroundColor(); - - float[] hsv = new float[3]; - Color.colorToHSV(color, hsv); - hsv[2] *= 0.75f; - color = Color.HSVToColor(hsv); - - backgroundImageView.setImageDrawable(new ColorDrawable(color)); - } - } - } - - @Override - protected void onFailureImpl(DataSource> dataSource) { - } - }, UiThreadImmediateExecutorService.getInstance()); - - break; - case GROUP_CONVERSATION: - avatarImageView.getHierarchy() - .setImage(DisplayUtils.INSTANCE.getRoundedDrawable( - context.getDrawable(R.drawable.ic_people_group_white_24px)) - , 100, true); - case PUBLIC_CONVERSATION: - avatarImageView.getHierarchy() - .setImage(DisplayUtils.INSTANCE.getRoundedDrawable( - context.getDrawable(R.drawable.ic_people_group_white_24px)) - , 100, true); - break; - default: - } - } - - private void endMediaAndVibratorNotifications() { - if (mediaPlayer != null) { - if (mediaPlayer.isPlaying()) { - mediaPlayer.stop(); - } - - mediaPlayer.release(); - mediaPlayer = null; - } - - if (vibrator != null) { - vibrator.cancel(); - } - } - - @Override - public void onDestroy() { - AvatarStatusCodeHolder.getInstance().setStatusCode(0); - leavingScreen = true; - if (handler != null) { - handler.removeCallbacksAndMessages(null); - handler = null; - } - endMediaAndVibratorNotifications(); - super.onDestroy(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.kt b/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.kt new file mode 100644 index 000000000..45bd9e525 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.kt @@ -0,0 +1,473 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.controllers + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.ColorDrawable +import android.media.AudioAttributes +import android.media.MediaPlayer +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.os.VibrationEffect +import android.os.Vibrator +import android.text.TextUtils +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.RelativeLayout +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import autodagger.AutoInjector +import butterknife.BindView +import butterknife.OnClick +import coil.api.load +import coil.bitmappool.BitmapPool +import coil.drawable.CrossfadeDrawable +import coil.transform.BlurTransformation +import coil.transform.CircleCropTransformation +import com.bluelinelabs.conductor.RouterTransaction +import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler +import com.bluelinelabs.logansquare.LoganSquare +import com.nextcloud.talk.R +import com.nextcloud.talk.api.NcApi +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.controllers.base.BaseController +import com.nextcloud.talk.events.ConfigurationChangeEvent +import com.nextcloud.talk.models.RingtoneSettings +import com.nextcloud.talk.models.database.UserEntity +import com.nextcloud.talk.models.json.conversations.Conversation +import com.nextcloud.talk.models.json.conversations.RoomsOverall +import com.nextcloud.talk.models.json.participants.Participant +import com.nextcloud.talk.models.json.participants.ParticipantsOverall +import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.DoNotDisturbUtils +import com.nextcloud.talk.utils.bundle.BundleKeys +import com.nextcloud.talk.utils.singletons.AvatarStatusCodeHolder +import com.uber.autodispose.AutoDispose +import io.reactivex.Observer +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import org.michaelevans.colorart.library.ColorArt +import org.parceler.Parcels +import java.io.IOException +import javax.inject.Inject + +@AutoInjector(NextcloudTalkApplication::class) +class CallNotificationController(private val originalBundle: Bundle) : BaseController() { + + @JvmField + @Inject + internal var ncApi: NcApi? = null + + @JvmField + @BindView(R.id.conversationNameTextView) + var conversationNameTextView: TextView? = null + + @JvmField + @BindView(R.id.avatarImageView) + var avatarImageView: ImageView? = null + + @JvmField + @BindView(R.id.callAnswerVoiceOnlyView) + var callAnswerVoiceOnlyView: ImageView? = null + + @JvmField + @BindView(R.id.callAnswerCameraView) + var callAnswerCameraView: ImageView? = null + + @JvmField + @BindView(R.id.backgroundImageView) + var backgroundImageView: ImageView? = null + + @JvmField + @BindView(R.id.incomingTextRelativeLayout) + var incomingTextRelativeLayout: RelativeLayout? = null + private val roomId: String + private val userBeingCalled: UserEntity? + private val credentials: String? + private var currentConversation: Conversation? = null + private var mediaPlayer: MediaPlayer? = null + private var leavingScreen = false + private var vibrator: Vibrator? = null + private var handler: Handler? = null + + init { + NextcloudTalkApplication.sharedApplication!! + .componentApplication + .inject(this) + + this.roomId = originalBundle.getString(BundleKeys.KEY_ROOM_ID, "") + this.currentConversation = Parcels.unwrap(originalBundle.getParcelable(BundleKeys.KEY_ROOM)) + this.userBeingCalled = originalBundle.getParcelable(BundleKeys.KEY_USER_ENTITY) + credentials = ApiUtils.getCredentials(userBeingCalled!!.username, userBeingCalled.token) + } + + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { + return inflater.inflate(R.layout.controller_call_notification, container, false) + } + + override fun onDetach(view: View) { + eventBus.unregister(this) + super.onDetach(view) + } + + override fun onAttach(view: View) { + super.onAttach(view) + eventBus.register(this) + } + + private fun showAnswerControls() { + callAnswerCameraView!!.visibility = View.VISIBLE + callAnswerVoiceOnlyView!!.visibility = View.VISIBLE + } + + @OnClick(R.id.callControlHangupView) + internal fun hangup() { + leavingScreen = true + + if (activity != null) { + activity!!.finish() + } + } + + @OnClick(R.id.callAnswerCameraView) + internal fun answerWithCamera() { + originalBundle.putBoolean(BundleKeys.KEY_CALL_VOICE_ONLY, false) + proceedToCall() + } + + @OnClick(R.id.callAnswerVoiceOnlyView) + internal fun answerVoiceOnly() { + originalBundle.putBoolean(BundleKeys.KEY_CALL_VOICE_ONLY, true) + proceedToCall() + } + + private fun proceedToCall() { + originalBundle.putString( + BundleKeys.KEY_ROOM_TOKEN, + currentConversation!!.token + ) + + router.replaceTopController( + RouterTransaction.with(CallController(originalBundle)) + .popChangeHandler(HorizontalChangeHandler()) + .pushChangeHandler(HorizontalChangeHandler()) + ) + } + + private fun checkIfAnyParticipantsRemainInRoom() { + ncApi!!.getPeersForCall( + credentials, ApiUtils.getUrlForParticipants( + userBeingCalled!!.baseUrl, + currentConversation!!.token + ) + ) + .subscribeOn(Schedulers.io()) + .takeWhile { observable -> !leavingScreen } + .retry(3) + .`as`(AutoDispose.autoDisposable(scopeProvider)) + .subscribe(object : Observer { + override fun onSubscribe(d: Disposable) {} + + override fun onNext(participantsOverall: ParticipantsOverall) { + var hasParticipantsInCall = false + var inCallOnDifferentDevice = false + val participantList = participantsOverall.ocs.data + for (participant in participantList) { + if (participant.participantFlags != Participant.ParticipantFlags.NOT_IN_CALL) { + hasParticipantsInCall = true + + if (participant.userId == userBeingCalled.userId) { + inCallOnDifferentDevice = true + break + } + } + } + + if (!hasParticipantsInCall || inCallOnDifferentDevice) { + if (activity != null) { + activity!!.runOnUiThread { hangup() } + } + } + } + + override fun onError(e: Throwable) { + + } + + override fun onComplete() { + if (!leavingScreen) { + checkIfAnyParticipantsRemainInRoom() + } + } + }) + } + + private fun handleFromNotification() { + ncApi!!.getRooms(credentials, ApiUtils.getUrlForGetRooms(userBeingCalled!!.baseUrl)) + .subscribeOn(Schedulers.io()) + .retry(3) + .observeOn(AndroidSchedulers.mainThread()) + .`as`(AutoDispose.autoDisposable(scopeProvider)) + .subscribe(object : Observer { + override fun onSubscribe(d: Disposable) {} + + override fun onNext(roomsOverall: RoomsOverall) { + for (conversation in roomsOverall.ocs.data) { + if (roomId == conversation.conversationId) { + currentConversation = conversation + runAllThings() + break + } + } + } + + override fun onError(e: Throwable) { + + } + + override fun onComplete() { + + } + }) + } + + private fun runAllThings() { + if (conversationNameTextView != null) { + conversationNameTextView!!.text = currentConversation!!.displayName + } + + loadAvatar() + checkIfAnyParticipantsRemainInRoom() + showAnswerControls() + } + + @SuppressLint("LongLogTag") + override fun onViewBound(view: View) { + super.onViewBound(view) + + if (handler == null) { + handler = Handler() + } + + if (currentConversation == null) { + handleFromNotification() + } else { + runAllThings() + } + + if (DoNotDisturbUtils.shouldPlaySound()) { + val callRingtonePreferenceString = appPreferences.callRingtoneUri + var ringtoneUri: Uri? + + if (TextUtils.isEmpty(callRingtonePreferenceString)) { + // play default sound + ringtoneUri = Uri.parse( + "android.resource://" + applicationContext!!.packageName + + "/raw/librem_by_feandesign_call" + ) + } else { + try { + val ringtoneSettings = + LoganSquare.parse(callRingtonePreferenceString, RingtoneSettings::class.java) + ringtoneUri = ringtoneSettings.ringtoneUri + } catch (e: IOException) { + Log.e(TAG, "Failed to parse ringtone settings") + ringtoneUri = Uri.parse( + "android.resource://" + applicationContext!!.packageName + + "/raw/librem_by_feandesign_call" + ) + } + + } + + if (ringtoneUri != null && activity != null) { + mediaPlayer = MediaPlayer() + try { + mediaPlayer!!.setDataSource(activity!!, ringtoneUri) + + mediaPlayer!!.isLooping = true + val audioAttributes = AudioAttributes.Builder() + .setContentType( + AudioAttributes + .CONTENT_TYPE_SONIFICATION + ) + .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) + .build() + mediaPlayer!!.setAudioAttributes(audioAttributes) + + mediaPlayer!!.setOnPreparedListener { mp -> mediaPlayer!!.start() } + + mediaPlayer!!.prepareAsync() + } catch (e: IOException) { + Log.e(TAG, "Failed to set data source") + } + + } + } + + if (DoNotDisturbUtils.shouldVibrate(appPreferences.shouldVibrateSetting)) { + vibrator = applicationContext!!.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + + if (vibrator != null) { + val vibratePattern = longArrayOf(0, 400, 800, 600, 800, 800, 800, 1000) + val amplitudes = intArrayOf(0, 255, 0, 255, 0, 255, 0, 255) + + val vibrationEffect: VibrationEffect + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (vibrator!!.hasAmplitudeControl()) { + vibrationEffect = VibrationEffect.createWaveform(vibratePattern, amplitudes, -1) + vibrator!!.vibrate(vibrationEffect) + } else { + vibrationEffect = VibrationEffect.createWaveform(vibratePattern, -1) + vibrator!!.vibrate(vibrationEffect) + } + } else { + vibrator!!.vibrate(vibratePattern, -1) + } + } + + handler!!.postDelayed({ + if (vibrator != null) { + vibrator!!.cancel() + } + }, 10000) + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onMessageEvent(configurationChangeEvent: ConfigurationChangeEvent) { + val layoutParams = avatarImageView!!.layoutParams as ConstraintLayout.LayoutParams + val dimen = resources!!.getDimension(R.dimen.avatar_size_very_big) + .toInt() + + layoutParams.width = dimen + layoutParams.height = dimen + avatarImageView!!.layoutParams = layoutParams + } + + private fun loadAvatar() { + when (currentConversation!!.type) { + Conversation.ConversationType.ONE_TO_ONE_CONVERSATION -> { + avatarImageView!!.visibility = View.VISIBLE + + incomingTextRelativeLayout?.background = + resources?.getDrawable(R.drawable.incoming_gradient) + avatarImageView?.load( + ApiUtils.getUrlForAvatarWithName( + userBeingCalled!!.baseUrl, + currentConversation!!.name, R.dimen.avatar_size_very_big + ) + ) { + transformations(CircleCropTransformation()) + listener(onSuccess = { data, dataSource -> + GlobalScope.launch { + if ((AvatarStatusCodeHolder.getInstance().statusCode == 200 || AvatarStatusCodeHolder.getInstance().statusCode == 0) + && userBeingCalled.hasSpreedFeatureCapability("no-ping")) { + + if (activity != null) { + val newBitmap = BlurTransformation(activity!!, 5f).transform( + BitmapPool(10000000), ((avatarImageView!!.getDrawable() as CrossfadeDrawable).end as BitmapDrawable).bitmap + ) + backgroundImageView!!.setImageBitmap(newBitmap) + } + } else if (AvatarStatusCodeHolder.getInstance().statusCode == 201) { + val colorArt = ColorArt(((avatarImageView!!.getDrawable() as CrossfadeDrawable).end as BitmapDrawable).bitmap) + var color = colorArt.backgroundColor + + val hsv = FloatArray(3) + Color.colorToHSV(color, hsv) + hsv[2] *= 0.75f + color = Color.HSVToColor(hsv) + + backgroundImageView!!.setImageDrawable(ColorDrawable(color)) + } + } + }) + + } + } + + Conversation.ConversationType.GROUP_CONVERSATION -> { + avatarImageView?.load(R.drawable.ic_people_group_white_24px) { + transformations(CircleCropTransformation()) + } + } + Conversation.ConversationType.PUBLIC_CONVERSATION -> { + avatarImageView?.load(R.drawable.ic_link_white_24px) { + transformations(CircleCropTransformation()) + } + + } + + else -> { + // do nothing + } + } + } + + private fun endMediaAndVibratorNotifications() { + if (mediaPlayer != null) { + if (mediaPlayer!!.isPlaying) { + mediaPlayer!!.stop() + } + + mediaPlayer!!.release() + mediaPlayer = null + } + + if (vibrator != null) { + vibrator!!.cancel() + } + } + + public override fun onDestroy() { + AvatarStatusCodeHolder.getInstance() + .statusCode = 0 + leavingScreen = true + if (handler != null) { + handler!!.removeCallbacksAndMessages(null) + handler = null + } + endMediaAndVibratorNotifications() + super.onDestroy() + } + + companion object { + + private val TAG = "CallNotificationController" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java b/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java index 9ee73c08d..5f99ee588 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java @@ -163,7 +163,7 @@ public class RestModule { @Provides CookieManager provideCookieManager() { CookieManager cookieManager = new CookieManager(); - cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_NONE); + cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); return cookieManager; } diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt index 5fc4a1fb5..b83de5023 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt @@ -145,27 +145,33 @@ class NotificationWorker( private var credentials: String? = null private var muteCall = false private var importantConversation = false + + private fun showNotificationForCallWithNoPing(intent: Intent) { val userEntity: UserEntity = signatureVerification!!.userEntity - var arbitraryStorageEntity: ArbitraryStorageEntity - if (arbitraryStorageUtils!!.getStorageSetting( - userEntity.id, - "mute_calls", - intent.extras!!.getString(KEY_ROOM_TOKEN) - ).also { arbitraryStorageEntity = it } - != null - ) { + var arbitraryStorageEntity: ArbitraryStorageEntity? + + arbitraryStorageEntity = arbitraryStorageUtils!!.getStorageSetting( + userEntity.id, + "mute_calls", + intent.extras!!.getString(KEY_ROOM_TOKEN) + ) + + if (arbitraryStorageEntity != null) { muteCall = arbitraryStorageEntity.value!!.toBoolean() } - if (arbitraryStorageUtils!!.getStorageSetting( - userEntity.id, - "important_conversation", - intent.extras!!.getString(KEY_ROOM_TOKEN) - ).also { arbitraryStorageEntity = it } != null - ) { + + arbitraryStorageEntity = arbitraryStorageUtils!!.getStorageSetting( + userEntity.id, + "important_conversation", + intent.extras!!.getString(KEY_ROOM_TOKEN) + ) + + if (arbitraryStorageEntity != null) { importantConversation = arbitraryStorageEntity.value!!.toBoolean() } + if (isDnDActive()) { if (!isInDoNotDisturbWithPriority() || !importantConversation @@ -624,133 +630,125 @@ class NotificationWorker( data.getString(KEY_NOTIFICATION_SUBJECT) val signature = data.getString(KEY_NOTIFICATION_SIGNATURE) + val base64DecodedSubject: ByteArray = Base64.decode(subject, Base64.DEFAULT) + val base64DecodedSignature: ByteArray = Base64.decode(signature, Base64.DEFAULT) + val pushUtils = PushUtils() + val privateKey = pushUtils.readKeyFromFile(false) as PrivateKey try { - val base64DecodedSubject: ByteArray? = - Base64.decode(subject, Base64.DEFAULT) - val base64DecodedSignature: ByteArray? = - Base64.decode(signature, Base64.DEFAULT) - val pushUtils = PushUtils() - val privateKey = - pushUtils.readKeyFromFile(false) as PrivateKey - try { - signatureVerification = pushUtils.verifySignature( - base64DecodedSignature, - base64DecodedSubject - ) - if (signatureVerification!!.signatureValid) { - val cipher: Cipher = Cipher.getInstance("RSA/None/PKCS1Padding") - cipher.init(Cipher.DECRYPT_MODE, privateKey) - val decryptedSubject: ByteArray? = cipher.doFinal(base64DecodedSubject) - decryptedPushMessage = - LoganSquare.parse( - String(decryptedSubject!!), - DecryptedPushMessage::class.java - ) - decryptedPushMessage!!.timestamp = System.currentTimeMillis() - if (decryptedPushMessage!!.delete) { - cancelExistingNotificationWithId( - context, - signatureVerification!!.userEntity, decryptedPushMessage!!.notificationId - ) - } else if (decryptedPushMessage!!.deleteAll) { - cancelAllNotificationsForAccount( - context, + signatureVerification = pushUtils.verifySignature( + base64DecodedSignature, + base64DecodedSubject + ) + if (signatureVerification!!.signatureValid) { + val cipher: Cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + cipher.init(Cipher.DECRYPT_MODE, privateKey) + val decryptedSubject: ByteArray? = cipher.doFinal(base64DecodedSubject) + decryptedPushMessage = + LoganSquare.parse( + decryptedSubject!!.toString(Charsets.UTF_8), + DecryptedPushMessage::class.java + ) + decryptedPushMessage!!.timestamp = System.currentTimeMillis() + if (decryptedPushMessage!!.delete) { + cancelExistingNotificationWithId( + context, + signatureVerification!!.userEntity, decryptedPushMessage!!.notificationId + ) + } else if (decryptedPushMessage!!.deleteAll) { + cancelAllNotificationsForAccount( + context, + signatureVerification!!.userEntity + ) + } else { + credentials = signatureVerification!!.userEntity.getCredentials() + + ncApi = retrofit!!.newBuilder() + .client( + okHttpClient!!.newBuilder().cookieJar( + JavaNetCookieJar(CookieManager()) + ).build() + ) + .build() + .create( + NcApi::class.java + ) + val hasChatSupport = + signatureVerification!!.userEntity.hasSpreedFeatureCapability("chat-v2") + val shouldShowNotification = decryptedPushMessage!!.app == "spreed" + if (shouldShowNotification) { + val intent: Intent + val bundle = Bundle() + val startACall = + decryptedPushMessage!!.type == "call" || !hasChatSupport + intent = if (startACall) { + Intent( + context, MagicCallActivity::class.java + ) + } else { + Intent( + context, MainActivity::class.java + ) + } + intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK + if (!signatureVerification!!.userEntity.hasSpreedFeatureCapability("no-ping")) { + bundle.putString( + KEY_ROOM_ID, + decryptedPushMessage!!.id + ) + } else { + bundle.putString( + KEY_ROOM_TOKEN, + decryptedPushMessage!!.id + ) + } + bundle.putParcelable( + KEY_USER_ENTITY, signatureVerification!!.userEntity ) - } else { - credentials = signatureVerification!!.userEntity.getCredentials() - - ncApi = retrofit!!.newBuilder() - .client( - okHttpClient!!.newBuilder().cookieJar( - JavaNetCookieJar(CookieManager()) - ).build() - ) - .build() - .create( - NcApi::class.java - ) - val hasChatSupport = signatureVerification!!.userEntity.hasSpreedFeatureCapability("chat-v2") - val shouldShowNotification = decryptedPushMessage!!.app == "spreed" - if (shouldShowNotification) { - val intent: Intent - val bundle = Bundle() - val startACall = - decryptedPushMessage!!.type == "call" || !hasChatSupport - intent = if (startACall) { - Intent( - context, MagicCallActivity::class.java - ) + bundle.putBoolean( + KEY_FROM_NOTIFICATION_START_CALL, + startACall + ) + intent.putExtras(bundle) + when (decryptedPushMessage!!.type) { + "call" -> if (!bundle.containsKey( + KEY_ROOM_TOKEN + ) + ) { + context!!.startActivity(intent) } else { - Intent( - context, MainActivity::class.java - ) + showNotificationForCallWithNoPing(intent) } - intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK - if (!signatureVerification!!.userEntity.hasSpreedFeatureCapability("no-ping")) { - bundle.putString( - KEY_ROOM_ID, - decryptedPushMessage!!.id - ) + "room" -> if (bundle.containsKey( + KEY_ROOM_TOKEN + ) + ) { + showNotificationWithObjectData(intent) + } + "chat" -> if (decryptedPushMessage!!.notificationId != Long.MIN_VALUE) { + showNotificationWithObjectData(intent) } else { - bundle.putString( - KEY_ROOM_TOKEN, - decryptedPushMessage!!.id - ) + showNotification(intent) } - bundle.putParcelable( - KEY_USER_ENTITY, - signatureVerification!!.userEntity - ) - bundle.putBoolean( - KEY_FROM_NOTIFICATION_START_CALL, - startACall - ) - intent.putExtras(bundle) - when (decryptedPushMessage!!.type) { - "call" -> if (!bundle.containsKey( - KEY_ROOM_TOKEN - ) - ) { - context!!.startActivity(intent) - } else { - showNotificationForCallWithNoPing(intent) - } - "room" -> if (bundle.containsKey( - KEY_ROOM_TOKEN - ) - ) { - showNotificationWithObjectData(intent) - } - "chat" -> if (decryptedPushMessage!!.notificationId != Long.MIN_VALUE) { - showNotificationWithObjectData(intent) - } else { - showNotification(intent) - } - else -> { - } + else -> { } } } } - } catch (e1: NoSuchAlgorithmException) { - Log.d( - TAG, - "No proper algorithm to decrypt the message " + e1.localizedMessage - ) - } catch (e1: NoSuchPaddingException) { - Log.d( - TAG, - "No proper padding to decrypt the message " + e1.localizedMessage - ) - } catch (e1: InvalidKeyException) { - Log.d( - TAG, "Invalid private key " + e1.localizedMessage - ) } - } catch (exception: Exception) { + } catch (e1: NoSuchAlgorithmException) { Log.d( - TAG, "Something went very wrong " + exception.localizedMessage + TAG, + "No proper algorithm to decrypt the message " + e1.localizedMessage + ) + } catch (e1: NoSuchPaddingException) { + Log.d( + TAG, + "No proper padding to decrypt the message " + e1.localizedMessage + ) + } catch (e1: InvalidKeyException) { + Log.d( + TAG, "Invalid private key " + e1.localizedMessage ) } return Result.success() diff --git a/app/src/main/java/com/nextcloud/talk/newarch/di/module/NetworkModule.kt b/app/src/main/java/com/nextcloud/talk/newarch/di/module/NetworkModule.kt index d1196cbce..ed0241748 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/di/module/NetworkModule.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/di/module/NetworkModule.kt @@ -60,6 +60,7 @@ import retrofit2.Retrofit import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import java.io.IOException import java.net.CookieManager +import java.net.CookiePolicy.ACCEPT_ALL import java.net.CookiePolicy.ACCEPT_NONE import java.net.Proxy import java.security.KeyStore @@ -90,7 +91,7 @@ val NetworkModule = module { fun createCookieManager(): CookieManager { val cookieManager = CookieManager() - cookieManager.setCookiePolicy(ACCEPT_NONE) + cookieManager.setCookiePolicy(ACCEPT_ALL) return cookieManager } diff --git a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.kt index 4cbd9341e..7445343ba 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.kt @@ -220,7 +220,7 @@ object DisplayUtils { val chip = ChipDrawable.createFromResource(context, chipResource) chip.text = EmojiCompat.get() .process(label) - chip.ellipsize = TextUtils.TruncateAt.MIDDLE + chip.ellipsize = TextUtils.TruncateAt.END if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val config = context.resources.configuration diff --git a/app/src/main/res/layout/controller_call_notification.xml b/app/src/main/res/layout/controller_call_notification.xml index ee7c6dc8f..6a989ceb2 100644 --- a/app/src/main/res/layout/controller_call_notification.xml +++ b/app/src/main/res/layout/controller_call_notification.xml @@ -101,12 +101,11 @@ -