Fix phonebook refresh flicker and duplicate Android recent tasks
Tests / test (push) Canceled after 0s
Build Android (FCM) / build-android-fcm (push) Canceled after 0s
Build Android / build-android (push) Canceled after 0s
Build iOS / build-ios (push) Canceled after 0s
Build Linux / build-linux (push) Canceled after 0s
Build macOS / build-macos (push) Canceled after 0s
Build Windows / build-windows (push) Canceled after 0s
Native crypto / native-crypto (push) Canceled after 0s
Release (main) / android (oneme) (push) Canceled after 0s
Release (main) / android (qlyra) (push) Canceled after 0s
Release (main) / windows (push) Canceled after 0s
Release (main) / linux (push) Canceled after 0s
Release (main) / macos (push) Canceled after 0s
Release (main) / ios (push) Canceled after 0s
Release (main) / release (push) Canceled after 0s
Tests / test (push) Canceled after 0s
Build Android (FCM) / build-android-fcm (push) Canceled after 0s
Build Android / build-android (push) Canceled after 0s
Build iOS / build-ios (push) Canceled after 0s
Build Linux / build-linux (push) Canceled after 0s
Build macOS / build-macos (push) Canceled after 0s
Build Windows / build-windows (push) Canceled after 0s
Native crypto / native-crypto (push) Canceled after 0s
Release (main) / android (oneme) (push) Canceled after 0s
Release (main) / android (qlyra) (push) Canceled after 0s
Release (main) / windows (push) Canceled after 0s
Release (main) / linux (push) Canceled after 0s
Release (main) / macos (push) Canceled after 0s
Release (main) / ios (push) Canceled after 0s
Release (main) / release (push) Canceled after 0s
This commit is contained in:
@@ -179,6 +179,7 @@ android.applicationVariants.all {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||
implementation("androidx.media3:media3-transformer:1.9.3")
|
||||
implementation("androidx.media3:media3-effect:1.9.3")
|
||||
|
||||
@@ -51,7 +51,8 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="ИКАР"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity=""
|
||||
android:taskAffinity="ru.ikar.app"
|
||||
android:documentLaunchMode="never"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
|
||||
@@ -145,6 +145,10 @@ class MainActivity : AudioServiceFragmentActivity() {
|
||||
result.success(apkSignerMatchesInstalled(path))
|
||||
}
|
||||
}
|
||||
"launchApkInstaller" -> {
|
||||
val path = call.argument<String>("path")
|
||||
result.success(path != null && launchApkInstaller(path))
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
@@ -582,7 +586,36 @@ class MainActivity : AudioServiceFragmentActivity() {
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
private fun launchApkInstaller(path: String): Boolean {
|
||||
return try {
|
||||
val apk = File(path).canonicalFile
|
||||
if (!apk.isFile ||
|
||||
!apk.path.startsWith(cacheDir.canonicalPath + File.separator) ||
|
||||
!apkSignerMatchesInstalled(apk.path)
|
||||
) return false
|
||||
val uri = androidx.core.content.FileProvider.getUriForFile(
|
||||
this,
|
||||
"$packageName.fileProvider.com.crazecoder.openfile",
|
||||
apk,
|
||||
)
|
||||
val installer = Intent(Intent.ACTION_VIEW).apply {
|
||||
addCategory(Intent.CATEGORY_DEFAULT)
|
||||
setDataAndType(uri, "application/vnd.android.package-archive")
|
||||
addFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK or
|
||||
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION,
|
||||
)
|
||||
}
|
||||
startActivity(installer)
|
||||
true
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
RecentAppTasks.removeDuplicates(this)
|
||||
if (intent?.hasExtra(CallConst.EXTRA_CALL) == true) applyCallWindowFlags()
|
||||
super.onCreate(savedInstanceState)
|
||||
applyKeepAwake()
|
||||
@@ -594,6 +627,7 @@ class MainActivity : AudioServiceFragmentActivity() {
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
RecentAppTasks.removeDuplicates(this)
|
||||
if (intent.hasExtra(CallConst.EXTRA_CALL)) {
|
||||
applyCallWindowFlags()
|
||||
stashCall(intent, emit = true)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package ru.ikar.app
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.ActivityManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
|
||||
/** Removes old launcher cards retained across an APK replacement. */
|
||||
internal object RecentAppTasks {
|
||||
fun removeDuplicates(activity: Activity) {
|
||||
val manager = activity.getSystemService(ActivityManager::class.java) ?: return
|
||||
val tasks = runCatching { manager.appTasks }.getOrElse { return }
|
||||
for (task in tasks) {
|
||||
try {
|
||||
val info = task.taskInfo ?: continue
|
||||
@Suppress("DEPRECATION")
|
||||
val id = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
info.taskId
|
||||
} else {
|
||||
info.persistentId
|
||||
}
|
||||
val root = info.baseIntent.component ?: info.baseActivity
|
||||
if (RecentTaskPolicy.isDuplicateHome(
|
||||
activity.taskId, id, activity.packageName,
|
||||
root?.packageName, root?.className,
|
||||
)
|
||||
) {
|
||||
task.finishAndRemoveTask()
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
// OEM task snapshots can disappear between enumeration and removal.
|
||||
Log.w("IkarRecentTasks", "A stale task could not be removed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package ru.ikar.app
|
||||
|
||||
internal object RecentTaskPolicy {
|
||||
fun isDuplicateHome(
|
||||
currentId: Int,
|
||||
candidateId: Int,
|
||||
applicationPackage: String,
|
||||
rootPackage: String?,
|
||||
rootClass: String?,
|
||||
): Boolean {
|
||||
if (candidateId < 0 || candidateId == currentId) return false
|
||||
if (rootPackage != applicationPackage) return false
|
||||
return rootClass in setOf(
|
||||
"$applicationPackage.MainActivity",
|
||||
"$applicationPackage.MinimalIcon",
|
||||
"$applicationPackage.IntentEntry",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package ru.ikar.app
|
||||
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class RecentTaskPolicyTest {
|
||||
private val app = "ru.ikar.app"
|
||||
|
||||
@Test
|
||||
fun currentWindowIsNeverRemoved() {
|
||||
assertFalse(RecentTaskPolicy.isDuplicateHome(12, 12, app, app, "$app.MainActivity"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun allOldHomeEntryPointsAreRecognized() {
|
||||
for (entry in listOf("MainActivity", "MinimalIcon", "IntentEntry")) {
|
||||
assertTrue(RecentTaskPolicy.isDuplicateHome(12, 9, app, app, "$app.$entry"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun anotherApplicationsTaskIsPreserved() {
|
||||
assertFalse(RecentTaskPolicy.isDuplicateHome(12, 9, app, "com.android.packageinstaller", "$app.MainActivity"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun otherAppActivitiesArePreserved() {
|
||||
assertFalse(RecentTaskPolicy.isDuplicateHome(12, 9, app, app, "$app.MediaPicker"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun disappearedTaskIsIgnored() {
|
||||
assertFalse(RecentTaskPolicy.isDuplicateHome(12, -1, app, app, "$app.MainActivity"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unidentifiedRootIsPreserved() {
|
||||
assertFalse(RecentTaskPolicy.isDuplicateHome(12, 9, app, null, null))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user