Skip to content

Commit

Permalink
Merge pull request #86 from solrudev/develop
Browse files Browse the repository at this point in the history
0.8.1
  • Loading branch information
solrudev authored Oct 31, 2024
2 parents 75f834b + 8cd162b commit 1e64f54
Show file tree
Hide file tree
Showing 16 changed files with 176 additions and 151 deletions.
13 changes: 6 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,13 @@ jobs:

- name: Publish
run: |
./gradlew publishReleasePublicationToSonatypeRepository --max-workers 1 closeAndReleaseSonatypeStagingRepository
./gradlew publishAndReleaseToMavenCentral --no-configuration-cache --max-workers 1
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.OSSRH_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.OSSRH_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_KEY_ID }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}

publish-docs:
name: Publish documentation
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Ackpine depends on Jetpack libraries, so it's necessary to declare the `google()

```kotlin
dependencies {
val ackpineVersion = "0.8.0"
val ackpineVersion = "0.8.1"
implementation("ru.solrudev.ackpine:ackpine-core:$ackpineVersion")

// optional - Kotlin extensions and Coroutines support
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ internal abstract class SessionCommitActivity<F : Failure> protected constructor
}
}

protected fun abortSession(message: String? = null) = withCompletableSession { session ->
session?.complete(
Session.State.Failed(
abortedStateFailureFactory(message ?: "$tag was finished by user")
)
)
}

private fun initializeState(savedInstanceState: Bundle?) {
if (savedInstanceState != null) {
requestCode = savedInstanceState.getInt(REQUEST_CODE_KEY)
Expand Down Expand Up @@ -159,14 +167,6 @@ internal abstract class SessionCommitActivity<F : Failure> protected constructor
}
}

private fun abortSession() = withCompletableSession { session ->
session?.complete(
Session.State.Failed(
abortedStateFailureFactory("$tag was finished by user")
)
)
}

private fun finishActivityOnTerminalSessionState() = ackpineSessionFuture.handleResult { session ->
session?.addStateListener(subscriptions) { _, state ->
if (state.isTerminal) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,13 @@ internal class InstallSessionFactoryImpl internal constructor(
private object AckpinePromptInstallTitle : ResolvableString.Resource() {
private const val serialVersionUID = 7815666924791958742L
override fun stringId() = R.string.ackpine_prompt_install_title
private fun readResolve(): Any = AckpinePromptInstallTitle
}

private object AckpinePromptInstallMessage : ResolvableString.Resource() {
private const val serialVersionUID = 1224637050663404482L
override fun stringId() = R.string.ackpine_prompt_install_message
private fun readResolve(): Any = AckpinePromptInstallMessage
}

private class AckpinePromptInstallMessageWithLabel(name: String) : ResolvableString.Resource(name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ internal class IntentBasedInstallActivity : InstallActivity(TAG, startsActivity
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode != this.requestCode) {
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,19 @@

package ru.solrudev.ackpine.impl.installer.activity

import android.Manifest.permission.INSTALL_PACKAGES
import android.app.ActivityManager
import android.content.Intent
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import ru.solrudev.ackpine.impl.installer.activity.helpers.getParcelableCompat
import ru.solrudev.ackpine.impl.session.helpers.commitSession
import ru.solrudev.ackpine.impl.session.helpers.getSessionBasedSessionCommitProgressValue
Expand All @@ -34,6 +39,8 @@ import ru.solrudev.ackpine.session.Session

private const val CONFIRMATION_TAG = "SessionBasedInstallConfirmationActivity"
private const val LAUNCHER_TAG = "SessionBasedInstallCommitActivity"
private const val CAN_INSTALL_PACKAGES_KEY = "CAN_INSTALL_PACKAGES"
private const val IS_FIRST_RESUME_KEY = "IS_FIRST_RESUME"

@RestrictTo(RestrictTo.Scope.LIBRARY)
internal class SessionBasedInstallCommitActivity : InstallActivity(LAUNCHER_TAG, startsActivity = false) {
Expand All @@ -54,6 +61,9 @@ internal class SessionBasedInstallConfirmationActivity : InstallActivity(CONFIRM

private val sessionId by lazy(LazyThreadSafetyMode.NONE) { getSessionId(CONFIRMATION_TAG) }
private val handler = Handler(Looper.getMainLooper())
private var canInstallPackages = false
private var isFirstResume = true
private var isOnActivityResultCalled = false

private val deadSessionCompletionRunnable = Runnable {
withCompletableSession { session ->
Expand All @@ -65,42 +75,96 @@ internal class SessionBasedInstallConfirmationActivity : InstallActivity(CONFIRM

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
canInstallPackages = savedInstanceState?.getBoolean(CAN_INSTALL_PACKAGES_KEY) ?: canInstallPackages()
isFirstResume = savedInstanceState?.getBoolean(IS_FIRST_RESUME_KEY) ?: true
if (savedInstanceState == null) {
launchInstallActivity()
}
}

override fun onResume() {
super.onResume()
if (!isFirstResume && !isOnActivityResultCalled && getTopActivityClassName() == this::class.java.name) {
// Activity was recreated and brought to top, but install confirmation from OS was dismissed.
abortSession()
}
isFirstResume = false
}

override fun onDestroy() {
super.onDestroy()
if (isFinishing) {
handler.removeCallbacks(deadSessionCompletionRunnable)
}
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(CAN_INSTALL_PACKAGES_KEY, canInstallPackages)
outState.putBoolean(IS_FIRST_RESUME_KEY, isFirstResume)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode != this.requestCode) {
return
}
isOnActivityResultCalled = true
val isActivityCancelled = resultCode == RESULT_CANCELED
val sessionInfo = packageInstaller.getSessionInfo(sessionId)
// Hacky workaround: progress not going higher after commit means session is dead. This is needed to complete
// the Ackpine session with failure on reasons which are not handled in PackageInstallerStatusReceiver.
// For example, "There was a problem parsing the package" error falls under that.
val isSessionAlive = sessionInfo != null && sessionInfo.progress >= getSessionBasedSessionCommitProgressValue()
if (!isSessionAlive) {
setLoading(isLoading = true, delayMillis = 100)
handler.postDelayed(deadSessionCompletionRunnable, 1000)
} else {
finish()
val isSessionAlive = sessionInfo != null
val isSessionStuck = sessionInfo != null && sessionInfo.progress < getSessionBasedSessionCommitProgressValue()
val previousCanInstallPackagesValue = canInstallPackages
canInstallPackages = canInstallPackages()
val isInstallPermissionStatusChanged = previousCanInstallPackagesValue != canInstallPackages
// Order of checks is important.
when {
// User has cancelled install permission request or hasn't granted permission.
!canInstallPackages -> abortSession("Install permission denied")
// User hasn't confirmed installation because confirmation activity didn't appear after permission request.
// Unfortunately, on some OS versions session may stay stuck if confirmation was dismissed by clicking
// outside of confirmation dialog, so this may lead to repeated confirmation if permission status changes.
isSessionStuck && isInstallPermissionStatusChanged -> launchInstallActivity()
// Session proceeded normally.
// On API 31-32 in case of requireUserAction = false and if _update_ confirmation was dismissed by clicking
// outside of confirmation dialog, session will stay stuck, unfortunately, because for some reason progress
// gets updated almost like the installation was confirmed even though it wasn't and no result is received
// from PackageInstallerStatusReceiver.
isSessionAlive && !isSessionStuck -> finish()
// User has dismissed confirmation activity.
isSessionAlive && isActivityCancelled -> abortSession()
// There was some error while installing which is not handled in PackageInstallerStatusReceiver,
// or session may have completed too quickly.
else -> {
// Wait for possible result from PackageInstallerStatusReceiver before completing with failure.
setLoading(isLoading = true, delayMillis = 100)
handler.postDelayed(deadSessionCompletionRunnable, 1000)
}
}
}

private fun launchInstallActivity() {
canInstallPackages = canInstallPackages()
intent.extras
?.getParcelableCompat<Intent>(Intent.EXTRA_INTENT)
?.let { confirmationIntent -> startActivityForResult(confirmationIntent, requestCode) }
notifySessionCommitted()
}

private fun getTopActivityClassName(): String? {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return null
}
return getSystemService<ActivityManager>()
?.appTasks
?.firstOrNull()
?.taskInfo
?.topActivity
?.className
}

private fun canInstallPackages() = Build.VERSION.SDK_INT < Build.VERSION_CODES.O
|| packageManager.canRequestPackageInstalls()
|| ContextCompat.checkSelfPermission(this, INSTALL_PACKAGES) == PERMISSION_GRANTED
}

private val InstallActivity.packageInstaller: PackageInstaller
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,13 @@ internal class UninstallSessionFactoryImpl internal constructor(
private object AckpinePromptUninstallTitle : ResolvableString.Resource() {
private const val serialVersionUID = -4086992997791586590L
override fun stringId() = R.string.ackpine_prompt_uninstall_title
private fun readResolve(): Any = AckpinePromptUninstallTitle
}

private object AckpinePromptUninstallMessage : ResolvableString.Resource() {
private const val serialVersionUID = -3150252606151986307L
override fun stringId(): Int = R.string.ackpine_prompt_uninstall_message
private fun readResolve(): Any = AckpinePromptUninstallMessage
}

private class AckpinePromptUninstallMessageWithLabel(label: String) : ResolvableString.Resource(label) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,10 @@ public interface DrawableId : Serializable {
}
}

private object DefaultNotificationIcon : DrawableId {
private data object DefaultNotificationIcon : DrawableId {
private const val serialVersionUID = 6906923061913799903L
override fun drawableId() = android.R.drawable.ic_dialog_alert
private fun readResolve(): Any = DefaultNotificationIcon
}

@RestrictTo(RestrictTo.Scope.LIBRARY)
Expand Down
4 changes: 2 additions & 2 deletions build-logic/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023 Ilya Fomichev
* Copyright (C) 2023-2024 Ilya Fomichev
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -43,7 +43,7 @@ gradlePlugin {
dependencies {
implementation(libs.plugin.agp)
implementation(libs.plugin.kotlin.android)
implementation(libs.plugin.nexus.publish)
implementation(libs.plugin.gradleMavenPublish)
implementation(libs.plugin.dokka)
implementation(libs.plugin.binaryCompatibilityValidator)
}
Loading

0 comments on commit 1e64f54

Please sign in to comment.