Fix notifications

Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
Mario Danic 2019-10-28 12:25:07 +01:00
parent e7790b38d1
commit f5468657d8
10 changed files with 611 additions and 630 deletions

View File

@ -70,7 +70,7 @@ class MagicCallActivity : BaseActivity() {
if (!router!!.hasRootController()) { if (!router!!.hasRootController()) {
if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) { if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) {
router!!.setRoot( router!!.setRoot(
RouterTransaction.with(CallNotificationController(intent.extras)) RouterTransaction.with(CallNotificationController(intent.extras!!))
.pushChangeHandler(HorizontalChangeHandler()) .pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler()) .popChangeHandler(HorizontalChangeHandler())
) )

View File

@ -154,7 +154,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) { if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) { if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) {
router!!.pushController( router!!.pushController(
RouterTransaction.with(CallNotificationController(intent.extras)) RouterTransaction.with(CallNotificationController(intent.extras!!))
.pushChangeHandler(HorizontalChangeHandler()) .pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler()) .popChangeHandler(HorizontalChangeHandler())
) )

View File

@ -106,6 +106,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import java.io.IOException; import java.io.IOException;
import java.net.CookieManager;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -203,6 +204,8 @@ public class CallController extends BaseController {
@Inject @Inject
AppPreferences appPreferences; AppPreferences appPreferences;
@Inject @Inject
CookieManager cookieManager;
@Inject
EventBus eventBus; EventBus eventBus;
private PeerConnectionFactory peerConnectionFactory; private PeerConnectionFactory peerConnectionFactory;
@ -1747,6 +1750,8 @@ public class CallController extends BaseController {
} }
setPipVideoViewDimensions(); setPipVideoViewDimensions();
cookieManager.getCookieStore().removeAll();
} }
private void setPipVideoViewDimensions() { private void setPipVideoViewDimensions() {

View File

@ -1,495 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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<ParticipantsOverall>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(ParticipantsOverall participantsOverall) {
boolean hasParticipantsInCall = false;
boolean inCallOnDifferentDevice = false;
List<Participant> 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<RoomsOverall>() {
@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<CloseableReference<CloseableImage>> 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<CloseableReference<CloseableImage>> 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();
}
}

View File

@ -0,0 +1,473 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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<ParticipantsOverall> {
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<RoomsOverall> {
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"
}
}

View File

@ -163,7 +163,7 @@ public class RestModule {
@Provides @Provides
CookieManager provideCookieManager() { CookieManager provideCookieManager() {
CookieManager cookieManager = new CookieManager(); CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_NONE); cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
return cookieManager; return cookieManager;
} }

View File

@ -145,27 +145,33 @@ class NotificationWorker(
private var credentials: String? = null private var credentials: String? = null
private var muteCall = false private var muteCall = false
private var importantConversation = false private var importantConversation = false
private fun showNotificationForCallWithNoPing(intent: Intent) { private fun showNotificationForCallWithNoPing(intent: Intent) {
val userEntity: UserEntity = val userEntity: UserEntity =
signatureVerification!!.userEntity signatureVerification!!.userEntity
var arbitraryStorageEntity: ArbitraryStorageEntity var arbitraryStorageEntity: ArbitraryStorageEntity?
if (arbitraryStorageUtils!!.getStorageSetting(
userEntity.id, arbitraryStorageEntity = arbitraryStorageUtils!!.getStorageSetting(
"mute_calls", userEntity.id,
intent.extras!!.getString(KEY_ROOM_TOKEN) "mute_calls",
).also { arbitraryStorageEntity = it } intent.extras!!.getString(KEY_ROOM_TOKEN)
!= null )
) {
if (arbitraryStorageEntity != null) {
muteCall = arbitraryStorageEntity.value!!.toBoolean() muteCall = arbitraryStorageEntity.value!!.toBoolean()
} }
if (arbitraryStorageUtils!!.getStorageSetting(
userEntity.id, arbitraryStorageEntity = arbitraryStorageUtils!!.getStorageSetting(
"important_conversation", userEntity.id,
intent.extras!!.getString(KEY_ROOM_TOKEN) "important_conversation",
).also { arbitraryStorageEntity = it } != null intent.extras!!.getString(KEY_ROOM_TOKEN)
) { )
if (arbitraryStorageEntity != null) {
importantConversation = arbitraryStorageEntity.value!!.toBoolean() importantConversation = arbitraryStorageEntity.value!!.toBoolean()
} }
if (isDnDActive()) { if (isDnDActive()) {
if (!isInDoNotDisturbWithPriority() if (!isInDoNotDisturbWithPriority()
|| !importantConversation || !importantConversation
@ -624,133 +630,125 @@ class NotificationWorker(
data.getString(KEY_NOTIFICATION_SUBJECT) data.getString(KEY_NOTIFICATION_SUBJECT)
val signature = val signature =
data.getString(KEY_NOTIFICATION_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 { try {
val base64DecodedSubject: ByteArray? = signatureVerification = pushUtils.verifySignature(
Base64.decode(subject, Base64.DEFAULT) base64DecodedSignature,
val base64DecodedSignature: ByteArray? = base64DecodedSubject
Base64.decode(signature, Base64.DEFAULT) )
val pushUtils = PushUtils() if (signatureVerification!!.signatureValid) {
val privateKey = val cipher: Cipher = Cipher.getInstance("RSA/None/PKCS1Padding")
pushUtils.readKeyFromFile(false) as PrivateKey cipher.init(Cipher.DECRYPT_MODE, privateKey)
try { val decryptedSubject: ByteArray? = cipher.doFinal(base64DecodedSubject)
signatureVerification = pushUtils.verifySignature( decryptedPushMessage =
base64DecodedSignature, LoganSquare.parse(
base64DecodedSubject decryptedSubject!!.toString(Charsets.UTF_8),
) DecryptedPushMessage::class.java
if (signatureVerification!!.signatureValid) { )
val cipher: Cipher = Cipher.getInstance("RSA/None/PKCS1Padding") decryptedPushMessage!!.timestamp = System.currentTimeMillis()
cipher.init(Cipher.DECRYPT_MODE, privateKey) if (decryptedPushMessage!!.delete) {
val decryptedSubject: ByteArray? = cipher.doFinal(base64DecodedSubject) cancelExistingNotificationWithId(
decryptedPushMessage = context,
LoganSquare.parse<DecryptedPushMessage>( signatureVerification!!.userEntity, decryptedPushMessage!!.notificationId
String(decryptedSubject!!), )
DecryptedPushMessage::class.java } else if (decryptedPushMessage!!.deleteAll) {
) cancelAllNotificationsForAccount(
decryptedPushMessage!!.timestamp = System.currentTimeMillis() context,
if (decryptedPushMessage!!.delete) { signatureVerification!!.userEntity
cancelExistingNotificationWithId( )
context, } else {
signatureVerification!!.userEntity, decryptedPushMessage!!.notificationId credentials = signatureVerification!!.userEntity.getCredentials()
)
} else if (decryptedPushMessage!!.deleteAll) { ncApi = retrofit!!.newBuilder()
cancelAllNotificationsForAccount( .client(
context, 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 signatureVerification!!.userEntity
) )
} else { bundle.putBoolean(
credentials = signatureVerification!!.userEntity.getCredentials() KEY_FROM_NOTIFICATION_START_CALL,
startACall
ncApi = retrofit!!.newBuilder() )
.client( intent.putExtras(bundle)
okHttpClient!!.newBuilder().cookieJar( when (decryptedPushMessage!!.type) {
JavaNetCookieJar(CookieManager()) "call" -> if (!bundle.containsKey(
).build() KEY_ROOM_TOKEN
) )
.build() ) {
.create( context!!.startActivity(intent)
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 { } else {
Intent( showNotificationForCallWithNoPing(intent)
context, MainActivity::class.java
)
} }
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK "room" -> if (bundle.containsKey(
if (!signatureVerification!!.userEntity.hasSpreedFeatureCapability("no-ping")) { KEY_ROOM_TOKEN
bundle.putString( )
KEY_ROOM_ID, ) {
decryptedPushMessage!!.id showNotificationWithObjectData(intent)
) }
"chat" -> if (decryptedPushMessage!!.notificationId != Long.MIN_VALUE) {
showNotificationWithObjectData(intent)
} else { } else {
bundle.putString( showNotification(intent)
KEY_ROOM_TOKEN,
decryptedPushMessage!!.id
)
} }
bundle.putParcelable( else -> {
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 -> {
}
} }
} }
} }
} }
} 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( 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() return Result.success()

View File

@ -60,6 +60,7 @@ import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import java.io.IOException import java.io.IOException
import java.net.CookieManager import java.net.CookieManager
import java.net.CookiePolicy.ACCEPT_ALL
import java.net.CookiePolicy.ACCEPT_NONE import java.net.CookiePolicy.ACCEPT_NONE
import java.net.Proxy import java.net.Proxy
import java.security.KeyStore import java.security.KeyStore
@ -90,7 +91,7 @@ val NetworkModule = module {
fun createCookieManager(): CookieManager { fun createCookieManager(): CookieManager {
val cookieManager = CookieManager() val cookieManager = CookieManager()
cookieManager.setCookiePolicy(ACCEPT_NONE) cookieManager.setCookiePolicy(ACCEPT_ALL)
return cookieManager return cookieManager
} }

View File

@ -220,7 +220,7 @@ object DisplayUtils {
val chip = ChipDrawable.createFromResource(context, chipResource) val chip = ChipDrawable.createFromResource(context, chipResource)
chip.text = EmojiCompat.get() chip.text = EmojiCompat.get()
.process(label) .process(label)
chip.ellipsize = TextUtils.TruncateAt.MIDDLE chip.ellipsize = TextUtils.TruncateAt.END
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val config = context.resources.configuration val config = context.resources.configuration

View File

@ -101,12 +101,11 @@
</RelativeLayout> </RelativeLayout>
<com.facebook.drawee.view.SimpleDraweeView <ImageView
android:id="@+id/avatarImageView" android:id="@+id/avatarImageView"
android:layout_width="@dimen/avatar_size_very_big" android:layout_width="@dimen/avatar_size_very_big"
android:layout_height="@dimen/avatar_size_very_big" android:layout_height="@dimen/avatar_size_very_big"
android:layout_centerInParent="true" android:layout_centerInParent="true"
app:roundAsCircle="true"
tools:srcCompat="@tools:sample/avatars[0]" /> tools:srcCompat="@tools:sample/avatars[0]" />
</RelativeLayout> </RelativeLayout>