Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
fun getAppVersion(context: Context): String? {
val appVersion: Int? =
try {
context.packageManager.getPackageInfo(context.packageName, 0).versionCode

Check warning on line 63 in OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/AndroidUtils.kt

View workflow job for this annotation

GitHub Actions / build

'versionCode: Int' is deprecated. Deprecated in Java
} catch (e: PackageManager.NameNotFoundException) {
null
}
Expand Down Expand Up @@ -145,7 +145,7 @@
fun hasNotificationManagerCompat(): Boolean {
return try {
// noinspection ConstantConditions
NotificationManagerCompat::class.java != null

Check warning on line 148 in OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/AndroidUtils.kt

View workflow job for this annotation

GitHub Actions / build

Condition 'NotificationManagerCompat::class.java != null' is always 'true'
} catch (e: Throwable) {
false
}
Expand All @@ -167,7 +167,7 @@
}

fun openURLInBrowserIntent(uri: Uri): Intent {
var uri = uri

Check warning on line 170 in OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/AndroidUtils.kt

View workflow job for this annotation

GitHub Actions / build

Name shadowed: uri
var type = if (uri.scheme != null) SchemaType.fromString(uri.scheme) else null

if (type == null) {
Expand All @@ -184,7 +184,7 @@
intent.data = uri
}
SchemaType.HTTPS, SchemaType.HTTP -> intent = Intent(Intent.ACTION_VIEW, uri)
else -> intent = Intent(Intent.ACTION_VIEW, uri)

Check warning on line 187 in OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/AndroidUtils.kt

View workflow job for this annotation

GitHub Actions / build

'when' is exhaustive so 'else' is redundant here
}
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK,
Expand Down Expand Up @@ -246,7 +246,7 @@
permissions: List<String>,
applicationService: IApplicationService,
): List<String> {
var requestPermission: String? = null

Check warning on line 249 in OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/AndroidUtils.kt

View workflow job for this annotation

GitHub Actions / build

Variable 'requestPermission' is never used
val packageInfo: PackageInfo =
applicationService.appContext
.packageManager
Expand All @@ -264,7 +264,7 @@
// This @Keep annotation is key so this method does not get removed / inlined.
// Addresses issue https://github.com/OneSignal/OneSignal-Android-SDK/issues/1423
@Keep
fun opaqueHasClass(_class: Class<*>): Boolean {

Check warning on line 267 in OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/AndroidUtils.kt

View workflow job for this annotation

GitHub Actions / build

Parameter '_class' is never used
return true
}

Expand All @@ -285,4 +285,14 @@
}
}
}

/**
* Safely finishes the activity only if it's still valid.
* Prevents redundant or unsafe calls to finish(), reducing lifecycle issues or potential leaks.
*/
fun finishSafely(activity: Activity) {
if (!activity.isDestroyed && !activity.isFinishing) {
activity.finish()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,25 @@ fun suspendifyOnMain(block: suspend () -> Unit) {
fun suspendifyOnThread(
priority: Int = -1,
block: suspend () -> Unit,
) {
suspendifyOnThread(priority, block, null)
}

/**
* Allows a non suspending function to create a scope that can
* call suspending functions. This is a nonblocking call, which
* means the scope will run on a background thread. This will
* return immediately!!! Also provides an optional onComplete.
*/
fun suspendifyOnThread(
priority: Int = -1,
block: suspend () -> Unit,
onComplete: (() -> Unit)? = null,
) {
thread(priority = priority) {
try {
runBlocking {
block()
}
runBlocking { block() }
onComplete?.invoke()
} catch (e: Exception) {
Logging.error("Exception on thread", e)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import android.app.Activity
import android.content.Intent
import android.os.Bundle
import com.onesignal.OneSignal
import com.onesignal.common.AndroidUtils
import com.onesignal.common.threading.suspendifyOnThread
import com.onesignal.notifications.internal.open.INotificationOpenedProcessor

Expand All @@ -44,18 +45,26 @@ abstract class NotificationOpenedActivityBase : Activity() {
processIntent()
}

private fun processIntent() {
internal open fun processIntent() {
if (!OneSignal.initWithContext(applicationContext)) {
return
}
suspendifyOnThread {
val openedProcessor = OneSignal.getService<INotificationOpenedProcessor>()
openedProcessor.processFromContext(this, intent)
// KEEP: Xiaomi Compatibility:
// Must keep this Activity alive while trampolining, that is
// startActivity() must be called BEFORE finish(), otherwise
// the app is never foregrounded.
finish()
}
suspendifyOnThread(
block = {
val openedProcessor = OneSignal.getService<INotificationOpenedProcessor>()
openedProcessor.processFromContext(this, intent)
// KEEP: Xiaomi Compatibility:
// Must keep this Activity alive while trampolining, that is
// startActivity() must be called BEFORE finish(), otherwise
// the app is never foregrounded.
},
onComplete = {
// Safely finish the activity on the main thread after processing is complete.
// This gives the system enough time to complete rendering before closing the Trampoline activity.
runOnUiThread {
AndroidUtils.finishSafely(this)
}
},
Comment on lines +61 to +67
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why add this new onComplete instead of just adding this to the existing block above? This adds complexity that doesn't' seems to be needed.

)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.onesignal.notifications.activities

import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import org.robolectric.Robolectric
import org.robolectric.shadows.ShadowLooper

class TestNotificationOpenedActivity : NotificationOpenedActivityBase() {
var wasFinishCalledOnMainThread = false
var wasProcessIntentCalled = false

override fun finish() {
wasFinishCalledOnMainThread = Looper.myLooper() == Looper.getMainLooper()
super.finish()
}

override fun getIntent(): Intent {
return Intent().apply {
putExtra("some_key", "some_value") // simulate a valid OneSignal intent
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// This triggers processIntent inside base
}

override fun processIntent() {
wasProcessIntentCalled = true
super.processIntent()
}
}

@RobolectricTest
class NotificationOpenedActivityTest : FunSpec({
test("finishSafely should be called on main thread") {
val controller = Robolectric.buildActivity(TestNotificationOpenedActivity::class.java)
val activity = controller.setup().get()
Handler(Looper.getMainLooper()).post {
activity.finish()
}
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
activity.wasFinishCalledOnMainThread shouldBe true
}

test("processIntent should be called during activity setup") {
val controller = Robolectric.buildActivity(TestNotificationOpenedActivity::class.java)
val activity = controller.setup().get()

activity.wasProcessIntentCalled shouldBe true
}
})
Loading