Skip to content

Commit

Permalink
Merge pull request #88 from solrudev/develop
Browse files Browse the repository at this point in the history
0.8.3
  • Loading branch information
solrudev authored Nov 7, 2024
2 parents 4f7dcaa + 1a9ef13 commit 7cc6980
Show file tree
Hide file tree
Showing 14 changed files with 180 additions and 108 deletions.
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.2"
val ackpineVersion = "0.8.3"
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 @@ -99,11 +99,13 @@ internal abstract class SessionCommitActivity<F : Failure> protected constructor
}

@JvmSynthetic
internal inline fun withCompletableSession(crossinline block: (CompletableSession<F>?) -> Unit) {
ackpineSessionFuture.handleResult { session ->
val completableSession = session as? CompletableSession<F>
block(completableSession)
}
internal fun completeSession(state: Session.State.Completed<F>) = withCompletableSession { session ->
session?.complete(state)
}

@JvmSynthetic
internal fun completeSessionExceptionally(exception: Exception) = withCompletableSession { session ->
session?.completeExceptionally(exception)
}

protected fun notifySessionCommitted() {
Expand Down Expand Up @@ -135,6 +137,13 @@ internal abstract class SessionCommitActivity<F : Failure> protected constructor
)
}

private inline fun withCompletableSession(crossinline block: (CompletableSession<F>?) -> Unit) {
ackpineSessionFuture.handleResult { session ->
val completableSession = session as? CompletableSession<F>
block(completableSession)
}
}

