Basic FCM vs fdroid mode

This commit is contained in:
Valere
2019-06-20 15:22:40 +02:00
committed by Benoit Marty
parent 0e46fc4c0a
commit 2e417a9143
30 changed files with 663 additions and 1245 deletions

View File

@ -2,14 +2,24 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="im.vector.riotredesign">
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application>
<receiver android:name=".receiver.OnApplicationUpgradeReceiver">
<receiver android:name=".receiver.OnApplicationUpgradeOrRebootReceiver">
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver
android:name=".core.services.AlarmSyncBroadcastReceiver"
android:enabled="true"
android:exported="false">
</receiver>
</application>
</manifest>

View File

@ -14,24 +14,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.push.fcm;
package im.vector.riotredesign.push.fcm
import android.app.Activity;
import android.content.Context;
import android.app.Activity
import android.content.Context
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import im.vector.riotredesign.core.pushers.PushersManager
public class FcmHelper {
object FcmHelper {
fun isPushSupported(): Boolean = false
/**
* Retrieves the FCM registration token.
*
* @return the FCM token or null if not received from FCM
*/
@Nullable
public static String getFcmToken(Context context) {
return null;
fun getFcmToken(context: Context): String? {
return null
}
/**
@ -40,8 +40,7 @@ public class FcmHelper {
* @param context android context
* @param token the token to store
*/
public static void storeFcmToken(@NonNull Context context,
@Nullable String token) {
fun storeFcmToken(context: Context, token: String?) {
// No op
}
@ -50,7 +49,7 @@ public class FcmHelper {
*
* @param activity the first launch Activity
*/
public static void ensureFcmTokenIsRetrieved(final Activity activity, PushersManager pushersManager) {
fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager) {
// No op
}
}

View File

@ -1,5 +1,6 @@
/*
* Copyright 2018 New Vector Ltd
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,21 +15,18 @@
* limitations under the License.
*/
package im.vector.riotredesign.receiver;
package im.vector.riotredesign.receiver
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import im.vector.riotredesign.core.services.AlarmSyncBroadcastReceiver
import timber.log.Timber
import timber.log.Timber;
class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() {
public class OnApplicationUpgradeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Timber.v("## onReceive() : Application has been upgraded, restart event stream service.");
// Start Event stream
// TODO EventStreamServiceX.Companion.onApplicationUpgrade(context);
override fun onReceive(context: Context, intent: Intent) {
Timber.v("## onReceive() ${intent.action}")
AlarmSyncBroadcastReceiver.scheduleAlarm(context, 10)
}
}

View File

@ -1,113 +0,0 @@
/*
* Copyright 2014 OpenMarket Ltd
* Copyright 2017 Vector Creations Ltd
* Copyright 2018 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.push.fcm;
import android.app.Activity;
import android.content.Context;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.firebase.iid.FirebaseInstanceId;
import im.vector.riotredesign.R;
import im.vector.riotredesign.core.pushers.PushersManager;
import timber.log.Timber;
/**
* This class store the FCM token in SharedPrefs and ensure this token is retrieved.
* It has an alter ego in the fdroid variant.
*/
public class FcmHelper {
private static final String LOG_TAG = FcmHelper.class.getSimpleName();
private static final String PREFS_KEY_FCM_TOKEN = "FCM_TOKEN";
/**
* Retrieves the FCM registration token.
*
* @return the FCM token or null if not received from FCM
*/
@Nullable
public static String getFcmToken(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getString(PREFS_KEY_FCM_TOKEN, null);
}
/**
* Store FCM token to the SharedPrefs
*
* @param context android context
* @param token the token to store
*/
public static void storeFcmToken(@NonNull Context context,
@Nullable String token) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putString(PREFS_KEY_FCM_TOKEN, token)
.apply();
}
/**
* onNewToken may not be called on application upgrade, so ensure my shared pref is set
*
* @param activity the first launch Activity
*/
public static void ensureFcmTokenIsRetrieved(final Activity activity, PushersManager pushersManager) {
// if (TextUtils.isEmpty(getFcmToken(activity))) {
//vfe: according to firebase doc
//'app should always check the device for a compatible Google Play services APK before accessing Google Play services features'
if (checkPlayServices(activity)) {
try {
FirebaseInstanceId.getInstance().getInstanceId()
.addOnSuccessListener(activity, instanceIdResult -> {
storeFcmToken(activity, instanceIdResult.getToken());
pushersManager.registerPusherWithFcmKey(instanceIdResult.getToken());
})
.addOnFailureListener(activity, e -> Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed " + e.getMessage()));
} catch (Throwable e) {
Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed " + e.getMessage());
}
} else {
Toast.makeText(activity, R.string.no_valid_google_play_services_apk, Toast.LENGTH_SHORT).show();
Timber.e("No valid Google Play Services found. Cannot use FCM.");
}
// }
}
/**
* Check the device to make sure it has the Google Play Services APK. If
* it doesn't, display a dialog that allows users to download the APK from
* the Google Play Store or enable it in the device's system settings.
*/
private static boolean checkPlayServices(Activity activity) {
GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
int resultCode = apiAvailability.isGooglePlayServicesAvailable(activity);
if (resultCode != ConnectionResult.SUCCESS) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright 2014 OpenMarket Ltd
* Copyright 2017 Vector Creations Ltd
* Copyright 2018 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.push.fcm
import android.app.Activity
import android.content.Context
import android.preference.PreferenceManager
import android.widget.Toast
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import com.google.firebase.iid.FirebaseInstanceId
import im.vector.riotredesign.R
import im.vector.riotredesign.core.pushers.PushersManager
import timber.log.Timber
/**
* This class store the FCM token in SharedPrefs and ensure this token is retrieved.
* It has an alter ego in the fdroid variant.
*/
object FcmHelper {
private val LOG_TAG = FcmHelper::class.java.simpleName
private val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN"
fun isPushSupported(): Boolean = true
/**
* Retrieves the FCM registration token.
*
* @return the FCM token or null if not received from FCM
*/
fun getFcmToken(context: Context): String? {
return PreferenceManager.getDefaultSharedPreferences(context).getString(PREFS_KEY_FCM_TOKEN, null)
}
/**
* Store FCM token to the SharedPrefs
*
* @param context android context
* @param token the token to store
*/
fun storeFcmToken(context: Context,
token: String?) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putString(PREFS_KEY_FCM_TOKEN, token)
.apply()
}
/**
* onNewToken may not be called on application upgrade, so ensure my shared pref is set
*
* @param activity the first launch Activity
*/
fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager) {
// if (TextUtils.isEmpty(getFcmToken(activity))) {
//'app should always check the device for a compatible Google Play services APK before accessing Google Play services features'
if (checkPlayServices(activity)) {
try {
FirebaseInstanceId.getInstance().instanceId
.addOnSuccessListener(activity) { instanceIdResult ->
storeFcmToken(activity, instanceIdResult.token)
pushersManager.registerPusherWithFcmKey(instanceIdResult.token)
}
.addOnFailureListener(activity) { e -> Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed " + e.message) }
} catch (e: Throwable) {
Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed " + e.message)
}
} else {
Toast.makeText(activity, R.string.no_valid_google_play_services_apk, Toast.LENGTH_SHORT).show()
Timber.e("No valid Google Play Services found. Cannot use FCM.")
}
}
/**
* Check the device to make sure it has the Google Play Services APK. If
* it doesn't, display a dialog that allows users to download the APK from
* the Google Play Store or enable it in the device's system settings.
*/
private fun checkPlayServices(activity: Activity): Boolean {
val apiAvailability = GoogleApiAvailability.getInstance()
val resultCode = apiAvailability.isGooglePlayServicesAvailable(activity)
return resultCode == ConnectionResult.SUCCESS
}
}

View File

@ -144,14 +144,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
Timber.i("Ignoring push, event already knwown")
} else {
Timber.v("Requesting background sync")
val workRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setInputData(Data.Builder().put("timeout", 0L).build())
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.setBackoffCriteria(BackoffPolicy.LINEAR, 10_000, TimeUnit.MILLISECONDS)
.build()
WorkManager.getInstance().enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.REPLACE, workRequest)
session.requireBackgroundSync(0L)
}
}
@ -214,7 +207,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
isPushGatewayEvent = true
)
notificationDrawerManager.onNotifiableEventReceived(simpleNotifiableEvent)
notificationDrawerManager.refreshNotificationDrawer(null)
notificationDrawerManager.refreshNotificationDrawer()
return
} else {
@ -249,7 +242,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
notifiableEvent.isPushGatewayEvent = true
notifiableEvent.matrixID = session.sessionParams.credentials.userId
notificationDrawerManager.onNotifiableEventReceived(notifiableEvent)
notificationDrawerManager.refreshNotificationDrawer(null)
notificationDrawerManager.refreshNotificationDrawer()
}
}
}

