diff --git a/changelog.d/6548.feature b/changelog.d/6548.feature
new file mode 100644
index 0000000000..8c40a37063
--- /dev/null
+++ b/changelog.d/6548.feature
@@ -0,0 +1 @@
+Move initialization of the Session to a background thread. MainActivity is restoring the session now, instead of VectorApplication. Useful when for instance a long migration of a database is required.
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index 1c104f3bbf..b7bdac6879 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -380,6 +380,11 @@
android:exported="false"
android:foregroundServiceType="location" />
+
+
+ @Binds
+ @IntoMap
+ @MavericksViewModelKey(StartAppViewModel::class)
+ fun startAppViewModelFactory(factory: StartAppViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
+
@Binds
@IntoMap
@MavericksViewModelKey(HomeServerCapabilitiesViewModel::class)
diff --git a/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt b/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt
index 53a5470ff7..be84dfeaba 100644
--- a/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt
+++ b/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt
@@ -27,6 +27,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.BuildConfig
import im.vector.app.core.di.ActiveSessionHolder
+import im.vector.app.core.di.ActiveSessionSetter
import im.vector.app.core.network.WifiDetector
import im.vector.app.core.pushers.model.PushData
import im.vector.app.core.services.GuardServiceStarter
@@ -59,6 +60,7 @@ class VectorMessagingReceiver : MessagingReceiver() {
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
@Inject lateinit var notifiableEventResolver: NotifiableEventResolver
@Inject lateinit var pushersManager: PushersManager
+ @Inject lateinit var activeSessionSetter: ActiveSessionSetter
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var vectorDataStore: VectorDataStore
@@ -177,6 +179,11 @@ class VectorMessagingReceiver : MessagingReceiver() {
}
val session = activeSessionHolder.getSafeActiveSession()
+ ?: run {
+ // Active session may not exist yet, if MainActivity has not been launched
+ activeSessionSetter.tryToSetActiveSession(startSync = false)
+ activeSessionHolder.getSafeActiveSession()
+ }
if (session == null) {
Timber.tag(loggerTag.value).w("## Can't sync from push, no current session")
diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt
index c2f6f2d778..61127e2c82 100644
--- a/vector/src/main/java/im/vector/app/features/MainActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt
@@ -17,11 +17,15 @@
package im.vector.app.features
import android.app.Activity
+import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
+import androidx.core.content.ContextCompat
+import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
+import com.airbnb.mvrx.viewModel
import com.bumptech.glide.Glide
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
@@ -44,9 +48,16 @@ import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.session.VectorSessionStore
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.signout.hard.SignedOutActivity
+import im.vector.app.features.start.StartAppAction
+import im.vector.app.features.start.StartAppAndroidService
+import im.vector.app.features.start.StartAppViewEvent
+import im.vector.app.features.start.StartAppViewModel
+import im.vector.app.features.start.StartAppViewState
import im.vector.app.features.themes.ActivityOtherThemes
import im.vector.app.features.ui.UiStateRepository
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
@@ -73,6 +84,8 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
companion object {
private const val EXTRA_ARGS = "EXTRA_ARGS"
+ private const val EXTRA_NEXT_INTENT = "EXTRA_NEXT_INTENT"
+ private const val EXTRA_INIT_SESSION = "EXTRA_INIT_SESSION"
// Special action to clear cache and/or clear credentials
fun restartApp(activity: Activity, args: MainActivityArgs) {
@@ -82,8 +95,22 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
intent.putExtra(EXTRA_ARGS, args)
activity.startActivity(intent)
}
+
+ fun getIntentToInitSession(activity: Activity): Intent {
+ val intent = Intent(activity, MainActivity::class.java)
+ intent.putExtra(EXTRA_INIT_SESSION, true)
+ return intent
+ }
+
+ fun getIntentWithNextIntent(context: Context, nextIntent: Intent): Intent {
+ val intent = Intent(context, MainActivity::class.java)
+ intent.putExtra(EXTRA_NEXT_INTENT, nextIntent)
+ return intent
+ }
}
+ private val startAppViewModel: StartAppViewModel by viewModel()
+
override fun getBinding() = ActivityMainBinding.inflate(layoutInflater)
override fun getOtherThemes() = ActivityOtherThemes.Launcher
@@ -103,15 +130,58 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- args = parseArgs()
- if (args.clearCredentials || args.isUserLoggedOut || args.clearCache) {
- clearNotifications()
+
+ startAppViewModel.onEach {
+ renderState(it)
}
- // Handle some wanted cleanup
- if (args.clearCache || args.clearCredentials) {
- doCleanUp()
+ startAppViewModel.viewEvents.stream()
+ .onEach(::handleViewEvents)
+ .launchIn(lifecycleScope)
+
+ startAppViewModel.handle(StartAppAction.StartApp)
+ }
+
+ private fun renderState(state: StartAppViewState) {
+ if (state.mayBeLongToProcess) {
+ views.status.setText(R.string.updating_your_data)
+ }
+ views.status.isVisible = state.mayBeLongToProcess
+ }
+
+ private fun handleViewEvents(event: StartAppViewEvent) {
+ when (event) {
+ StartAppViewEvent.StartForegroundService -> handleStartForegroundService()
+ StartAppViewEvent.AppStarted -> handleAppStarted()
+ }
+ }
+
+ private fun handleStartForegroundService() {
+ if (startAppViewModel.shouldStartApp()) {
+ // Start foreground service, because the operation may take a while
+ val intent = Intent(this, StartAppAndroidService::class.java)
+ ContextCompat.startForegroundService(this, intent)
+ }
+ }
+
+ private fun handleAppStarted() {
+ if (intent.hasExtra(EXTRA_NEXT_INTENT)) {
+ // Start the next Activity
+ val nextIntent = intent.getParcelableExtra(EXTRA_NEXT_INTENT)
+ startIntentAndFinish(nextIntent)
+ } else if (intent.hasExtra(EXTRA_INIT_SESSION)) {
+ setResult(RESULT_OK)
+ finish()
} else {
- startNextActivityAndFinish()
+ args = parseArgs()
+ if (args.clearCredentials || args.isUserLoggedOut || args.clearCache) {
+ clearNotifications()
+ }
+ // Handle some wanted cleanup
+ if (args.clearCache || args.clearCredentials) {
+ doCleanUp()
+ } else {
+ startNextActivityAndFinish()
+ }
}
}
@@ -241,7 +311,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
// We have a session.
// Check it can be opened
if (sessionHolder.getActiveSession().isOpenable) {
- HomeActivity.newIntent(this, existingSession = true)
+ HomeActivity.newIntent(this, firstStartMainActivity = false, existingSession = true)
} else {
// The token is still invalid
navigator.softLogout(this)
@@ -253,6 +323,10 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
null
}
}
+ startIntentAndFinish(intent)
+ }
+
+ private fun startIntentAndFinish(intent: Intent?) {
intent?.let { startActivity(it) }
finish()
}
diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
index 9d7ada9d63..f8a4c5eeca 100644
--- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
@@ -604,7 +604,7 @@ class VectorCallActivity :
private fun returnToChat() {
val roomId = withState(callViewModel) { it.roomId }
val args = TimelineArgs(roomId)
- val intent = RoomDetailActivity.newIntent(this, args).apply {
+ val intent = RoomDetailActivity.newIntent(this, args, false).apply {
flags = FLAG_ACTIVITY_CLEAR_TOP
}
startActivity(intent)
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
index 926c1eb113..ff114e4db9 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
@@ -611,6 +611,7 @@ class HomeActivity :
companion object {
fun newIntent(
context: Context,
+ firstStartMainActivity: Boolean,
clearNotification: Boolean = false,
authenticationDescription: AuthenticationDescription? = null,
existingSession: Boolean = false,
@@ -623,10 +624,16 @@ class HomeActivity :
inviteNotificationRoomId = inviteNotificationRoomId
)
- return Intent(context, HomeActivity::class.java)
+ val intent = Intent(context, HomeActivity::class.java)
.apply {
putExtra(Mavericks.KEY_ARG, args)
}
+
+ return if (firstStartMainActivity) {
+ MainActivity.getIntentWithNextIntent(context, intent)
+ } else {
+ intent
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
index f1e06dd5ef..a58eed42e1 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
@@ -35,6 +35,7 @@ import im.vector.app.core.extensions.keepScreenOn
import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityRoomDetailBinding
+import im.vector.app.features.MainActivity
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.analytics.plan.ViewRoom
import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
@@ -191,10 +192,15 @@ class RoomDetailActivity :
const val EXTRA_ROOM_ID = "EXTRA_ROOM_ID"
const val ACTION_ROOM_DETAILS_FROM_SHORTCUT = "ROOM_DETAILS_FROM_SHORTCUT"
- fun newIntent(context: Context, timelineArgs: TimelineArgs): Intent {
- return Intent(context, RoomDetailActivity::class.java).apply {
+ fun newIntent(context: Context, timelineArgs: TimelineArgs, firstStartMainActivity: Boolean): Intent {
+ val intent = Intent(context, RoomDetailActivity::class.java).apply {
putExtra(EXTRA_ROOM_DETAIL_ARGS, timelineArgs)
}
+ return if (firstStartMainActivity) {
+ MainActivity.getIntentWithNextIntent(context, intent)
+ } else {
+ intent
+ }
}
// Shortcuts can't have intents with parcelables
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
index 31c1004ef9..562f2d4aea 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
@@ -1206,9 +1206,9 @@ class TimelineFragment @Inject constructor(
getRootThreadEventId()?.let {
val newRoom = timelineArgs.copy(threadTimelineArgs = null, eventId = it)
context?.let { con ->
- val int = RoomDetailActivity.newIntent(con, newRoom)
- int.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
- con.startActivity(int)
+ val intent = RoomDetailActivity.newIntent(con, newRoom, false)
+ intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
+ con.startActivity(intent)
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt
index 6de73cb20f..0bdec53f60 100644
--- a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt
@@ -18,18 +18,23 @@ package im.vector.app.features.link
import android.content.Intent
import android.net.Uri
+import android.os.Bundle
import androidx.lifecycle.lifecycleScope
+import com.airbnb.mvrx.viewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.error.ErrorFormatter
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.toast
import im.vector.app.databinding.ActivityProgressBinding
+import im.vector.app.features.MainActivity
import im.vector.app.features.home.HomeActivity
import im.vector.app.features.login.LoginConfig
import im.vector.app.features.permalink.PermalinkHandler
+import im.vector.app.features.start.StartAppViewModel
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import timber.log.Timber
@@ -45,12 +50,33 @@ class LinkHandlerActivity : VectorBaseActivity() {
@Inject lateinit var errorFormatter: ErrorFormatter
@Inject lateinit var permalinkHandler: PermalinkHandler
+ private val startAppViewModel: StartAppViewModel by viewModel()
+
override fun getBinding() = ActivityProgressBinding.inflate(layoutInflater)
override fun initUiAndData() {
handleIntent()
}
+ private val launcher = registerStartForActivityResult {
+ if (it.resultCode == RESULT_OK) {
+ handleIntent()
+ } else {
+ // User has pressed back on the MainActivity, so finish also this one.
+ finish()
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ if (startAppViewModel.shouldStartApp()) {
+ launcher.launch(MainActivity.getIntentToInitSession(this))
+ } else {
+ handleIntent()
+ }
+ }
+
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
handleIntent()
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
index 4cbebd67a3..763d1eed38 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
@@ -221,7 +221,7 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA
analyticsScreenName = MobileScreen.ScreenName.Register
}
val authDescription = inferAuthDescription(loginViewState)
- val intent = HomeActivity.newIntent(this, authenticationDescription = authDescription)
+ val intent = HomeActivity.newIntent(this, firstStartMainActivity = false, authenticationDescription = authDescription)
startActivity(intent)
finish()
return
diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
index 291eee307f..7680b40506 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
@@ -173,7 +173,7 @@ class DefaultNavigator @Inject constructor(
}
val args = TimelineArgs(roomId = roomId, eventId = eventId, isInviteAlreadyAccepted = isInviteAlreadyAccepted)
- val intent = RoomDetailActivity.newIntent(context, args)
+ val intent = RoomDetailActivity.newIntent(context, args, false)
startActivity(context, intent, buildTask)
}
@@ -203,7 +203,7 @@ class DefaultNavigator @Inject constructor(
eventId = null,
openShareSpaceForId = spaceId.takeIf { postSwitchSpaceAction.showShareSheet }
)
- val intent = RoomDetailActivity.newIntent(context, args)
+ val intent = RoomDetailActivity.newIntent(context, args, false)
startActivity(context, intent, false)
}
}
@@ -290,7 +290,7 @@ class DefaultNavigator @Inject constructor(
override fun openRoomForSharingAndFinish(activity: Activity, roomId: String, sharedData: SharedData) {
val args = TimelineArgs(roomId, null, sharedData)
- val intent = RoomDetailActivity.newIntent(activity, args)
+ val intent = RoomDetailActivity.newIntent(activity, args, false)
activity.startActivity(intent)
activity.finish()
}
diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
index 71c8167788..2948565d58 100755
--- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
@@ -53,6 +53,7 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.core.services.CallAndroidService
import im.vector.app.core.time.Clock
import im.vector.app.core.utils.startNotificationChannelSettingsIntent
+import im.vector.app.features.MainActivity
import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.service.CallHeadsUpActionReceiver
import im.vector.app.features.call.webrtc.WebRtcCall
@@ -239,9 +240,10 @@ class NotificationUtils @Inject constructor(
@SuppressLint("NewApi")
fun buildForegroundServiceNotification(@StringRes subTitleResId: Int, withProgress: Boolean = true): Notification {
// build the pending intent go to the home screen if this is clicked.
- val i = HomeActivity.newIntent(context)
+ val i = HomeActivity.newIntent(context, firstStartMainActivity = false)
i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
- val pi = PendingIntent.getActivity(context, 0, i, PendingIntentCompat.FLAG_IMMUTABLE)
+ val mainIntent = MainActivity.getIntentWithNextIntent(context, i)
+ val pi = PendingIntent.getActivity(context, 0, mainIntent, PendingIntentCompat.FLAG_IMMUTABLE)
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
@@ -344,7 +346,7 @@ class NotificationUtils @Inject constructor(
)
val answerCallPendingIntent = TaskStackBuilder.create(context)
- .addNextIntentWithParentStack(HomeActivity.newIntent(context))
+ .addNextIntentWithParentStack(HomeActivity.newIntent(context, firstStartMainActivity = false))
.addNextIntent(
VectorCallActivity.newIntent(
context = context,
@@ -468,7 +470,7 @@ class NotificationUtils @Inject constructor(
)
val contentPendingIntent = TaskStackBuilder.create(context)
- .addNextIntentWithParentStack(HomeActivity.newIntent(context))
+ .addNextIntentWithParentStack(HomeActivity.newIntent(context, firstStartMainActivity = false))
.addNextIntent(VectorCallActivity.newIntent(context, call, null))
.getPendingIntent(clock.epochMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE)
@@ -530,8 +532,8 @@ class NotificationUtils @Inject constructor(
.setCategory(NotificationCompat.CATEGORY_CALL)
val contentPendingIntent = TaskStackBuilder.create(context)
- .addNextIntentWithParentStack(HomeActivity.newIntent(context))
- .addNextIntent(RoomDetailActivity.newIntent(context, TimelineArgs(callInformation.nativeRoomId)))
+ .addNextIntentWithParentStack(HomeActivity.newIntent(context, firstStartMainActivity = false))
+ .addNextIntent(RoomDetailActivity.newIntent(context, TimelineArgs(callInformation.nativeRoomId), true))
.getPendingIntent(clock.epochMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE)
builder.setContentIntent(contentPendingIntent)
@@ -566,6 +568,19 @@ class NotificationUtils @Inject constructor(
.build()
}
+ /**
+ * Creates a notification that indicates the application is initializing.
+ */
+ fun buildStartAppNotification(): Notification {
+ return NotificationCompat.Builder(context, LISTENING_FOR_EVENTS_NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(stringProvider.getString(R.string.updating_your_data))
+ .setSmallIcon(R.drawable.sync)
+ .setColor(ThemeUtils.getColor(context, android.R.attr.colorPrimary))
+ .setCategory(NotificationCompat.CATEGORY_SERVICE)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .build()
+ }
+
fun buildDownloadFileNotification(uri: Uri, fileName: String, mimeType: String): Notification {
return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID)
.setGroup(stringProvider.getString(R.string.app_name))
@@ -765,7 +780,11 @@ class NotificationUtils @Inject constructor(
joinIntentPendingIntent
)
- val contentIntent = HomeActivity.newIntent(context, inviteNotificationRoomId = inviteNotifiableEvent.roomId)
+ val contentIntent = HomeActivity.newIntent(
+ context,
+ firstStartMainActivity = true,
+ inviteNotificationRoomId = inviteNotifiableEvent.roomId
+ )
contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
// pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that
contentIntent.data = createIgnoredUri(inviteNotifiableEvent.eventId)
@@ -806,7 +825,7 @@ class NotificationUtils @Inject constructor(
.setColor(accentColor)
.setAutoCancel(true)
.apply {
- val contentIntent = HomeActivity.newIntent(context)
+ val contentIntent = HomeActivity.newIntent(context, firstStartMainActivity = true)
contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
// pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that
contentIntent.data = createIgnoredUri(simpleNotifiableEvent.eventId)
@@ -828,14 +847,14 @@ class NotificationUtils @Inject constructor(
}
private fun buildOpenRoomIntent(roomId: String): PendingIntent? {
- val roomIntentTap = RoomDetailActivity.newIntent(context, TimelineArgs(roomId = roomId, switchToParentSpace = true))
+ val roomIntentTap = RoomDetailActivity.newIntent(context, TimelineArgs(roomId = roomId, switchToParentSpace = true), true)
roomIntentTap.action = TAP_TO_VIEW_ACTION
// pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that
roomIntentTap.data = createIgnoredUri("openRoom?$roomId")
// Recreate the back stack
return TaskStackBuilder.create(context)
- .addNextIntentWithParentStack(HomeActivity.newIntent(context))
+ .addNextIntentWithParentStack(HomeActivity.newIntent(context, firstStartMainActivity = false))
.addNextIntent(roomIntentTap)
.getPendingIntent(
clock.epochMillis().toInt(),
@@ -844,13 +863,14 @@ class NotificationUtils @Inject constructor(
}
private fun buildOpenHomePendingIntentForSummary(): PendingIntent {
- val intent = HomeActivity.newIntent(context, clearNotification = true)
+ val intent = HomeActivity.newIntent(context, firstStartMainActivity = false, clearNotification = true)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
intent.data = createIgnoredUri("tapSummary")
+ val mainIntent = MainActivity.getIntentWithNextIntent(context, intent)
return PendingIntent.getActivity(
context,
Random.nextInt(1000),
- intent,
+ mainIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
)
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt
index 0d7c83e360..7def6d62f0 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt
@@ -302,7 +302,8 @@ class Login2Variant(
private fun terminate() {
val intent = HomeActivity.newIntent(
- activity
+ activity,
+ firstStartMainActivity = false,
)
activity.startActivity(intent)
activity.finish()
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
index bb8c523b5f..867ab45834 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
@@ -482,7 +482,11 @@ class FtueAuthVariant(
private fun navigateToHome() {
withState(onboardingViewModel) {
- val intent = HomeActivity.newIntent(activity, authenticationDescription = it.selectedAuthenticationState.description)
+ val intent = HomeActivity.newIntent(
+ activity,
+ firstStartMainActivity = false,
+ authenticationDescription = it.selectedAuthenticationState.description
+ )
activity.startActivity(intent)
activity.finish()
}
diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareAction.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareAction.kt
index 4e9f024b15..70be2b2b6d 100644
--- a/vector/src/main/java/im/vector/app/features/share/IncomingShareAction.kt
+++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareAction.kt
@@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
sealed class IncomingShareAction : VectorViewModelAction {
data class SelectRoom(val roomSummary: RoomSummary, val enableMultiSelect: Boolean) : IncomingShareAction()
object ShareToSelectedRooms : IncomingShareAction()
- data class ShareToRoom(val roomSummary: RoomSummary) : IncomingShareAction()
+ data class ShareToRoom(val roomId: String) : IncomingShareAction()
data class ShareMedia(val keepOriginalSize: Boolean) : IncomingShareAction()
data class FilterWith(val filter: String) : IncomingShareAction()
data class UpdateSharedData(val sharedData: SharedData) : IncomingShareAction()
diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt
index 439d9b64fa..3d603e3f6a 100644
--- a/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt
@@ -16,21 +16,66 @@
package im.vector.app.features.share
+import android.content.Intent
+import android.os.Bundle
+import com.airbnb.mvrx.viewModel
import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.addFragment
+import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding
+import im.vector.app.features.MainActivity
+import im.vector.app.features.start.StartAppViewModel
+import javax.inject.Inject
@AndroidEntryPoint
class IncomingShareActivity : VectorBaseActivity() {
+ private val startAppViewModel: StartAppViewModel by viewModel()
+
+ @Inject lateinit var activeSessionHolder: ActiveSessionHolder
+
+ private val launcher = registerStartForActivityResult {
+ if (it.resultCode == RESULT_OK) {
+ handleAppStarted()
+ } else {
+ // User has pressed back on the MainActivity, so finish also this one.
+ finish()
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ if (startAppViewModel.shouldStartApp()) {
+ launcher.launch(MainActivity.getIntentToInitSession(this))
+ } else {
+ handleAppStarted()
+ }
+ }
+
override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
override fun getCoordinatorLayout() = views.coordinatorLayout
- override fun initUiAndData() {
- if (isFirstCreation()) {
- addFragment(views.simpleFragmentContainer, IncomingShareFragment::class.java)
+ private fun handleAppStarted() {
+ // If we are not logged in, stop the sharing process and open login screen.
+ // In the future, we might want to relaunch the sharing process after login.
+ if (!activeSessionHolder.hasActiveSession()) {
+ startLoginActivity()
+ } else {
+ if (isFirstCreation()) {
+ addFragment(views.simpleFragmentContainer, IncomingShareFragment::class.java)
+ }
}
}
+
+ private fun startLoginActivity() {
+ navigator.openLogin(
+ context = this,
+ flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
+ )
+ finish()
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt
index 3f8923dd68..3e2ddc469c 100644
--- a/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt
@@ -30,7 +30,6 @@ import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R
-import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.registerStartForActivityResult
@@ -40,7 +39,6 @@ import im.vector.app.features.analytics.plan.ViewRoom
import im.vector.app.features.attachments.ShareIntentHandler
import im.vector.app.features.attachments.preview.AttachmentsPreviewActivity
import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs
-import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import javax.inject.Inject
@@ -50,7 +48,6 @@ import javax.inject.Inject
*/
class IncomingShareFragment @Inject constructor(
private val incomingShareController: IncomingShareController,
- private val sessionHolder: ActiveSessionHolder,
private val shareIntentHandler: ShareIntentHandler,
) :
VectorBaseFragment(),
@@ -63,12 +60,6 @@ class IncomingShareFragment @Inject constructor(
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- // If we are not logged in, stop the sharing process and open login screen.
- // In the future, we might want to relaunch the sharing process after login.
- if (!sessionHolder.hasActiveSession()) {
- startLoginActivity()
- return
- }
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
setupToolbar(views.incomingShareToolbar)
@@ -88,7 +79,7 @@ class IncomingShareFragment @Inject constructor(
// Direct share
if (intent.hasExtra(Intent.EXTRA_SHORTCUT_ID)) {
val roomId = intent.getStringExtra(Intent.EXTRA_SHORTCUT_ID)!!
- sessionHolder.getSafeActiveSession()?.getRoomSummary(roomId)?.let { viewModel.handle(IncomingShareAction.ShareToRoom(it)) }
+ viewModel.handle(IncomingShareAction.ShareToRoom(roomId))
}
isShareManaged
}
@@ -192,14 +183,6 @@ class IncomingShareFragment @Inject constructor(
.show()
}
- private fun startLoginActivity() {
- navigator.openLogin(
- context = requireActivity(),
- flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
- )
- requireActivity().finish()
- }
-
override fun invalidate() = withState(viewModel) {
views.sendShareButton.isVisible = it.isInMultiSelectionMode
incomingShareController.setData(it)
diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt
index 1191fd04e8..85629ea150 100644
--- a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt
@@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import org.matrix.android.sdk.api.session.getRoom
+import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.flow.flow
@@ -134,7 +135,8 @@ class IncomingShareViewModel @AssistedInject constructor(
private fun handleShareToRoom(action: IncomingShareAction.ShareToRoom) = withState { state ->
val sharedData = state.sharedData ?: return@withState
- _viewEvents.post(IncomingShareViewEvents.ShareToRoom(action.roomSummary, sharedData, showAlert = false))
+ val roomSummary = session.getRoomSummary(action.roomId) ?: return@withState
+ _viewEvents.post(IncomingShareViewEvents.ShareToRoom(roomSummary, sharedData, showAlert = false))
}
private fun handleShareMediaToSelectedRooms(action: IncomingShareAction.ShareMedia) = withState { state ->
diff --git a/vector/src/main/java/im/vector/app/features/start/StartAppAction.kt b/vector/src/main/java/im/vector/app/features/start/StartAppAction.kt
new file mode 100644
index 0000000000..fffb124f12
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/start/StartAppAction.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2022 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.app.features.start
+
+import im.vector.app.core.platform.VectorViewModelAction
+
+sealed interface StartAppAction : VectorViewModelAction {
+ object StartApp : StartAppAction
+}
diff --git a/vector/src/main/java/im/vector/app/features/start/StartAppAndroidService.kt b/vector/src/main/java/im/vector/app/features/start/StartAppAndroidService.kt
new file mode 100644
index 0000000000..e8e0eac863
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/start/StartAppAndroidService.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2022 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.app.features.start
+
+import android.content.Intent
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.core.di.ActiveSessionHolder
+import im.vector.app.core.di.NamedGlobalScope
+import im.vector.app.core.services.VectorAndroidService
+import im.vector.app.features.notifications.NotificationUtils
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+import kotlin.random.Random
+import kotlin.time.Duration.Companion.seconds
+
+/**
+ * A simple foreground service that let the app (and the SDK) time to initialize.
+ * Will self stop itself once the active session is set.
+ */
+@AndroidEntryPoint
+class StartAppAndroidService : VectorAndroidService() {
+
+ @NamedGlobalScope @Inject lateinit var globalScope: CoroutineScope
+ @Inject lateinit var notificationUtils: NotificationUtils
+ @Inject lateinit var activeSessionHolder: ActiveSessionHolder
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ showStickyNotification()
+ startPollingActiveSession()
+ return START_STICKY
+ }
+
+ private fun startPollingActiveSession() {
+ globalScope.launch {
+ do {
+ delay(1.seconds.inWholeMilliseconds)
+ } while (activeSessionHolder.hasActiveSession().not())
+ myStopSelf()
+ }
+ }
+
+ private fun showStickyNotification() {
+ val notificationId = Random.nextInt()
+ val notification = notificationUtils.buildStartAppNotification()
+ startForeground(notificationId, notification)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/start/StartAppViewEvent.kt b/vector/src/main/java/im/vector/app/features/start/StartAppViewEvent.kt
new file mode 100644
index 0000000000..986d41f983
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/start/StartAppViewEvent.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2022 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.app.features.start
+
+import im.vector.app.core.platform.VectorViewEvents
+
+sealed interface StartAppViewEvent : VectorViewEvents {
+ /**
+ * Will be sent if the process is taking more than 1 second.
+ */
+ object StartForegroundService : StartAppViewEvent
+
+ /**
+ * Will be sent when the current Session has been set.
+ */
+ object AppStarted : StartAppViewEvent
+}
diff --git a/vector/src/main/java/im/vector/app/features/start/StartAppViewModel.kt b/vector/src/main/java/im/vector/app/features/start/StartAppViewModel.kt
new file mode 100644
index 0000000000..62a7517f5a
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/start/StartAppViewModel.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2022 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.app.features.start
+
+import com.airbnb.mvrx.MavericksViewModelFactory
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import im.vector.app.core.di.ActiveSessionSetter
+import im.vector.app.core.di.MavericksAssistedViewModelFactory
+import im.vector.app.core.di.hiltMavericksViewModelFactory
+import im.vector.app.core.platform.VectorViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlin.time.Duration.Companion.seconds
+
+class StartAppViewModel @AssistedInject constructor(
+ @Assisted val initialState: StartAppViewState,
+ private val activeSessionSetter: ActiveSessionSetter,
+) : VectorViewModel(initialState) {
+
+ @AssistedFactory
+ interface Factory : MavericksAssistedViewModelFactory {
+ override fun create(initialState: StartAppViewState): StartAppViewModel
+ }
+
+ companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory()
+
+ fun shouldStartApp(): Boolean {
+ return activeSessionSetter.shouldSetActionSession()
+ }
+
+ override fun handle(action: StartAppAction) {
+ when (action) {
+ StartAppAction.StartApp -> handleStartApp()
+ }
+ }
+
+ private fun handleStartApp() {
+ handleLongProcessing()
+ viewModelScope.launch(Dispatchers.IO) {
+ // This can take time because of DB migration(s), so do it in a background task.
+ activeSessionSetter.tryToSetActiveSession(startSync = true)
+ _viewEvents.post(StartAppViewEvent.AppStarted)
+ }
+ }
+
+ private fun handleLongProcessing() {
+ viewModelScope.launch(Dispatchers.Default) {
+ delay(1.seconds.inWholeMilliseconds)
+ setState { copy(mayBeLongToProcess = true) }
+ _viewEvents.post(StartAppViewEvent.StartForegroundService)
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/start/StartAppViewState.kt b/vector/src/main/java/im/vector/app/features/start/StartAppViewState.kt
new file mode 100644
index 0000000000..3ff933f054
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/start/StartAppViewState.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2022 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.app.features.start
+
+import com.airbnb.mvrx.MavericksState
+
+data class StartAppViewState(
+ val mayBeLongToProcess: Boolean = false
+) : MavericksState
diff --git a/vector/src/main/res/layout/activity_main.xml b/vector/src/main/res/layout/activity_main.xml
index c7bca50acb..ba5925f000 100644
--- a/vector/src/main/res/layout/activity_main.xml
+++ b/vector/src/main/res/layout/activity_main.xml
@@ -1,5 +1,4 @@
-
-
+
+
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index b699ddae2f..665e1ba8b4 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -1625,6 +1625,7 @@
No network. Please check your Internet connection.
"Change network"
"Please wait…"
+ Updating your data…
"All Communities"