private fun initializeState(savedInstanceState: Bundle?) {
if (savedInstanceState != null) {
requestCode = savedInstanceState.getInt(REQUEST_CODE_KEY)
Expand Down
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 @@ -44,24 +44,17 @@ internal class IntentBasedInstallActivity : InstallActivity(TAG, startsActivity
if (requestCode != this.requestCode) {
return
}
val result = if (resultCode == RESULT_OK) {
Session.State.Succeeded
} else {
Session.State.Failed(InstallFailure.Generic())
}
withCompletableSession { session ->
session?.complete(result)
when (resultCode) {
RESULT_CANCELED -> abortSession("Session was cancelled")
RESULT_OK -> completeSession(Session.State.Succeeded)
else -> completeSession(Session.State.Failed(InstallFailure.Generic()))
}
}

@Suppress("DEPRECATION")
private fun launchInstallActivity() {
if (apkUri == null) {
withCompletableSession { session ->
session?.completeExceptionally(
IllegalStateException("$TAG: apkUri was null.")
)
}
completeSessionExceptionally(IllegalStateException("$TAG: apkUri was null."))
return
}
val intent = Intent().apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,24 +65,24 @@ internal class SessionBasedInstallConfirmationActivity : InstallActivity(CONFIRM
private var canInstallPackages = false
private var isFirstResume = true
private var isOnActivityResultCalled = false
private var isConfirmationRelaunchNeeded = false
private var wasOnTopOnStart = false

private val deadSessionCompletionRunnable = Runnable {
withCompletableSession { session ->
session?.complete(
Session.State.Failed(InstallFailure.Generic(message = "Session $sessionId is dead."))
completeSession(
Session.State.Failed(
InstallFailure.Generic(message = "Session $sessionId is dead.")
)
}
)
}

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

Expand All @@ -96,10 +96,8 @@ internal class SessionBasedInstallConfirmationActivity : InstallActivity(CONFIRM
when {
// Activity is freshly created, skip.
isFirstResume -> isFirstResume = false
// User hasn't confirmed installation because confirmation activity didn't appear after permission request.
isConfirmationRelaunchNeeded -> launchInstallActivity()
// Activity was recreated and brought to top, but install confirmation from OS was dismissed.
!isOnActivityResultCalled && isOnTop() -> abortSession()
!isOnActivityResultCalled && wasOnTopOnStart -> abortSession()
}
}

Expand Down Expand Up @@ -134,7 +132,7 @@ internal class SessionBasedInstallConfirmationActivity : InstallActivity(CONFIRM
// 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.
isSessionStuck && isInstallPermissionStatusChanged && wasOnTopOnStart -> isConfirmationRelaunchNeeded = true
isSessionStuck && isInstallPermissionStatusChanged && wasOnTopOnStart -> 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
Expand Down Expand Up @@ -184,11 +182,7 @@ private val InstallActivity.packageInstaller: PackageInstaller
private fun InstallActivity.getSessionId(tag: String): Int {
val sessionId = intent.extras?.getInt(PackageInstaller.EXTRA_SESSION_ID)
if (sessionId == null) {
withCompletableSession { session ->
session?.completeExceptionally(
IllegalStateException("$tag: sessionId was null.")
)
}
completeSessionExceptionally(IllegalStateException("$tag: sessionId was null."))
}
return sessionId ?: -1
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import android.content.Intent
import android.os.Bundle
import androidx.annotation.RestrictTo
import ru.solrudev.ackpine.impl.activity.SessionCommitActivity
import ru.solrudev.ackpine.session.Session
import ru.solrudev.ackpine.uninstaller.PackageUninstaller
import ru.solrudev.ackpine.uninstaller.UninstallFailure

Expand All @@ -48,11 +47,7 @@ internal class UninstallActivity : SessionCommitActivity<UninstallFailure>(
ackpinePackageUninstaller = PackageUninstaller.getInstance(this)
super.onCreate(savedInstanceState)
if (packageNameToUninstall == null) {
withCompletableSession { session ->
session?.completeExceptionally(
IllegalStateException("$TAG: packageNameToUninstall was null.")
)
}
completeSessionExceptionally(IllegalStateException("$TAG: packageNameToUninstall was null."))
finish()
return
}
Expand All @@ -67,11 +62,8 @@ internal class UninstallActivity : SessionCommitActivity<UninstallFailure>(
if (requestCode != this.requestCode) {
return
}
val success = uninstallPackageContract.parseResult(this, resultCode)
val result = if (success) Session.State.Succeeded else Session.State.Failed(UninstallFailure.Generic)
withCompletableSession { session ->
session?.complete(result)
}
val result = uninstallPackageContract.parseResult(this, resultCode)
completeSession(result)
}

@SuppressLint("RestrictedApi")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import androidx.annotation.RestrictTo
import ru.solrudev.ackpine.session.Session
import ru.solrudev.ackpine.uninstaller.UninstallFailure

/**
* Returns package uninstall [UninstallContract] for current API level.
Expand All @@ -40,7 +42,7 @@ internal fun UninstallPackageContract(packageName: String): UninstallContract {
@RestrictTo(RestrictTo.Scope.LIBRARY)
internal interface UninstallContract {
fun createIntent(context: Context): Intent
fun parseResult(context: Context, resultCode: Int): Boolean
fun parseResult(context: Context, resultCode: Int): Session.State.Completed<UninstallFailure>
}

private class ActionDeletePackageContract(private val packageName: String) : UninstallContract {
Expand All @@ -50,7 +52,12 @@ private class ActionDeletePackageContract(private val packageName: String) : Uni
return Intent(Intent.ACTION_DELETE, packageUri)
}

override fun parseResult(context: Context, resultCode: Int) = !context.isPackageInstalled(packageName)
override fun parseResult(context: Context, resultCode: Int): Session.State.Completed<UninstallFailure> {
if (!context.isPackageInstalled(packageName)) {
return Session.State.Succeeded
}
return Session.State.Failed(UninstallFailure.Generic)
}

private fun Context.isPackageInstalled(packageName: String) = try {
packageManager.getPackageInfoCompat(packageName, PackageManager.GET_ACTIVITIES)
Expand All @@ -60,11 +67,10 @@ private class ActionDeletePackageContract(private val packageName: String) : Uni
}

private fun PackageManager.getPackageInfoCompat(packageName: String, flags: Int): PackageInfo {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(flags.toLong()))
} else {
getPackageInfo(packageName, flags)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(flags.toLong()))
}
return getPackageInfo(packageName, flags)
}
}

Expand All @@ -77,5 +83,11 @@ private class ActionUninstallPackageContract(private val packageName: String) :
putExtra(Intent.EXTRA_RETURN_RESULT, true)
}

override fun parseResult(context: Context, resultCode: Int) = resultCode == Activity.RESULT_OK
override fun parseResult(context: Context, resultCode: Int): Session.State.Completed<UninstallFailure> {
return when (resultCode) {
Activity.RESULT_OK -> Session.State.Succeeded
Activity.RESULT_CANCELED -> Session.State.Failed(UninstallFailure.Aborted("Session was cancelled"))
else -> Session.State.Failed(UninstallFailure.Generic)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,4 @@ package ru.solrudev.ackpine.gradle
public object Constants {
public const val SAMPLE_PACKAGE_NAME: String = "ru.solrudev.ackpine.sample"
public const val PACKAGE_NAME: String = "ru.solrudev.ackpine"
internal const val signingKeyId = "signing.keyId"
internal const val signingPassword = "signing.password"
internal const val signingKey = "signing.key"
}
9 changes: 9 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Change Log
==========

Version 0.8.3 (2024-11-07)
--------------------------

### Bug fixes and improvements

- Return `Aborted` failure when `INTENT_BASED` install session is cancelled.
- Return `Aborted` failure when uninstall via `ACTION_UNINSTALL_PACKAGE` is cancelled.
- Request permissions if they're not granted when sample apps are launched via `ACTION_VIEW` intent.

Version 0.8.2 (2024-11-01)
--------------------------

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

```kotlin
dependencies {
val ackpineVersion = "0.8.2"
val ackpineVersion = "0.8.3"
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 @@ -18,13 +18,13 @@

import static android.content.Intent.ACTION_VIEW;

import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
import androidx.navigation.NavOptions;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
Expand Down Expand Up @@ -58,25 +58,13 @@ protected void onNewIntent(@NonNull Intent intent) {
maybeHandleInstallUri(intent);
}

@SuppressLint("RestrictedApi")
private void maybeHandleInstallUri(@NonNull Intent intent) {
final var uri = intent.getData();
if (ACTION_VIEW.equals(intent.getAction()) && uri != null) {
final var backStack = getNavController().getCurrentBackStack().getValue();
String fragmentTag = null;
for (final var entry : backStack) {
if (entry.getDestination().getId() == R.id.install_fragment) {
fragmentTag = entry.getId();
break;
}
}
final var fragment = binding.contentNavHost
.<NavHostFragment>getFragment()
.getChildFragmentManager()
.findFragmentByTag(fragmentTag);
if (fragment instanceof InstallFragment installFragment) {
installFragment.install(uri);
}
final var arguments = new Bundle();
arguments.putParcelable(InstallFragment.URI_KEY, uri);
final var navOptions = new NavOptions.Builder().setLaunchSingleTop(true).build();
getNavController().navigate(R.id.install_fragment, arguments, navOptions);
}
}

Expand Down
Loading

0 comments on commit 7cc6980

Please sign in to comment.