View File

@ -15,12 +15,6 @@
android:supportsRtl="true"
android:theme="@style/AppTheme.Light"
tools:replace="android:allowBackup">
<receiver
android:name=".core.services.RestartBroadcastReceiver"
android:enabled="true"
android:exported="false">
</receiver>
<activity
android:name=".features.MainActivity"

View File

@ -34,7 +34,6 @@ import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.multidex.MultiDex
import androidx.work.*
import com.airbnb.epoxy.EpoxyAsyncUtil
import com.airbnb.epoxy.EpoxyController
import com.facebook.stetho.Stetho
@ -43,9 +42,8 @@ import com.github.piasy.biv.loader.glide.GlideImageLoader
import com.jakewharton.threetenabp.AndroidThreeTen
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.internal.session.sync.job.SyncService
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
import im.vector.riotredesign.core.di.AppModule
import im.vector.riotredesign.core.services.RestartBroadcastReceiver
import im.vector.riotredesign.core.services.AlarmSyncBroadcastReceiver
import im.vector.riotredesign.core.services.VectorSyncService
import im.vector.riotredesign.features.configuration.VectorConfiguration
import im.vector.riotredesign.features.crypto.keysbackup.KeysBackupModule
@ -57,6 +55,7 @@ import im.vector.riotredesign.features.rageshake.VectorFileLogger
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryModule
import im.vector.riotredesign.features.version.getVersion
import im.vector.riotredesign.push.fcm.FcmHelper
import org.koin.android.ext.android.get
import org.koin.android.ext.android.inject
import org.koin.log.EmptyLogger
@ -66,7 +65,7 @@ import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
class VectorApplication : Application(), SyncService.SyncListener {
class VectorApplication : Application() {
lateinit var appContext: Context
@ -75,26 +74,6 @@ class VectorApplication : Application(), SyncService.SyncListener {
val vectorConfiguration: VectorConfiguration by inject()
private var mBinder: SyncService.LocalBinder? = null
private val connection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) {
Timber.v("Service unbounded")
mBinder?.removeListener(this@VectorApplication)
mBinder = null
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Timber.v("Service bounded")
mBinder = service as SyncService.LocalBinder
mBinder?.addListener(this@VectorApplication)
mBinder?.getService()?.nextBatchDelay = 0
mBinder?.getService()?.timeout = 10_000L
mBinder?.getService()?.doSync()
}
}
// var slowMode = false
@ -141,92 +120,26 @@ class VectorApplication : Application(), SyncService.SyncListener {
ProcessLifecycleOwner.get().lifecycle.addObserver(object : LifecycleObserver {
fun cancelAlarm() {
val intent = Intent(applicationContext, RestartBroadcastReceiver::class.java)
val pIntent = PendingIntent.getBroadcast(applicationContext, RestartBroadcastReceiver.REQUEST_CODE,
intent, PendingIntent.FLAG_UPDATE_CURRENT)
val alarm = getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarm.cancel(pIntent)
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun entersForeground() {
// HttpLongPoolingSyncService.startService(applicationContext)
// cancelAlarm()
if (Matrix.getInstance().currentSession == null) return
WorkManager.getInstance().cancelAllWorkByTag("BG_SYNC")
Intent(applicationContext, VectorSyncService::class.java).also { intent ->
// intent.action = "NORMAL"
// try {
// startService(intent)
// } catch (e: Throwable) {
// Timber.e("Failed to launch sync service")
// }
bindService(intent, connection, Context.BIND_AUTO_CREATE)
AlarmSyncBroadcastReceiver.cancelAlarm(appContext)
Matrix.getInstance().currentSession?.also {
it.stopAnyBackgroundSync()
}
}
var isPushAvailable = true
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun entersBackground() {
Timber.i("App entered background")
//we have here 3 modes
if (isPushAvailable) {
// PUSH IS AVAILABLE:
// Just stop the service, we will sync when a notification is received
try {
unbindService(connection)
mBinder?.getService()?.stopMe()
mBinder = null
} catch (t: Throwable) {
Timber.e(t)
}
if (FcmHelper.isPushSupported()) {
//TODO FCM fallback
} else {
// NO PUSH, and don't care about battery
// unbindService(connection)
// mBinder?.getService()?.stopMe()// kill also
// mBinder = null
//In this case we will keep a permanent
//TODO if no push schedule reccuring alarm
// val workRequest = PeriodicWorkRequestBuilder<SyncWorker>(1, TimeUnit.MINUTES)
// .setConstraints(Constraints.Builder()
// .setRequiredNetworkType(NetworkType.CONNECTED)
// .build())
// .setBackoffCriteria(BackoffPolicy.LINEAR, 10_000, TimeUnit.MILLISECONDS)
// .build()
// WorkManager.getInstance().enqueueUniquePeriodicWork(
// "BG_SYNC",
// ExistingPeriodicWorkPolicy.KEEP,
// workRequest)
val workRequest = OneTimeWorkRequestBuilder<SyncWorker>()
// .setInitialDelay(30_000, TimeUnit.MILLISECONDS)
.setInputData(Data.Builder().put("timeout", 0L).build())
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.setBackoffCriteria(BackoffPolicy.LINEAR, 10_000, TimeUnit.MILLISECONDS)
.build()
WorkManager.getInstance().enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.REPLACE, workRequest)
// val intent = Intent(applicationContext, RestartBroadcastReceiver::class.java)
// // Create a PendingIntent to be triggered when the alarm goes off
// val pIntent = PendingIntent.getBroadcast(applicationContext, RestartBroadcastReceiver.REQUEST_CODE,
// intent, PendingIntent.FLAG_UPDATE_CURRENT);
// // Setup periodic alarm every every half hour from this point onwards
// val firstMillis = System.currentTimeMillis(); // alarm is set right away
// val alarmMgr = getSystemService(Context.ALARM_SERVICE) as AlarmManager
// // First parameter is the type: ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC_WAKEUP
// // Interval can be INTERVAL_FIFTEEN_MINUTES, INTERVAL_HALF_HOUR, INTERVAL_HOUR, INTERVAL_DAY
//// alarm.setInexactRepeating(AlarmManager.RTC_WAKEUP, firstMillis,
//// 30_000L, pIntent)
// alarmMgr.set(AlarmManager.RTC_WAKEUP, firstMillis, pIntent);
//TODO check if notifications are enabled for this device
//We need to use alarm in this mode
AlarmSyncBroadcastReceiver.scheduleAlarm(applicationContext,4_000L)
Timber.i("Alarm scheduled to restart service")
}
}
@ -273,36 +186,4 @@ class VectorApplication : Application(), SyncService.SyncListener {
return mFontThreadHandler!!
}
override fun onSyncFinsh() {
//in foreground sync right now!!
Timber.v("Sync just finished")
// mBinder?.getService()?.doSync()
}
override fun networkNotAvailable() {
//we then want to retry in 10s?
}
override fun onFailed(failure: Throwable) {
//stop it also?
// if (failure is Failure.NetworkConnection
// && failure.cause is SocketTimeoutException) {
// // Timeout are not critical just retry?
// //TODO
// }
//
// if (failure !is Failure.NetworkConnection
// || failure.cause is JsonEncodingException) {
// //TODO Retry in 10S?
// }
//
// if (failure is Failure.ServerError
// && (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) {
// // No token or invalid token, stop the thread
// mBinder?.getService()?.unbindService(connection)
// mBinder?.getService()?.stopMe()
// }
}
}

View File

@ -37,6 +37,7 @@ import im.vector.riotredesign.features.navigation.DefaultNavigator
import im.vector.riotredesign.features.navigation.Navigator
import im.vector.riotredesign.features.notifications.NotifiableEventResolver
import im.vector.riotredesign.features.notifications.NotificationDrawerManager
import im.vector.riotredesign.features.notifications.OutdatedEventDetector
import im.vector.riotredesign.features.notifications.PushRuleTriggerListener
import org.koin.dsl.module.module
@ -85,11 +86,15 @@ class AppModule(private val context: Context) {
}
single {
PushRuleTriggerListener(get(),get())
PushRuleTriggerListener(get(), get())
}
single {
NotificationDrawerManager(context)
OutdatedEventDetector(context)
}
single {
NotificationDrawerManager(context, get())
}
single {

View File

@ -0,0 +1,74 @@
package im.vector.riotredesign.core.services
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.PowerManager
import androidx.core.content.ContextCompat
import timber.log.Timber
class AlarmSyncBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
//Aquire a lock to give enough time for the sync :/
(context.getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "riotx:fdroidSynclock").apply {
acquire((10_000).toLong())
}
}
// This method is called when the BroadcastReceiver is receiving an Intent broadcast.
Timber.d("RestartBroadcastReceiver received intent")
Intent(context, VectorSyncService::class.java).also {
it.action = "SLOW"
context.startService(it)
try {
if (SDK_INT >= Build.VERSION_CODES.O) {
ContextCompat.startForegroundService(context, intent)
} else {
context.startService(intent)
}
} catch (ex: Throwable) {
//TODO
Timber.e(ex)
}
}
scheduleAlarm(context,30_000L)
Timber.i("Alarm scheduled to restart service")
}
companion object {
const val REQUEST_CODE = 0
fun scheduleAlarm(context: Context, delay: Long) {
//Reschedule
val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java)
val pIntent = PendingIntent.getBroadcast(context, AlarmSyncBroadcastReceiver.REQUEST_CODE,
intent, PendingIntent.FLAG_UPDATE_CURRENT)
val firstMillis = System.currentTimeMillis() + delay
val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
if (SDK_INT >= Build.VERSION_CODES.M) {
alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pIntent)
} else {
alarmMgr.set(AlarmManager.RTC_WAKEUP, firstMillis, pIntent)
}
}
fun cancelAlarm(context: Context) {
val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java)
val pIntent = PendingIntent.getBroadcast(context, AlarmSyncBroadcastReceiver.REQUEST_CODE,
intent, PendingIntent.FLAG_UPDATE_CURRENT)
val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmMgr.cancel(pIntent)
}
}
}

