Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion opencloudApp/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.REORDER_TASKS" />

<application
android:name=".MainApp"
Expand Down Expand Up @@ -238,11 +239,18 @@
android:label="@string/pattern_label"
android:theme="@style/Theme.openCloud" />
<activity android:name=".presentation.security.biometric.BiometricActivity" />
<!-- Own taskAffinity + singleTask so LoginActivity always runs in its own task.
During re-auth, startActivityForResult overrides singleTask, so the first
instance still lands in the main task — but the OAuth redirect instance gets
its own task and finishes the orphaned one via a static reference.
autoRemoveFromRecents cleans the login task from recents once it finishes. -->
<activity
android:name=".presentation.authentication.LoginActivity"
android:autoRemoveFromRecents="true"
android:exported="true"
android:label="@string/login_label"
android:launchMode="singleTop"
android:launchMode="singleTask"
android:taskAffinity="eu.opencloud.android.login"
android:theme="@style/Theme.openCloud.Toolbar">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,21 +130,9 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// Log OAuth redirect details for debugging (especially Firefox issues)
Timber.d("onCreate called with intent data: ${intent.data}, isTaskRoot: $isTaskRoot")

if (intent.data != null && (intent.data?.getQueryParameter("code") != null || intent.data?.getQueryParameter("error") != null)) {
Timber.d("OAuth redirect detected with code or error parameter")
if (!isTaskRoot) {
Timber.d("Not task root, forwarding OAuth redirect to existing LoginActivity instance")
val newIntent = Intent(this, LoginActivity::class.java)
newIntent.data = intent.data
newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(newIntent)
finish()
return
}
}
if (handleOAuthRedirectOnCreate()) return

checkPasscodeEnforced(this)

Expand Down Expand Up @@ -173,8 +161,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted

// UI initialization
binding = AccountSetupBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
setContentView(binding.root)

if (loginAction != ACTION_CREATE) {
binding.accountUsername.isEnabled = false
Expand Down Expand Up @@ -258,8 +245,35 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted

// Note: pendingAuthorizationIntent is processed in checkServerType() after
// getServerInfo() completes (process death recovery flow).
}

/**
* If this onCreate is an OAuth redirect, either forward it to the existing instance
* (when not task root) or let it proceed. Otherwise, track this instance so the
* redirect instance can finish it later (see [launchFileDisplayActivity]).
* @return true if onCreate should return early (redirect was forwarded).
*/
private fun handleOAuthRedirectOnCreate(): Boolean {
val hasOAuthData = intent.data != null &&
(intent.data?.getQueryParameter("code") != null || intent.data?.getQueryParameter("error") != null)

if (hasOAuthData) {
Timber.d("OAuth redirect detected with code or error parameter")
if (!isTaskRoot) {
Timber.d("Not task root, forwarding OAuth redirect to existing LoginActivity instance")
val newIntent = Intent(this, LoginActivity::class.java)
newIntent.data = intent.data
newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(newIntent)
finish()
return true
}
} else {
// Not an OAuth redirect — track this instance so the redirect instance
// can finish it later (see companion object and launchFileDisplayActivity).
orphanedInstance = java.lang.ref.WeakReference(this)
}
return false
}

private fun handleDeepLink() {
Expand All @@ -274,6 +288,29 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
}

private fun launchFileDisplayActivity() {
// Finish the orphaned LoginActivity in the main task (if any).
// During re-auth, startActivityForResult forces the first instance into the main
// task. This second instance (OAuth redirect) runs in its own task and can't reach
// the first via task flags, so we finish it directly via the static reference.
var mainTaskId = -1
orphanedInstance?.get()?.let { other ->
if (other !== this) {
Timber.d("Finishing orphaned LoginActivity in main task")
mainTaskId = other.taskId
other.finish()
}
}
orphanedInstance = null

if (mainTaskId != -1) {
// The main task already has FileDisplayActivity. Bring it to the foreground
// and finish this instance. Just calling finish() would leave the browser on top.
val am = getSystemService(android.content.Context.ACTIVITY_SERVICE) as android.app.ActivityManager
am.moveTaskToFront(mainTaskId, 0)
finish()
return
}

val newIntent = Intent(this, FileDisplayActivity::class.java)
if (authenticationViewModel.launchedFromDeepLink) {
newIntent.data = intent.data
Expand Down Expand Up @@ -1127,4 +1164,14 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
null
}
}

companion object {
/**
* During re-auth, startActivityForResult forces the first LoginActivity into the
* main task (ignoring singleTask). The OAuth redirect then creates a second instance
* in its own task. We track the first (orphaned) instance here so the second one
* can explicitly finish() it after auth completes.
*/
private var orphanedInstance: java.lang.ref.WeakReference<LoginActivity>? = null
}
}
Loading