Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.2.0 #31

Merged
merged 18 commits into from
Oct 28, 2023
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Ackpine depends on Jetpack libraries, so it's necessary to declare the `google()

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

// optional - Kotlin extensions and Coroutines support
Expand Down
2 changes: 0 additions & 2 deletions ackpine-core/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
Expand Down
16 changes: 10 additions & 6 deletions ackpine-core/src/main/kotlin/ru/solrudev/ackpine/Ackpine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@

package ru.solrudev.ackpine

import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.ComponentCallbacks
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import androidx.core.content.getSystemService
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationManagerCompat
import ru.solrudev.ackpine.core.R
import ru.solrudev.ackpine.exceptions.AckpineReinitializeException

Expand Down Expand Up @@ -63,9 +63,13 @@ public object Ackpine {
val channelName = applicationContext.getString(R.string.ackpine_notification_channel_name)
val channelDescription = applicationContext.getString(R.string.ackpine_notification_channel_description)
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(channelIdString, channelName, importance).apply {
description = channelDescription
}
applicationContext.getSystemService<NotificationManager>()?.createNotificationChannel(channel)
val channel = NotificationChannelCompat.Builder(channelIdString, importance)
.setName(channelName)
.setDescription(channelDescription)
.setVibrationEnabled(true)
.setLightsEnabled(true)
.setShowBadge(true)
.build()
NotificationManagerCompat.from(applicationContext).createNotificationChannel(channel)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ import com.google.common.util.concurrent.ListenableFuture
import ru.solrudev.ackpine.DisposableSubscriptionContainer
import ru.solrudev.ackpine.core.R
import ru.solrudev.ackpine.helpers.handleResult
import ru.solrudev.ackpine.impl.activity.helpers.clearTurnScreenOnSettings
import ru.solrudev.ackpine.impl.activity.helpers.turnScreenOnWhenLocked
import ru.solrudev.ackpine.impl.installer.activity.helpers.getSerializableCompat
import ru.solrudev.ackpine.impl.session.CompletableSession
import ru.solrudev.ackpine.session.Failure
Expand Down Expand Up @@ -60,7 +58,6 @@ internal abstract class SessionCommitActivity<S : Session<F>, F : Failure> prote
notifySessionCommitted()
}
}
turnScreenOnWhenLocked()
setContentView(R.layout.ackpine_activity_session_commit)
registerOnBackInvokedCallback()
finishActivityOnTerminalSessionState()
Expand All @@ -69,7 +66,6 @@ internal abstract class SessionCommitActivity<S : Session<F>, F : Failure> prote
override fun onDestroy() {
super.onDestroy()
subscriptions.clear()
clearTurnScreenOnSettings()
}