View File

@ -1,582 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.core.services
import android.content.Context
import android.content.Intent
import androidx.core.content.ContextCompat
import androidx.work.Constraints
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.riotredesign.R
import im.vector.riotredesign.features.notifications.NotifiableEventResolver
import im.vector.riotredesign.features.notifications.NotificationUtils
import org.koin.android.ext.android.inject
import timber.log.Timber
import java.util.concurrent.TimeUnit
/**
* A service in charge of controlling whether the event stream is running or not.
*
* It manages messages notifications displayed to the end user.
*/
class EventStreamServiceX : VectorService() {
/**
* Managed session (no multi session for Riot)
*/
private val mSession by inject<Session>()
/**
* Set to true to simulate a push immediately when service is destroyed
*/
private var mSimulatePushImmediate = false
/**
* The current state.
*/
private var serviceState = ServiceState.INIT
set(newServiceState) {
Timber.i("setServiceState from $field to $newServiceState")
field = newServiceState
}
/**
* Push manager
*/
// TODO private var mPushManager: PushManager? = null
private var mNotifiableEventResolver: NotifiableEventResolver? = null
/**
* Live events listener
*/
/* TODO
private val mEventsListener = object : MXEventListener() {
override fun onBingEvent(event: Event, roomState: RoomState, bingRule: BingRule) {
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.i("%%%%%%%% MXEventListener: the event $event")
}
Timber.i("prepareNotification : " + event.eventId + " in " + roomState.roomId)
val session = Matrix.getMXSession(applicationContext, event.matrixId)
// invalid session ?
// should never happen.
// But it could be triggered because of multi accounts management.
// The dedicated account is removing but some pushes are still received.
if (null == session || !session.isAlive) {
Timber.i("prepareNotification : don't bing - no session")
return
}
if (EventType.CALL_INVITE == event.getClearType()) {
handleCallInviteEvent(event)
return
}
val notifiableEvent = mNotifiableEventResolver!!.resolveEvent(event, roomState, bingRule, session)
if (notifiableEvent != null) {
VectorApp.getInstance().notificationDrawerManager.onNotifiableEventReceived(notifiableEvent)
}
}
override fun onLiveEventsChunkProcessed(fromToken: String, toToken: String) {
Timber.i("%%%%%%%% MXEventListener: onLiveEventsChunkProcessed[$fromToken->$toToken]")
VectorApp.getInstance().notificationDrawerManager.refreshNotificationDrawer(OutdatedEventDetector(this@EventStreamServiceX))
// do not suspend the application if there is some active calls
if (ServiceState.CATCHUP == serviceState) {
val hasActiveCalls = session?.mCallsManager?.hasActiveCalls() == true
// if there are some active calls, the catchup should not be stopped.
// because an user could answer to a call from another device.
// there will no push because it is his own message.
// so, the client has no choice to catchup until the ring is shutdown
if (hasActiveCalls) {
Timber.i("onLiveEventsChunkProcessed : Catchup again because there are active calls")
catchup(false)
} else if (ServiceState.CATCHUP == serviceState) {
Timber.i("onLiveEventsChunkProcessed : no Active call")
CallsManager.getSharedInstance().checkDeadCalls()
stop()
}
}
}
} */
/**
* Service internal state
*/
private enum class ServiceState {
// Initial state
INIT,
// Service is started for a Catchup. Once the catchup is finished the service will be stopped
CATCHUP,
// Service is started, and session is monitored
STARTED
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Cancel any previous worker
cancelAnySimulatedPushSchedule()
// no intent : restarted by Android
if (null == intent) {
// Cannot happen anymore
Timber.e("onStartCommand : null intent")
myStopSelf()
return START_NOT_STICKY
}
val action = intent.action
Timber.i("onStartCommand with action : $action (current state $serviceState)")
// Manage foreground notification
when (action) {
ACTION_BOOT_COMPLETE,
ACTION_APPLICATION_UPGRADE,
ACTION_SIMULATED_PUSH_RECEIVED -> {
// Display foreground notification
Timber.i("startForeground")
val notification = NotificationUtils.buildForegroundServiceNotification(this, R.string.notification_sync_in_progress)
startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
}
ACTION_GO_TO_FOREGROUND -> {
// Stop foreground notification display
Timber.i("stopForeground")
stopForeground(true)
}
}
if (null == mSession) {
Timber.e("onStartCommand : no sessions")
myStopSelf()
return START_NOT_STICKY
}
when (action) {
ACTION_START,
ACTION_GO_TO_FOREGROUND ->
when (serviceState) {
ServiceState.INIT ->
start(false)
ServiceState.CATCHUP ->
// A push has been received before, just change state, to avoid stopping the service when catchup is over
serviceState = ServiceState.STARTED
ServiceState.STARTED -> {
// Nothing to do
}
}
ACTION_STOP,
ACTION_GO_TO_BACKGROUND,
ACTION_LOGOUT ->
stop()
ACTION_PUSH_RECEIVED,
ACTION_SIMULATED_PUSH_RECEIVED ->
when (serviceState) {
ServiceState.INIT ->
start(true)
ServiceState.CATCHUP ->
catchup(true)
ServiceState.STARTED ->
// Nothing to do
Unit
}
ACTION_PUSH_UPDATE -> pushStatusUpdate()
ACTION_BOOT_COMPLETE -> {
// No FCM only
mSimulatePushImmediate = true
stop()
}
ACTION_APPLICATION_UPGRADE -> {
// FDroid only
catchup(true)
}
else -> {
// Should not happen
}
}
// We don't want the service to be restarted automatically by the System
return START_NOT_STICKY
}
override fun onDestroy() {
super.onDestroy()
// Schedule worker?
scheduleSimulatedPushIfNeeded()
}
/**
* Tell the WorkManager to cancel any schedule of push simulation
*/
private fun cancelAnySimulatedPushSchedule() {
WorkManager.getInstance().cancelAllWorkByTag(PUSH_SIMULATOR_REQUEST_TAG)
}
/**
* Configure the WorkManager to schedule a simulated push, if necessary
*/
private fun scheduleSimulatedPushIfNeeded() {
if (shouldISimulatePush()) {
val delay = if (mSimulatePushImmediate) 0 else 60_000 // TODO mPushManager?.backgroundSyncDelay ?: let { 60_000 }
Timber.i("## service is schedule to restart in $delay millis, if network is connected")
val pushSimulatorRequest = OneTimeWorkRequestBuilder<PushSimulatorWorker>()
.setInitialDelay(delay.toLong(), TimeUnit.MILLISECONDS)
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.addTag(PUSH_SIMULATOR_REQUEST_TAG)
.build()
WorkManager.getInstance().let {
// Cancel any previous worker
it.cancelAllWorkByTag(PUSH_SIMULATOR_REQUEST_TAG)
it.enqueue(pushSimulatorRequest)
}
}
}
/**
* Start the even stream.
*
* @param session the session
*/
private fun startEventStream(session: Session) {
/* TODO
// resume if it was only suspended
if (null != session.currentSyncToken) {
session.resumeEventStream()
} else {
session.startEventStream(store?.eventStreamToken)
}
*/
}
/**
* Monitor the provided session.
*
* @param session the session
*/
private fun monitorSession(session: Session) {
/* TODO
session.dataHandler.addListener(mEventsListener)
CallsManager.getSharedInstance().addSession(session)
val store = session.dataHandler.store
// the store is ready (no data loading in progress...)
if (store!!.isReady) {
startEventStream(session, store)
} else {
// wait that the store is ready before starting the events stream
store.addMXStoreListener(object : MXStoreListener() {
override fun onStoreReady(accountId: String) {
startEventStream(session, store)
store.removeMXStoreListener(this)
}
override fun onStoreCorrupted(accountId: String, description: String) {
// start a new initial sync
if (null == store.eventStreamToken) {
startEventStream(session, store)
} else {
// the data are out of sync
Matrix.getInstance(applicationContext)!!.reloadSessions(applicationContext)
}
store.removeMXStoreListener(this)
}
override fun onStoreOOM(accountId: String, description: String) {
val uiHandler = Handler(mainLooper)
uiHandler.post {
Toast.makeText(applicationContext, "$accountId : $description", Toast.LENGTH_LONG).show()
Matrix.getInstance(applicationContext)!!.reloadSessions(applicationContext)
}
}
})
store.open()
}
*/
}
/**
* internal start.
*/
private fun start(forPush: Boolean) {
val applicationContext = applicationContext
// TODO mPushManager = Matrix.getInstance(applicationContext)!!.pushManager
mNotifiableEventResolver = NotifiableEventResolver(applicationContext)
monitorSession(mSession!!)
serviceState = if (forPush) {
ServiceState.CATCHUP
} else {
ServiceState.STARTED
}
}
/**
* internal stop.
*/
private fun stop() {
Timber.i("## stop(): the service is stopped")
/* TODO
if (null != session && session!!.isAlive) {
session!!.stopEventStream()
session!!.dataHandler.removeListener(mEventsListener)
CallsManager.getSharedInstance().removeSession(session)
}
session = null
*/
// Stop the service
myStopSelf()
}
/**
* internal catchup method.
*
* @param checkState true to check if the current state allow to perform a catchup
*/
private fun catchup(checkState: Boolean) {
var canCatchup = true
if (!checkState) {
Timber.i("catchup without checking serviceState ")
} else {
Timber.i("catchup with serviceState " + serviceState + " CurrentActivity ") // TODO + VectorApp.getCurrentActivity())
/* TODO
// the catchup should only be done
// 1- the serviceState is in catchup : the event stream might have gone to sleep between two catchups
// 2- the thread is suspended
// 3- the application has been launched by a push so there is no displayed activity
canCatchup = (serviceState == ServiceState.CATCHUP
//|| (serviceState == ServiceState.PAUSE)
|| ServiceState.STARTED == serviceState && null == VectorApp.getCurrentActivity())
*/
}
if (canCatchup) {
if (mSession != null) {
// TODO session!!.catchupEventStream()
} else {
Timber.i("catchup no session")
}
serviceState = ServiceState.CATCHUP
} else {
Timber.i("No catchup is triggered because there is already a running event thread")
}
}
/**
* The push status has been updated (i.e disabled or enabled).
* TODO Useless now?
*/
private fun pushStatusUpdate() {
Timber.i("## pushStatusUpdate")
}
/* ==========================================================================================
* Push simulator
* ========================================================================================== */
/**
* @return true if the FCM is disable or not setup, user allowed background sync, user wants notification
*/
private fun shouldISimulatePush(): Boolean {
return false
/* TODO
if (Matrix.getInstance(applicationContext)?.defaultSession == null) {
Timber.i("## shouldISimulatePush: NO: no session")
return false
}
mPushManager?.let { pushManager ->
if (pushManager.useFcm()
&& !TextUtils.isEmpty(pushManager.currentRegistrationToken)
&& pushManager.isServerRegistered) {
// FCM is ok
Timber.i("## shouldISimulatePush: NO: FCM is up")
return false
}
if (!pushManager.isBackgroundSyncAllowed) {
// User has disabled background sync
Timber.i("## shouldISimulatePush: NO: background sync not allowed")
return false
}
if (!pushManager.areDeviceNotificationsAllowed()) {
// User does not want notifications
Timber.i("## shouldISimulatePush: NO: user does not want notification")
return false
}
}
// Lets simulate push
Timber.i("## shouldISimulatePush: YES")
return true
*/
}
//================================================================================
// Call management
//================================================================================
private fun handleCallInviteEvent(event: Event) {
/*
TODO
val session = Matrix.getMXSession(applicationContext, event.matrixId)
// invalid session ?
// should never happen.
// But it could be triggered because of multi accounts management.
// The dedicated account is removing but some pushes are still received.
if (null == session || !session.isAlive) {
Timber.v("prepareCallNotification : don't bing - no session")
return
}
val room: Room? = session.dataHandler.getRoom(event.roomId)
// invalid room ?
if (null == room) {
Timber.i("prepareCallNotification : don't bing - the room does not exist")
return
}
var callId: String? = null
var isVideo = false
try {
callId = event.contentAsJsonObject?.get("call_id")?.asString
// Check if it is a video call
val offer = event.contentAsJsonObject?.get("offer")?.asJsonObject
val sdp = offer?.get("sdp")
val sdpValue = sdp?.asString
isVideo = sdpValue?.contains("m=video") == true
} catch (e: Exception) {
Timber.e(e, "prepareNotification : getContentAsJsonObject")
}
if (!TextUtils.isEmpty(callId)) {
CallService.onIncomingCall(this,
isVideo,
room.getRoomDisplayName(this),
room.roomId,
session.myUserId!!,
callId!!)
}
*/
}
companion object {
private const val PUSH_SIMULATOR_REQUEST_TAG = "PUSH_SIMULATOR_REQUEST_TAG"
private const val ACTION_START = "im.vector.riotredesign.core.services.EventStreamServiceX.START"
private const val ACTION_LOGOUT = "im.vector.riotredesign.core.services.EventStreamServiceX.LOGOUT"
private const val ACTION_GO_TO_FOREGROUND = "im.vector.riotredesign.core.services.EventStreamServiceX.GO_TO_FOREGROUND"
private const val ACTION_GO_TO_BACKGROUND = "im.vector.riotredesign.core.services.EventStreamServiceX.GO_TO_BACKGROUND"
private const val ACTION_PUSH_UPDATE = "im.vector.riotredesign.core.services.EventStreamServiceX.PUSH_UPDATE"
private const val ACTION_PUSH_RECEIVED = "im.vector.riotredesign.core.services.EventStreamServiceX.PUSH_RECEIVED"
private const val ACTION_SIMULATED_PUSH_RECEIVED = "im.vector.riotredesign.core.services.EventStreamServiceX.SIMULATED_PUSH_RECEIVED"
private const val ACTION_STOP = "im.vector.riotredesign.core.services.EventStreamServiceX.STOP"
private const val ACTION_BOOT_COMPLETE = "im.vector.riotredesign.core.services.EventStreamServiceX.BOOT_COMPLETE"
private const val ACTION_APPLICATION_UPGRADE = "im.vector.riotredesign.core.services.EventStreamServiceX.APPLICATION_UPGRADE"
/* ==========================================================================================
* Events sent to the service
* ========================================================================================== */
fun onApplicationStarted(context: Context) {
sendAction(context, ACTION_START)
}
fun onLogout(context: Context) {
sendAction(context, ACTION_LOGOUT)
}
fun onAppGoingToForeground(context: Context) {
sendAction(context, ACTION_GO_TO_FOREGROUND)
}
fun onAppGoingToBackground(context: Context) {
sendAction(context, ACTION_GO_TO_BACKGROUND)
}
fun onPushUpdate(context: Context) {
sendAction(context, ACTION_PUSH_UPDATE)
}
fun onPushReceived(context: Context) {
sendAction(context, ACTION_PUSH_RECEIVED)
}
fun onSimulatedPushReceived(context: Context) {
sendAction(context, ACTION_SIMULATED_PUSH_RECEIVED, true)
}
fun onApplicationStopped(context: Context) {
sendAction(context, ACTION_STOP)
}
fun onBootComplete(context: Context) {
sendAction(context, ACTION_BOOT_COMPLETE, true)
}
fun onApplicationUpgrade(context: Context) {
sendAction(context, ACTION_APPLICATION_UPGRADE, true)
}
private fun sendAction(context: Context, action: String, foreground: Boolean = false) {
Timber.i("sendAction $action")
val intent = Intent(context, EventStreamServiceX::class.java)
intent.action = action
if (foreground) {
ContextCompat.startForegroundService(context, intent)
} else {
context.startService(intent)
}
}
}
}

