From 83dc59b36eaf8f49e9e0e2912f10533f8d862dab Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Tue, 24 Oct 2023 09:26:46 +0500 Subject: [PATCH 01/18] make AckpineExtension implement ExtensionAware directly --- .../kotlin/ru/solrudev/ackpine/gradle/AckpineExtension.kt | 5 ++++- .../ackpine/gradle/publishing/AckpineLibraryPublishPlugin.kt | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/build-logic/src/main/kotlin/ru/solrudev/ackpine/gradle/AckpineExtension.kt b/build-logic/src/main/kotlin/ru/solrudev/ackpine/gradle/AckpineExtension.kt index bcf5fba75..cacddf42e 100644 --- a/build-logic/src/main/kotlin/ru/solrudev/ackpine/gradle/AckpineExtension.kt +++ b/build-logic/src/main/kotlin/ru/solrudev/ackpine/gradle/AckpineExtension.kt @@ -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 = "" diff --git a/build-logic/src/main/kotlin/ru/solrudev/ackpine/gradle/publishing/AckpineLibraryPublishPlugin.kt b/build-logic/src/main/kotlin/ru/solrudev/ackpine/gradle/publishing/AckpineLibraryPublishPlugin.kt index 993605ea7..899a5e4a6 100644 --- a/build-logic/src/main/kotlin/ru/solrudev/ackpine/gradle/publishing/AckpineLibraryPublishPlugin.kt +++ b/build-logic/src/main/kotlin/ru/solrudev/ackpine/gradle/publishing/AckpineLibraryPublishPlugin.kt @@ -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 @@ -47,7 +46,7 @@ public class AckpineLibraryPublishPlugin : Plugin { apply(DokkaPlugin::class) } val ackpineExtension = extensions.getByType() - val artifact = (ackpineExtension as ExtensionAware).extensions.create("artifact") + val artifact = ackpineExtension.extensions.create("artifact") configurePublishing(ackpineExtension, artifact) configureSigning() } From 4c5432c3b2da3945a08a40450e8f5af84678b9bb Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Tue, 24 Oct 2023 10:03:08 +0500 Subject: [PATCH 02/18] make AbstractSession's newNotificationId injectable from subclasses --- .../impl/installer/session/IntentBasedInstallSession.kt | 7 +++++-- .../impl/installer/session/SessionBasedInstallSession.kt | 5 ++++- .../ackpine/impl/session/AbstractProgressSession.kt | 5 +++-- .../ru/solrudev/ackpine/impl/session/AbstractSession.kt | 5 +++-- .../ackpine/impl/uninstaller/session/UninstallSession.kt | 7 +++++-- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/installer/session/IntentBasedInstallSession.kt b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/installer/session/IntentBasedInstallSession.kt index fdff07bd4..0cdc8e000 100644 --- a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/installer/session/IntentBasedInstallSession.kt +++ b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/installer/session/IntentBasedInstallSession.kt @@ -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 @@ -64,13 +65,15 @@ internal class IntentBasedInstallSession internal constructor( sessionProgressDao: SessionProgressDao, notificationIdDao: NotificationIdDao, serialExecutor: Executor, - handler: Handler + handler: Handler, + newNotificationId: Int = globalNotificationId.incrementAndGet() ) : AbstractProgressSession( 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 diff --git a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/installer/session/SessionBasedInstallSession.kt b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/installer/session/SessionBasedInstallSession.kt index a1a5b0351..5d270fab0 100644 --- a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/installer/session/SessionBasedInstallSession.kt +++ b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/installer/session/SessionBasedInstallSession.kt @@ -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 @@ -73,12 +74,14 @@ internal class SessionBasedInstallSession internal constructor( private val executor: Executor, serialExecutor: Executor, private val handler: Handler, + newNotificationId: Int = globalNotificationId.incrementAndGet() ) : AbstractProgressSession( context, INSTALLER_NOTIFICATION_TAG, id, initialState, initialProgress, sessionDao, sessionFailureDao, sessionProgressDao, notificationIdDao, serialExecutor, handler, - exceptionalFailureFactory = InstallFailure::Exceptional + exceptionalFailureFactory = InstallFailure::Exceptional, + newNotificationId ) { @Volatile diff --git a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/session/AbstractProgressSession.kt b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/session/AbstractProgressSession.kt index 718a9672b..5649adf87 100644 --- a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/session/AbstractProgressSession.kt +++ b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/session/AbstractProgressSession.kt @@ -48,12 +48,13 @@ internal abstract class AbstractProgressSession protected construct notificationIdDao: NotificationIdDao, private val serialExecutor: Executor, private val handler: Handler, - exceptionalFailureFactory: (Exception) -> F + exceptionalFailureFactory: (Exception) -> F, + newNotificationId: Int ) : AbstractSession( context, notificationTag, id, initialState, sessionDao, sessionFailureDao, notificationIdDao, - serialExecutor, handler, exceptionalFailureFactory + serialExecutor, handler, exceptionalFailureFactory, newNotificationId ), ProgressSession { private val progressListeners = mutableSetOf() diff --git a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/session/AbstractSession.kt b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/session/AbstractSession.kt index f7a006273..51f7da0d2 100644 --- a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/session/AbstractSession.kt +++ b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/session/AbstractSession.kt @@ -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]. @@ -55,7 +56,7 @@ internal abstract class AbstractSession protected constructor( private val serialExecutor: Executor, private val handler: Handler, private val exceptionalFailureFactory: (Exception) -> F, - newNotificationId: Int = globalNotificationId.incrementAndGet() + newNotificationId: Int ) : CompletableSession { init { diff --git a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/uninstaller/session/UninstallSession.kt b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/uninstaller/session/UninstallSession.kt index b78d332d1..ec6e386e7 100644 --- a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/uninstaller/session/UninstallSession.kt +++ b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/uninstaller/session/UninstallSession.kt @@ -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 @@ -47,13 +48,15 @@ internal class UninstallSession internal constructor( sessionFailureDao: SessionFailureDao, notificationIdDao: NotificationIdDao, serialExecutor: Executor, - handler: Handler + handler: Handler, + newNotificationId: Int = globalNotificationId.incrementAndGet() ) : AbstractSession( context, UNINSTALLER_NOTIFICATION_TAG, id, initialState, sessionDao, sessionFailureDao, notificationIdDao, serialExecutor, handler, - exceptionalFailureFactory = UninstallFailure::Exceptional + exceptionalFailureFactory = UninstallFailure::Exceptional, + newNotificationId ) { override fun prepare(cancellationSignal: CancellationSignal) { From 254f23b4b0d4b6c45ff027931973ee2419551257 Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Sat, 28 Oct 2023 23:57:15 +0500 Subject: [PATCH 03/18] fix file: uris handling in sample apps --- .../ru/solrudev/ackpine/sample/install/InstallFragment.java | 6 ++++++ .../ru/solrudev/ackpine/sample/util/ContentResolverExt.kt | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/sample-java/src/main/java/ru/solrudev/ackpine/sample/install/InstallFragment.java b/sample-java/src/main/java/ru/solrudev/ackpine/sample/install/InstallFragment.java index 76a3d565a..fedd427b4 100644 --- a/sample-java/src/main/java/ru/solrudev/ackpine/sample/install/InstallFragment.java +++ b/sample-java/src/main/java/ru/solrudev/ackpine/sample/install/InstallFragment.java @@ -46,6 +46,9 @@ import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.snackbar.Snackbar; +import java.io.File; +import java.util.Objects; + import kotlin.sequences.Sequence; import kotlin.sequences.SequencesKt; import ru.solrudev.ackpine.sample.R; @@ -149,6 +152,9 @@ private Sequence getApksFromUri(@NonNull Uri uri, @NonNull String name) { @NonNull private static String getDisplayName(@NonNull ContentResolver resolver, @NonNull Uri uri) { + if (Objects.equals(uri.getScheme(), ContentResolver.SCHEME_FILE)) { + return new File(Objects.requireNonNull(uri.getPath())).getName(); + } try (final var cursor = resolver.query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)) { if (cursor == null) return ""; if (!cursor.moveToFirst()) return ""; diff --git a/sample-ktx/src/main/kotlin/ru/solrudev/ackpine/sample/util/ContentResolverExt.kt b/sample-ktx/src/main/kotlin/ru/solrudev/ackpine/sample/util/ContentResolverExt.kt index 8c3aef6af..e308cdaa1 100644 --- a/sample-ktx/src/main/kotlin/ru/solrudev/ackpine/sample/util/ContentResolverExt.kt +++ b/sample-ktx/src/main/kotlin/ru/solrudev/ackpine/sample/util/ContentResolverExt.kt @@ -19,8 +19,12 @@ package ru.solrudev.ackpine.sample.util import android.content.ContentResolver import android.net.Uri import android.provider.OpenableColumns +import androidx.core.net.toFile fun ContentResolver.getDisplayName(uri: Uri): String { + if (uri.scheme == ContentResolver.SCHEME_FILE) { + return uri.toFile().name + } query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null).use { cursor -> if (cursor == null || !cursor.moveToFirst()) { return "" From 9c1c3912c808b5bcbbf5fd4c8934ce8bc07254eb Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Sun, 29 Oct 2023 00:02:24 +0500 Subject: [PATCH 04/18] enable vibration and lights for ackpine notification channel --- .../main/kotlin/ru/solrudev/ackpine/Ackpine.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/Ackpine.kt b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/Ackpine.kt index 5077c9fea..dd0194dbd 100644 --- a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/Ackpine.kt +++ b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/Ackpine.kt @@ -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 @@ -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()?.createNotificationChannel(channel) + val channel = NotificationChannelCompat.Builder(channelIdString, importance) + .setName(channelName) + .setDescription(channelDescription) + .setVibrationEnabled(true) + .setLightsEnabled(true) + .setShowBadge(true) + .build() + NotificationManagerCompat.from(applicationContext).createNotificationChannel(channel) } } \ No newline at end of file From c0550f611e9002cce1dca1b0b1315d411b1f9a23 Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Sun, 29 Oct 2023 00:06:21 +0500 Subject: [PATCH 05/18] remove intrusive behavior for notifications and get rid of respective permissions also makes confirmation behavior consistent on different versions of android --- ackpine-core/src/main/AndroidManifest.xml | 2 - .../impl/activity/SessionCommitActivity.kt | 4 -- .../impl/activity/helpers/ActivityHelpers.kt | 57 ------------------- .../session/helpers/LaunchConfirmation.kt | 1 - 4 files changed, 64 deletions(-) delete mode 100644 ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/activity/helpers/ActivityHelpers.kt diff --git a/ackpine-core/src/main/AndroidManifest.xml b/ackpine-core/src/main/AndroidManifest.xml index 3eb9ced1c..eab522d84 100644 --- a/ackpine-core/src/main/AndroidManifest.xml +++ b/ackpine-core/src/main/AndroidManifest.xml @@ -21,8 +21,6 @@ - - diff --git a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/activity/SessionCommitActivity.kt b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/activity/SessionCommitActivity.kt index 1cddad189..452990bae 100644 --- a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/activity/SessionCommitActivity.kt +++ b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/activity/SessionCommitActivity.kt @@ -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 @@ -60,7 +58,6 @@ internal abstract class SessionCommitActivity, F : Failure> prote notifySessionCommitted() } } - turnScreenOnWhenLocked() setContentView(R.layout.ackpine_activity_session_commit) registerOnBackInvokedCallback() finishActivityOnTerminalSessionState() @@ -69,7 +66,6 @@ internal abstract class SessionCommitActivity, F : Failure> prote override fun onDestroy() { super.onDestroy() subscriptions.clear() - clearTurnScreenOnSettings() } @Deprecated("Deprecated in Java") diff --git a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/activity/helpers/ActivityHelpers.kt b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/activity/helpers/ActivityHelpers.kt deleted file mode 100644 index c1c89457c..000000000 --- a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/activity/helpers/ActivityHelpers.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2023 Ilya Fomichev - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ru.solrudev.ackpine.impl.activity.helpers - -import android.app.Activity -import android.app.KeyguardManager -import android.os.Build -import android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON -import android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD -import android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON -import android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON -import androidx.core.content.getSystemService - -@Suppress("DEPRECATION") -@JvmSynthetic -internal fun Activity.turnScreenOnWhenLocked() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { - setShowWhenLocked(true) - setTurnScreenOn(true) - } else { - window.addFlags(FLAG_KEEP_SCREEN_ON or FLAG_ALLOW_LOCK_WHILE_SCREEN_ON or FLAG_TURN_SCREEN_ON) - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - window.addFlags(FLAG_DISMISS_KEYGUARD) - } - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - getSystemService()?.requestDismissKeyguard(this, null) - } -} - -@Suppress("DEPRECATION") -@JvmSynthetic -internal fun Activity.clearTurnScreenOnSettings() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { - setShowWhenLocked(false) - setTurnScreenOn(false) - } else { - window.clearFlags(FLAG_KEEP_SCREEN_ON or FLAG_ALLOW_LOCK_WHILE_SCREEN_ON or FLAG_TURN_SCREEN_ON) - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - window.clearFlags(FLAG_DISMISS_KEYGUARD) - } - } -} \ No newline at end of file diff --git a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/session/helpers/LaunchConfirmation.kt b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/session/helpers/LaunchConfirmation.kt index f9dc3e9ce..cd1982607 100644 --- a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/session/helpers/LaunchConfirmation.kt +++ b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/impl/session/helpers/LaunchConfirmation.kt @@ -90,7 +90,6 @@ private fun Context.showNotification( setDefaults(NotificationCompat.DEFAULT_ALL) setSmallIcon(notificationData.icon) setOngoing(true) - setFullScreenIntent(intent, true) setAutoCancel(true) }.build() getSystemService()?.notify(notificationTag, notificationId, notification) From e3f4426e6fac39414b2ad4d8f94e107a0c827b44 Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Sun, 29 Oct 2023 00:06:55 +0500 Subject: [PATCH 06/18] add docs about permissions --- docs/permissions.md | 18 ++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 19 insertions(+) create mode 100644 docs/permissions.md diff --git a/docs/permissions.md b/docs/permissions.md new file mode 100644 index 000000000..815a56b57 --- /dev/null +++ b/docs/permissions.md @@ -0,0 +1,18 @@ +Permissions +=========== + +Ackpine adds the following permissions to `AndroidManifest.xml`: + +- `WRITE_EXTERNAL_STORAGE` — used by `INTENT_BASED` package installer to create temporary APK copy if needed; +- `REQUEST_INSTALL_PACKAGES` and `REQUEST_DELETE_PACKAGES` — self-explanatory; +- `VIBRATE` — to be able to set heads-up notifications' vibration when using `DEFFERED` confirmation; +- `POST_NOTIFICATIONS` — for posting notifications when using `DEFFERED` confirmation; +- `UPDATE_PACKAGES_WITHOUT_USER_ACTION` — to be able to leverage `requireUserAction` feature on API >= 31. + +If you don't need some of the features listed above and don't want to have unneeded permissions in your app, you can remove them from the resulting merged `AndroidManifest.xml`, but do so carefully to not break the library functionality you use: + +```xml + +``` \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 41d55c49c..61a7c8802 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -46,6 +46,7 @@ nav: - 'Configuration': configuration.md - 'Split APKs': split_apks.md - 'Samples': samples.md + - 'Permissions': permissions.md - 'API reference ⏏': api/index.html - 'Change Log': changelog.md From 623b11efabf277ab8375e0f7425c3ed4bd0a8ad0 Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Sun, 29 Oct 2023 00:07:23 +0500 Subject: [PATCH 07/18] make session commit activity semi-transparent --- .../main/res/layout/ackpine_activity_session_commit.xml | 1 + .../src/main/res/{values-night => values-v19}/themes.xml | 8 +++++++- ackpine-core/src/main/res/values/themes.xml | 8 +++++++- 3 files changed, 15 insertions(+), 2 deletions(-) rename ackpine-core/src/main/res/{values-night => values-v19}/themes.xml (65%) diff --git a/ackpine-core/src/main/res/layout/ackpine_activity_session_commit.xml b/ackpine-core/src/main/res/layout/ackpine_activity_session_commit.xml index ca9a6787d..850eaaca4 100644 --- a/ackpine-core/src/main/res/layout/ackpine_activity_session_commit.xml +++ b/ackpine-core/src/main/res/layout/ackpine_activity_session_commit.xml @@ -22,5 +22,6 @@ \ No newline at end of file diff --git a/ackpine-core/src/main/res/values-night/themes.xml b/ackpine-core/src/main/res/values-v19/themes.xml similarity index 65% rename from ackpine-core/src/main/res/values-night/themes.xml rename to ackpine-core/src/main/res/values-v19/themes.xml index 321239861..66541abb0 100644 --- a/ackpine-core/src/main/res/values-night/themes.xml +++ b/ackpine-core/src/main/res/values-v19/themes.xml @@ -16,5 +16,11 @@ --> - + + + + From 5ce8d54e6417021ff45df6f673b2be464c8a5dea Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Sun, 29 Oct 2023 00:40:21 +0500 Subject: [PATCH 12/18] fix spelling --- docs/permissions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/permissions.md b/docs/permissions.md index 815a56b57..710b79c4e 100644 --- a/docs/permissions.md +++ b/docs/permissions.md @@ -5,8 +5,8 @@ Ackpine adds the following permissions to `AndroidManifest.xml`: - `WRITE_EXTERNAL_STORAGE` — used by `INTENT_BASED` package installer to create temporary APK copy if needed; - `REQUEST_INSTALL_PACKAGES` and `REQUEST_DELETE_PACKAGES` — self-explanatory; -- `VIBRATE` — to be able to set heads-up notifications' vibration when using `DEFFERED` confirmation; -- `POST_NOTIFICATIONS` — for posting notifications when using `DEFFERED` confirmation; +- `VIBRATE` — to be able to set heads-up notifications' vibration when using `DEFERRED` confirmation; +- `POST_NOTIFICATIONS` — for posting notifications when using `DEFERRED` confirmation; - `UPDATE_PACKAGES_WITHOUT_USER_ACTION` — to be able to leverage `requireUserAction` feature on API >= 31. If you don't need some of the features listed above and don't want to have unneeded permissions in your app, you can remove them from the resulting merged `AndroidManifest.xml`, but do so carefully to not break the library functionality you use: From f452da34aa0f6acba691ff0e61595d1f1e65f31e Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Sun, 29 Oct 2023 00:55:42 +0500 Subject: [PATCH 13/18] update Confirmation.DEFERRED docs about permissions --- .../ru/solrudev/ackpine/session/parameters/Confirmation.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/session/parameters/Confirmation.kt b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/session/parameters/Confirmation.kt index 4febee83a..e5d27e81b 100644 --- a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/session/parameters/Confirmation.kt +++ b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/session/parameters/Confirmation.kt @@ -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 @@ -41,7 +39,7 @@ public enum class Confirmation { /** * Show a high-priority notification (full-screen intent) 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 } \ No newline at end of file From 09fa29ab46263007fa05052617cd8825ec1533d3 Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Sun, 29 Oct 2023 00:57:47 +0500 Subject: [PATCH 14/18] add version 0.2.0 to changelog --- docs/changelog.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index b55cd8160..900415529 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -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. + Version 0.1.6 (2023-10-17) -------------------------- From aa58d809bfc0c854f718d829a8e3a0925825a9b6 Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Sun, 29 Oct 2023 01:01:30 +0500 Subject: [PATCH 15/18] remove mentions of full-screen intent from docs --- .../solrudev/ackpine/session/parameters/Confirmation.kt | 6 +++--- docs/configuration.md | 9 +++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/session/parameters/Confirmation.kt b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/session/parameters/Confirmation.kt index e5d27e81b..6a9350f14 100644 --- a/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/session/parameters/Confirmation.kt +++ b/ackpine-core/src/main/kotlin/ru/solrudev/ackpine/session/parameters/Confirmation.kt @@ -26,8 +26,8 @@ import ru.solrudev.ackpine.session.parameters.Confirmation.IMMEDIATE * * * [IMMEDIATE] — 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) — user will be shown a high-priority notification (full-screen intent) which will launch - * confirmation activity. + * * [DEFERRED] (default) — user will be shown a high-priority notification which will launch confirmation + * activity. */ public enum class Confirmation { @@ -37,7 +37,7 @@ 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 [VIBRATE], [POST_NOTIFICATIONS] permissions. */ diff --git a/docs/configuration.md b/docs/configuration.md index ba061c6af..b64063199 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -47,14 +47,11 @@ An example of creating a session with custom parameters: User's confirmation ------------------- -A strategy for handling user's confirmation of installation or uninstallation. -Can be `DEFERRED` (used by default) or `IMMEDIATE`. +A strategy for handling user's confirmation of installation or uninstallation. Can be `DEFERRED` (used by default) or `IMMEDIATE`. -`DEFERRED` (default) — user will be shown a high-priority notification (full-screen intent) which -will launch confirmation activity. +`DEFERRED` (default) — user will be shown a high-priority notification which will launch confirmation activity. -`IMMEDIATE` — user will be prompted to confirm installation or uninstallation right away. Suitable -for launching session directly from the UI when app is in foreground. +`IMMEDIATE` — user will be prompted to confirm installation or uninstallation right away. Suitable for launching session directly from the UI when app is in foreground. Notification ------------ From f9fc5163f78462191b4ca7dbe7f59919b7e859f4 Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Sun, 29 Oct 2023 01:09:38 +0500 Subject: [PATCH 16/18] improve configuration docs --- docs/configuration.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index b64063199..6f2f9eef0 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -53,6 +53,8 @@ A strategy for handling user's confirmation of installation or uninstallation. C `IMMEDIATE` — user will be prompted to confirm installation or uninstallation right away. Suitable for launching session directly from the UI when app is in foreground. +It's also possible to configure `requireUserAction` option for install sessions. It will have effect only on API level >= 31. If set to `false`, user's confirmation from system won't be triggered if some conditions are met. See the details [here](https://developer.android.com/reference/android/content/pm/PackageInstaller.SessionParams#setRequireUserAction(int)). + Notification ------------ @@ -68,12 +70,12 @@ It is possible to provide notification title, text and icon. Session name ------------ -You can provide an optional session `name` parameter to be used in default notification content text. It may be a name of the app being installed or a file name. It will be ignored if you specify custom notification content text or set `Confirmation` to `IMMEDIATE`. +Available for install sessions. You can provide an optional session `name` parameter to be used in default notification content text. It may be a name of the app being installed or a file name. It will be ignored if you specify custom notification content text or set `Confirmation` to `IMMEDIATE`. Installer type -------------- -Ackpine supports two different package installer implementations: Android's `PackageInstaller` and an intent with `ACTION_INSTALL_PACKAGE` action. They're configured with `InstallerType` enum with entries `SESSION_BASED` and `INTENT_BASED` respectively. +Available for install sessions. Ackpine supports two different package installer implementations: Android's `PackageInstaller` and an intent with `ACTION_INSTALL_PACKAGE` action. They're configured with `InstallerType` enum with entries `SESSION_BASED` and `INTENT_BASED` respectively. `InstallParameters` builder will maintain the following invariants when configuring the installer type: From a210b7a18f7be81f1f970d8c5efb345fb03e0994 Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Sun, 29 Oct 2023 01:42:36 +0500 Subject: [PATCH 17/18] add docs about observing progress --- docs/getting_started.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/getting_started.md b/docs/getting_started.md index fe90d6faf..a60eee15b 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -138,4 +138,27 @@ Handling process death is not any different with Ackpine as with any other persi ``` -Every example on this page is using `PackageInstaller`, but APIs for `PackageUninstaller` are absolutely the same. \ No newline at end of file +Observing progress +------------------ + +Install sessions provide progress updates: + +=== "Kotlin" + + ```kotlin + session.progress // Flow + .onEach { progress -> + updateProgress(progress.progress, progress.max) + } + .launchIn(coroutineScope) + ``` + +=== "Java" + + ```java + var subscription = session.addProgressListener((sessionId, progress) -> { + updateProgress(progress.getProgress(), progress.getMax()); + }); + ``` + +Every example on this page is using `PackageInstaller`, but APIs for `PackageUninstaller` are absolutely the same except for progress updates. \ No newline at end of file From c5057701c97d66706421c4b4cb43ee7d4bf467d6 Mon Sep 17 00:00:00 2001 From: Ilya Fomichev Date: Sun, 29 Oct 2023 01:42:59 +0500 Subject: [PATCH 18/18] add docs about library's architecture --- docs/architecture.md | 14 ++++++++++++++ docs/changelog.md | 2 +- mkdocs.yml | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 docs/architecture.md diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 000000000..a4f9759c7 --- /dev/null +++ b/docs/architecture.md @@ -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. \ No newline at end of file diff --git a/docs/changelog.md b/docs/changelog.md index 900415529..ae313188f 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -15,7 +15,7 @@ Version 0.2.0 (2023-10-28) - 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. +- Add documentation about permissions and library's architecture. Version 0.1.6 (2023-10-17) -------------------------- diff --git a/mkdocs.yml b/mkdocs.yml index 61a7c8802..8bdc35094 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -47,6 +47,7 @@ nav: - 'Split APKs': split_apks.md - 'Samples': samples.md - 'Permissions': permissions.md + - 'Architecture': architecture.md - 'API reference ⏏': api/index.html - 'Change Log': changelog.md