@Deprecated("Deprecated in Java")
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import ru.solrudev.ackpine.impl.installer.session.helpers.copyTo
import ru.solrudev.ackpine.impl.installer.session.helpers.openAssetFileDescriptor
import ru.solrudev.ackpine.impl.installer.session.helpers.toFile
import ru.solrudev.ackpine.impl.session.AbstractProgressSession
import ru.solrudev.ackpine.impl.session.globalNotificationId
import ru.solrudev.ackpine.impl.session.helpers.CANCEL_CURRENT_FLAGS
import ru.solrudev.ackpine.impl.session.helpers.launchConfirmation
import ru.solrudev.ackpine.installer.InstallFailure
Expand Down Expand Up @@ -64,13 +65,15 @@ internal class IntentBasedInstallSession internal constructor(
sessionProgressDao: SessionProgressDao,
notificationIdDao: NotificationIdDao,
serialExecutor: Executor,
handler: Handler
handler: Handler,
newNotificationId: Int = globalNotificationId.incrementAndGet()
) : AbstractProgressSession<InstallFailure>(
context, INSTALLER_NOTIFICATION_TAG,
id, initialState, initialProgress,
sessionDao, sessionFailureDao, sessionProgressDao, notificationIdDao,
serialExecutor, handler,
exceptionalFailureFactory = InstallFailure::Exceptional
exceptionalFailureFactory = InstallFailure::Exceptional,
newNotificationId
) {

private val Context.externalDir: File
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import ru.solrudev.ackpine.impl.installer.session.helpers.STREAM_COPY_PROGRESS_M
import ru.solrudev.ackpine.impl.installer.session.helpers.copyTo
import ru.solrudev.ackpine.impl.installer.session.helpers.openAssetFileDescriptor
import ru.solrudev.ackpine.impl.session.AbstractProgressSession
import ru.solrudev.ackpine.impl.session.globalNotificationId
import ru.solrudev.ackpine.impl.session.helpers.CANCEL_CURRENT_FLAGS
import ru.solrudev.ackpine.impl.session.helpers.launchConfirmation
import ru.solrudev.ackpine.installer.InstallFailure
Expand Down Expand Up @@ -73,12 +74,14 @@ internal class SessionBasedInstallSession internal constructor(
private val executor: Executor,
serialExecutor: Executor,
private val handler: Handler,
newNotificationId: Int = globalNotificationId.incrementAndGet()
) : AbstractProgressSession<InstallFailure>(
context, INSTALLER_NOTIFICATION_TAG,
id, initialState, initialProgress,
sessionDao, sessionFailureDao, sessionProgressDao, notificationIdDao,
serialExecutor, handler,
exceptionalFailureFactory = InstallFailure::Exceptional
exceptionalFailureFactory = InstallFailure::Exceptional,
newNotificationId
) {

@Volatile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,13 @@ internal abstract class AbstractProgressSession<F : Failure> protected construct
notificationIdDao: NotificationIdDao,
private val serialExecutor: Executor,
private val handler: Handler,
exceptionalFailureFactory: (Exception) -> F
exceptionalFailureFactory: (Exception) -> F,
newNotificationId: Int
) : AbstractSession<F>(
context, notificationTag,
id, initialState,
sessionDao, sessionFailureDao, notificationIdDao,
serialExecutor, handler, exceptionalFailureFactory
serialExecutor, handler, exceptionalFailureFactory, newNotificationId
), ProgressSession<F> {

private val progressListeners = mutableSetOf<ProgressSession.ProgressListener>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicInteger
import kotlin.random.Random

private val globalNotificationId = AtomicInteger(Random.nextInt(from = 10000, until = 1000000))
@get:JvmSynthetic
internal val globalNotificationId = AtomicInteger(Random.nextInt(from = 10000, until = 1000000))

/**
* A base implementation for Ackpine [sessions][Session].
Expand All @@ -55,7 +56,7 @@ internal abstract class AbstractSession<F : Failure> protected constructor(
private val serialExecutor: Executor,
private val handler: Handler,
private val exceptionalFailureFactory: (Exception) -> F,
newNotificationId: Int = globalNotificationId.incrementAndGet()
newNotificationId: Int
) : CompletableSession<F> {

init {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ private fun Context.showNotification(
setDefaults(NotificationCompat.DEFAULT_ALL)
setSmallIcon(notificationData.icon)
setOngoing(true)
setFullScreenIntent(intent, true)
setAutoCancel(true)
}.build()
getSystemService<NotificationManager>()?.notify(notificationTag, notificationId, notification)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import ru.solrudev.ackpine.impl.database.dao.NotificationIdDao
import ru.solrudev.ackpine.impl.database.dao.SessionDao
import ru.solrudev.ackpine.impl.database.dao.SessionFailureDao
import ru.solrudev.ackpine.impl.session.AbstractSession
import ru.solrudev.ackpine.impl.session.globalNotificationId
import ru.solrudev.ackpine.impl.session.helpers.UPDATE_CURRENT_FLAGS
import ru.solrudev.ackpine.impl.session.helpers.launchConfirmation
import ru.solrudev.ackpine.impl.uninstaller.activity.UninstallActivity
Expand All @@ -47,13 +48,15 @@ internal class UninstallSession internal constructor(
sessionFailureDao: SessionFailureDao<UninstallFailure>,
notificationIdDao: NotificationIdDao,
serialExecutor: Executor,
handler: Handler
handler: Handler,
newNotificationId: Int = globalNotificationId.incrementAndGet()
) : AbstractSession<UninstallFailure>(
context, UNINSTALLER_NOTIFICATION_TAG,
id, initialState,
sessionDao, sessionFailureDao, notificationIdDao,
serialExecutor, handler,
exceptionalFailureFactory = UninstallFailure::Exceptional
exceptionalFailureFactory = UninstallFailure::Exceptional,
newNotificationId
) {

override fun prepare(cancellationSignal: CancellationSignal) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@

package ru.solrudev.ackpine.session.parameters

import android.Manifest.permission.DISABLE_KEYGUARD
import android.Manifest.permission.POST_NOTIFICATIONS
import android.Manifest.permission.USE_FULL_SCREEN_INTENT
import android.Manifest.permission.VIBRATE
import ru.solrudev.ackpine.session.parameters.Confirmation.DEFERRED
import ru.solrudev.ackpine.session.parameters.Confirmation.IMMEDIATE
Expand All @@ -28,8 +26,8 @@ import ru.solrudev.ackpine.session.parameters.Confirmation.IMMEDIATE
*
* * [IMMEDIATE] &mdash; user will be prompted to confirm installation or uninstallation right away. Suitable for
* launching session directly from the UI when app is in foreground.
* * [DEFERRED] (default) &mdash; user will be shown a high-priority notification (full-screen intent) which will launch
* confirmation activity.
* * [DEFERRED] (default) &mdash; user will be shown a high-priority notification which will launch confirmation
* activity.
*/
public enum class Confirmation {

Expand All @@ -39,9 +37,9 @@ public enum class Confirmation {
IMMEDIATE,

/**
* Show a high-priority notification (full-screen intent) which will launch confirmation activity to a user.
* Show a high-priority notification which will launch confirmation activity to a user.
*
* Requires [USE_FULL_SCREEN_INTENT], [DISABLE_KEYGUARD], [VIBRATE], [POST_NOTIFICATIONS] permissions.
* Requires [VIBRATE], [POST_NOTIFICATIONS] permissions.
*/
DEFERRED
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,11 @@
-->

<resources>
<style name="Theme.Ackpine.SessionCommitActivity" parent="android:Theme.DeviceDefault.NoActionBar" />

<style name="Base.V19.Theme.Ackpine.SessionCommitActivity" parent="Base.V16.Theme.Ackpine.SessionCommitActivity">
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:windowTranslucentStatus">true</item>
</style>

<style name="Base.Theme.Ackpine.SessionCommitActivity" parent="Base.V19.Theme.Ackpine.SessionCommitActivity" />
</resources>
9 changes: 8 additions & 1 deletion ackpine-core/src/main/res/values/themes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,12 @@
-->

<resources>
<style name="Theme.Ackpine.SessionCommitActivity" parent="android:Theme.DeviceDefault.Light.NoActionBar" />

<style name="Base.V16.Theme.Ackpine.SessionCommitActivity" parent="android:Theme.Translucent.NoTitleBar">
<item name="android:progressBarStyle">@android:style/Widget.DeviceDefault.ProgressBar</item>
<item name="android:windowBackground">@android:drawable/screen_background_dark_transparent</item>
</style>

<style name="Base.Theme.Ackpine.SessionCommitActivity" parent="Base.V16.Theme.Ackpine.SessionCommitActivity" />
<style name="Theme.Ackpine.SessionCommitActivity" parent="Base.Theme.Ackpine.SessionCommitActivity" />
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ package ru.solrudev.ackpine.gradle

import com.android.build.gradle.LibraryExtension
import org.gradle.api.model.ObjectFactory
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.provider.Property
import org.gradle.kotlin.dsl.property
import javax.inject.Inject

public open class AckpineExtension @Inject constructor(private val libraryExtension: LibraryExtension) {
public abstract class AckpineExtension @Inject constructor(
private val libraryExtension: LibraryExtension
) : ExtensionAware {

private var _id = ""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package ru.solrudev.ackpine.gradle.publishing
import com.android.build.gradle.LibraryExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
Expand Down Expand Up @@ -47,7 +46,7 @@ public class AckpineLibraryPublishPlugin : Plugin<Project> {
apply(DokkaPlugin::class)
}
val ackpineExtension = extensions.getByType<AckpineExtension>()
val artifact = (ackpineExtension as ExtensionAware).extensions.create<AckpineArtifact>("artifact")
val artifact = ackpineExtension.extensions.create<AckpineArtifact>("artifact")
configurePublishing(ackpineExtension, artifact)
configureSigning()
}
Expand Down
14 changes: 14 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Architecture
============

Ackpine library has a concept of `Session`. `Session` manages the flow of installation or uninstallation process.

One can launch multiple different sessions at once, but can't create them directly. To create a session, one needs to use `PackageInstaller` or `PackageUninstaller`.

In essence, `PackageInstaller` is a _repository_ of `ProgressSessions`, and `PackageUninstaller` is a _repository_ of `Sessions`. They track and persist every session launched.

Session by itself is passive, it doesn't do anything until client code says so. One can say that session is a finite-state machine which transfers from one state to another, but for this to be possible client needs to react to state changes and call necessary methods on the session. This is done to make sessions persistable and suspendable.

If any of the steps while the session is active is interrupted (e.g. with process death), it can be re-executed later by examining last session's state and executing the steps which weren't finished. The library provides ready-to-use implementations of such listeners in the form of `Session.DefaultStateListener` and `Session.await()`. They can be safely re-attached after interruption.

`Uri` is chosen as a sole input type of APKs. It makes them persistable and allows to plug in any APK source via `ContentProvider`. The library leverages this in `ackpine-splits` and `ackpine-assets` modules to read APKs from zipped files and app's asset files respectively.
16 changes: 16 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
Change Log
==========

Version 0.2.0 (2023-10-28)
--------------------------

### Dependencies

- Updated AndroidX Room to 2.6.0.
- Updated RecyclerView to 1.3.2 (sample apps dependency).

### Bug fixes and improvements

- Remove intrusive behavior of `DEFERRED` confirmation: dismissing keyguard, using full-screen intent. `USE_FULL_SCREEN_INTENT` and `DISABLE_KEYGUARD` permissions were removed. Also it allowed to make `DEFERRED` confirmation's behavior consistent on old and new Android versions, as on old versions full-screen intent behaved like `IMMEDIATE` confirmation.
- Enable vibration and lights for library's notification channel.
- Make confirmation's background semi-transparent.
- Fix incorrect handling of `file:` URIs in sample apps.
- Add documentation about permissions and library's architecture.

Version 0.1.6 (2023-10-17)
--------------------------

Expand Down
Loading