View File

@ -1,102 +0,0 @@
//package im.vector.riotredesign.core.services
//
//import android.app.NotificationManager
//import android.content.Context
//import android.content.Intent
//import android.os.Build.VERSION.SDK_INT
//import android.os.Build.VERSION_CODES
//import android.os.Handler
//import android.os.HandlerThread
//import android.os.Looper
//import androidx.core.content.ContextCompat.startForegroundService
//import im.vector.matrix.android.api.Matrix
//import im.vector.matrix.android.api.session.Session
//import im.vector.riotredesign.R
//import im.vector.riotredesign.features.notifications.NotificationUtils
//import timber.log.Timber
//import java.net.HttpURLConnection
//import java.net.URL
//
//
///**
// *
// * This is used to display message notifications to the user when Push is not enabled (or not configured)
// *
// * This service is used to implement a long pooling mechanism in order to get messages from
// * the home server when the user is not interacting with the app.
// *
// * It is intended to be started when the app enters background, and stopped when app is in foreground.
// *
// * When in foreground, the app uses another mechanism to get messages (doing sync wia a thread).
// *
// */
//class HttpLongPoolingSyncService : VectorService() {
//
// private var mServiceLooper: Looper? = null
// private var mHandler: Handler? = null
// private val currentSessions = ArrayList<Session>()
// private var mCount = 0
// private var lastTimeMs = System.currentTimeMillis()
//
// lateinit var myRun: () -> Unit
// override fun onCreate() {
// //Add the permanent listening notification
// super.onCreate()
//
// if (SDK_INT >= VERSION_CODES.O) {
// val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// val notification = NotificationUtils.buildForegroundServiceNotification(applicationContext, R.string.notification_listening_for_events, false)
// startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
// }
// val thread = HandlerThread("My service Handler")
// thread.start()
//
// mServiceLooper = thread.looper
// mHandler = Handler(mServiceLooper)
// myRun = {
// val diff = System.currentTimeMillis() - lastTimeMs
// lastTimeMs = System.currentTimeMillis()
// val isAlive = Matrix.getInstance().currentSession?.isSyncThreadAlice()
// val state = Matrix.getInstance().currentSession?.syncThreadState()
// Timber.w(" timeDiff[${diff/1000}] Yo me here $mCount, sync thread is Alive? $isAlive, state:$state")
// mCount++
// mHandler?.postDelayed(Runnable { myRun() }, 10_000L)
// }
// }
//
// override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// //START_STICKY mode makes sense for things that will be explicitly started
// //and stopped to run for arbitrary periods of time
//
// mHandler?.post {
// myRun()
// }
// return START_STICKY
// }
//
//
// override fun onDestroy() {
// //TODO test if this service should be relaunched (preference)
// Timber.i("Service is destroyed, relaunch asap")
// Intent(applicationContext, RestartBroadcastReceiver::class.java).also { sendBroadcast(it) }
// super.onDestroy()
// }
//
// companion object {
//
// fun startService(context: Context) {
// Timber.i("Start sync service")
// val intent = Intent(context, HttpLongPoolingSyncService::class.java)
// try {
// if (SDK_INT >= VERSION_CODES.O) {
// startForegroundService(context, intent)
// } else {
// context.startService(intent)
// }
// } catch (ex: Throwable) {
// //TODO
// Timber.e(ex)
// }
// }
// }
//}

View File

@ -1,36 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.core.services
import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
/**
* This class simulate push event when FCM is not working/disabled
*/
class PushSimulatorWorker(val context: Context,
workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
// Simulate a Push
EventStreamServiceX.onSimulatedPushReceived(context)
// Indicate whether the task finished successfully with the Result
return Result.success()
}
}

View File

@ -1,37 +0,0 @@
package im.vector.riotredesign.core.services
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import androidx.core.content.ContextCompat
import androidx.legacy.content.WakefulBroadcastReceiver
import im.vector.matrix.android.internal.session.sync.job.SyncService
import timber.log.Timber
class RestartBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// This method is called when the BroadcastReceiver is receiving an Intent broadcast.
Timber.d("RestartBroadcastReceiver received intent")
Intent(context,VectorSyncService::class.java).also {
it.action = "SLOW"
context.startService(it)
try {
if (SDK_INT >= Build.VERSION_CODES.O) {
ContextCompat.startForegroundService(context, intent)
} else {
context.startService(intent)
}
} catch (ex: Throwable) {
//TODO
Timber.e(ex)
}
}
}
companion object {
const val REQUEST_CODE = 0
}
}

View File

@ -76,9 +76,7 @@ class LoginActivity : VectorBaseActivity() {
Matrix.getInstance().currentSession = data
data.open()
data.setFilter(FilterService.FilterPreset.RiotFilter)
//TODO sync
// data.shoudPauseOnBackground(false)
// data.startSync()
data.startSync()
get<PushRuleTriggerListener>().startWithSession(data)
goToHome()
}

View File

@ -36,7 +36,7 @@ import java.io.FileOutputStream
* organise them in order to display them in the notification drawer.
* Events can be grouped into the same notification, old (already read) events can be removed to do some cleaning.
*/
class NotificationDrawerManager(val context: Context) {
class NotificationDrawerManager(val context: Context, private val outdatedDetector: OutdatedEventDetector?) {
//The first time the notification drawer is refreshed, we force re-render of all notifications
private var firstTime = true
@ -53,7 +53,7 @@ class NotificationDrawerManager(val context: Context) {
object : IconLoader.IconLoaderListener {
override fun onIconsLoaded() {
// Force refresh
refreshNotificationDrawer(null)
refreshNotificationDrawer()
}
})
@ -123,7 +123,7 @@ class NotificationDrawerManager(val context: Context) {
synchronized(eventList) {
eventList.clear()
}
refreshNotificationDrawer(null)
refreshNotificationDrawer()
}
/** Clear all known message events for this room and refresh the notification drawer */
@ -139,7 +139,7 @@ class NotificationDrawerManager(val context: Context) {
}
NotificationUtils.cancelNotificationMessage(context, roomId, ROOM_MESSAGES_NOTIFICATION_ID)
}
refreshNotificationDrawer(null)
refreshNotificationDrawer()
}
/**
@ -177,7 +177,7 @@ class NotificationDrawerManager(val context: Context) {
}
fun refreshNotificationDrawer(outdatedDetector: OutdatedEventDetector?) {
fun refreshNotificationDrawer() {
if (myUserDisplayName.isBlank()) {
// TODO
// initWithSession(Matrix.getInstance(context).defaultSession)

View File

@ -16,6 +16,7 @@
package im.vector.riotredesign.features.notifications
import android.content.Context
import im.vector.matrix.android.api.Matrix
class OutdatedEventDetector(val context: Context) {
@ -28,20 +29,9 @@ class OutdatedEventDetector(val context: Context) {
if (notifiableEvent is NotifiableMessageEvent) {
val eventID = notifiableEvent.eventId
val roomID = notifiableEvent.roomId
/*
TODO
Matrix.getMXSession(context.applicationContext, notifiableEvent.matrixID)?.let { session ->
//find the room
if (session.isAlive) {
session.dataHandler.getRoom(roomID)?.let { room ->
if (room.isEventRead(eventID)) {
Timber.v("Notifiable Event $eventID is read, and should be removed")
return true
}
}
}
}
*/
val session = Matrix.getInstance().currentSession ?: return false
val room = session.getRoom(roomID) ?: return false
return room.isEventRead(eventID)
}
return false
}

View File

@ -26,7 +26,7 @@ class PushRuleTriggerListener(
}
override fun batchFinish() {
drawerManager.refreshNotificationDrawer(null)
drawerManager.refreshNotificationDrawer()
}
fun startWithSession(session: Session) {
@ -41,6 +41,6 @@ class PushRuleTriggerListener(
session?.removePushRuleListener(this)
session = null
drawerManager.clearAllEvents()
drawerManager.refreshNotificationDrawer(null)
drawerManager.refreshNotificationDrawer()
}
}