Performance Mode bug fixes

This commit is contained in:
DarkKnight2122
2026-04-24 16:08:04 +05:30
parent 0e549a1565
commit c988c489e7
4 changed files with 301 additions and 107 deletions

View File

@@ -55,9 +55,7 @@ class Global : ConfigContainer() {
}
}
val mediaUploadQualityConfig = container("media_upload_quality", MediaUploadQualityConfig())
val performanceMode = container("performance_mode", PerformanceModeConfig()) { requireRestart() }.apply {
profile.set("max")
}
val performanceMode = container("performance_mode", PerformanceModeConfig()) { requireRestart() }
val disableConfirmationDialogs = multiple("disable_confirmation_dialogs", "erase_message", "remove_friend", "block_friend", "ignore_friend", "hide_friend", "hide_conversation", "clear_conversation") { requireRestart() }
val disableMetrics = boolean("disable_metrics") { requireRestart() }
val disableStorySections = multiple("disable_story_sections", "friends", "suggested_stories", "following", "discover") { requireRestart(); requireCleanCache() }

View File

@@ -128,6 +128,16 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A
}
}
// Background Watchdog: Periodically refreshes UI and verifies engine health
this@AutoOpenSnaps.context.coroutineScope.launch(Dispatchers.Default) {
while (isActive && engineActive.get()) {
if (autoOpenConfig.globalState == true) {
updateStatusNotification()
}
delay(5000)
}
}
setupReceivers()
startEngineWorker()
setupDetector()
@@ -158,12 +168,12 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A
processSnapItem(item)
lastSnapProcessedAt.set(System.currentTimeMillis())
// HIGH SPEED: 10ms floor for 20+ snaps/s
// Process at natural network speed when safety is disabled
val baseDelay = if (currentSpeedText == "Throttled") 3000L else (autoOpenConfig.delayBetweenSnaps as PropertyValue<Int>).get().toLong()
if (isSafe) {
delay(Random.nextLong(baseDelay, baseDelay + 200))
} else {
delay(baseDelay.coerceAtMost(10))
if (baseDelay > 0) delay(baseDelay)
}
if (synchronized(queuedSnaps) { queuedSnaps.isEmpty() }) {
@@ -175,6 +185,13 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A
}
private suspend fun processSnapItem(item: SnapQueueItem) {
// Verify database state on background thread before processing
val dbMessage = withContext(Dispatchers.IO) { this@AutoOpenSnaps.context.database.getConversationMessageFromId(item.messageId) }
if (dbMessage?.isViewedByUser == 1) {
synchronized(queuedSnaps) { queuedSnaps.remove(item) }
return
}
currentStatusText = "Active"; updateStatusNotification()
var success = false
val startTime = System.currentTimeMillis()
@@ -186,7 +203,7 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A
delay(1000)
}
success = withContext(Dispatchers.IO) { performOpen(item) }
success = performOpen(item)
if (success) {
synchronized(queuedSnaps) { queuedSnaps.remove(item) }
sessionProcessed.incrementAndGet(); totalProcessed.incrementAndGet(); recordSpeedTimestamp()
@@ -200,23 +217,24 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A
logError("Engine failed to open Snap: ${item.messageId}")
synchronized(queuedSnaps) { queuedSnaps.remove(item) }
currentStatusText = "Failed: ${item.senderName}"; updateStatusNotification()
openedSnapsIds.remove(item.messageId)
}
}
private suspend fun performOpen(item: SnapQueueItem): Boolean {
val manager = messaging.conversationManager ?: return false
return suspendCancellableCoroutine { cont ->
runCatching {
manager.updateMessage(item.conversationId, item.messageId, MessageUpdate.READ) { result ->
if (result == null || result == "DUPLICATEREQUEST") { cont.resume(true) }
else if (item.serverMessageId != 0L) {
manager.updateMessage(item.conversationId, item.serverMessageId, MessageUpdate.READ) { serverResult ->
cont.resume(serverResult == null || serverResult == "DUPLICATEREQUEST")
}
} else { cont.resume(false) }
}
}.onFailure { logError("Bridge Error", it); cont.resume(false) }
return withContext(Dispatchers.Main) {
suspendCancellableCoroutine { cont ->
runCatching {
manager.updateMessage(item.conversationId, item.messageId, MessageUpdate.READ) { result ->
if (result == null || result == "DUPLICATEREQUEST") { cont.resume(true) }
else if (item.serverMessageId != 0L) {
manager.updateMessage(item.conversationId, item.serverMessageId, MessageUpdate.READ) { serverResult ->
cont.resume(serverResult == null || serverResult == "DUPLICATEREQUEST")
}
} else { cont.resume(false) }
}
}.onFailure { logError("Bridge Error", it); cont.resume(false) }
}
}
}
@@ -250,13 +268,17 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A
if (autoOpenConfig.globalState == false || !engineActive.get()) return@subscribe
val message = event.message
if (message.messageState != MessageState.COMMITTED || message.senderId?.toString() == this@AutoOpenSnaps.context.database.myUserId) return@subscribe
val conversationId = message.messageDescriptor?.conversationId?.toString() ?: return@subscribe
val clientMessageId = message.messageDescriptor?.messageId ?: return@subscribe
val conversationId = message.messageDescriptor?.conversationId?.toString() ?: return@subscribe
val serverMessageId = message.orderKey ?: 0L
val contentType = message.messageContent?.contentType
if (contentType != ContentType.SNAP && contentType != ContentType.EXTERNAL_MEDIA) return@subscribe
if (!canUseRule(conversationId)) return@subscribe
// Prevent re-queueing the same message while it is currently being processed
if (openedSnapsIds.contains(clientMessageId)) return@subscribe
openedSnapsIds.add(clientMessageId)
@@ -295,7 +317,7 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A
private fun isWifiConnected(): Boolean {
val cm = this@AutoOpenSnaps.context.androidContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
// RESILIENT WIFI CHECK: Iterates through all networks to find ANY WiFi transport (VPN aware)
// Check for any available network with a WiFi or Ethernet transport
return cm.allNetworks.any { network ->
cm.getNetworkCapabilities(network)?.let { caps ->
caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||

View File

@@ -1,7 +1,13 @@
package me.eternal.purrfectsnap.core.features.impl.messaging
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
@@ -64,11 +70,25 @@ import kotlin.time.toDuration
class SendOverride : Feature("Send Override") {
companion object {
private const val NOTIFICATION_CHANNEL_ID = "scheduled_send"
private const val CONTINUOUS_SEND_CHANNEL_ID = "continuous_send_status"
private const val STATUS_NOTIFICATION_ID = 54322
private const val COMPLETION_NOTIFICATION_ID = 54323
const val ACTION_PAUSE_RESUME = "me.eternal.purrfectsnap.CONTINUOUS_SEND_PAUSE_RESUME"
const val ACTION_STOP = "me.eternal.purrfectsnap.CONTINUOUS_SEND_STOP"
private val internalMultipartSend = ThreadLocal.withInitial { false }
private var queuedOriginalItemRepeatCount = 0
private var queuedOriginalItemRepeatOverrideType: String? = null
private var queuedOriginalItemRepeatSnapDurationMs: Int? = null
// Notification & Loop Tracking
private var totalRepeatCount = 0
private var processedRepeatCount = 0
private var currentRecipientName: String = "Unknown"
private val isPaused = java.util.concurrent.atomic.AtomicBoolean(false)
private val isStopped = java.util.concurrent.atomic.AtomicBoolean(false)
private fun queueOriginalItemRepeats(repeatCount: Int, overrideType: String, snapDurationMs: Int?) {
queuedOriginalItemRepeatCount = repeatCount
queuedOriginalItemRepeatOverrideType = overrideType
@@ -116,6 +136,80 @@ class SendOverride : Feature("Send Override") {
private val backgroundHookLock = Any()
private var backgroundHookRefs = 0
private var backgroundHooks: List<Hooker.HookHandle>? = null
private val engineActive = java.util.concurrent.atomic.AtomicBoolean(true)
private fun createContinuousNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = context.androidContext.getSystemService(NotificationManager::class.java)
val channel = NotificationChannel(
CONTINUOUS_SEND_CHANNEL_ID,
"Continuous Send",
NotificationManager.IMPORTANCE_LOW
)
channel.description = "Progress status for continuous snap sending"
notificationManager.createNotificationChannel(channel)
}
}
private fun updateContinuousSendNotification() {
if (!engineActive.get() || isStopped.get()) return
val notificationManager = context.androidContext.getSystemService(NotificationManager::class.java)
val remaining = queuedOriginalItemRepeatCount
val processed = processedRepeatCount
val total = totalRepeatCount
val isWorking = (remaining > 0 || (total > 0 && processed < total)) && !isStopped.get()
if (!isWorking) {
notificationManager.cancel(STATUS_NOTIFICATION_ID)
showCompletionNotification(processed, total)
return
}
val progressPercent = if (total > 0) (processed * 100) / total else 0
val builder = Notification.Builder(context.androidContext, CONTINUOUS_SEND_CHANNEL_ID)
.setOngoing(true)
.setOnlyAlertOnce(true)
.setSmallIcon(android.R.drawable.ic_popup_sync) // The Industrial Loop icon
.setColor(0xFF3498DB.toInt()) // Industrial Purple/Blue tint
.setContentTitle("Sending Snaps to $currentRecipientName")
.setContentText("Progress: $processed / $total ($progressPercent%)")
.setSubText("$processed / $total")
.setProgress(total, processed, false)
val pauseResumeLabel = if (isPaused.get()) "Resume" else "Pause"
builder.addAction(Notification.Action.Builder(null, pauseResumeLabel, createPendingIntent(ACTION_PAUSE_RESUME)).build())
builder.addAction(Notification.Action.Builder(null, "Stop", createPendingIntent(ACTION_STOP)).build())
notificationManager.notify(STATUS_NOTIFICATION_ID, builder.build())
}
private fun showCompletionNotification(sent: Int, total: Int) {
val title = if (isStopped.get()) "Continuous Send Stopped" else "Continuous Send Finished"
val content = "Successfully sent $sent / $total snaps to $currentRecipientName"
val notificationManager = context.androidContext.getSystemService(NotificationManager::class.java)
val builder = Notification.Builder(context.androidContext, CONTINUOUS_SEND_CHANNEL_ID)
.setSmallIcon(android.R.drawable.checkbox_on_background) // Emerald Green Checkmark
.setColor(0xFF2ECC71.toInt()) // Emerald Green tint
.setContentTitle(title)
.setContentText(content)
.setAutoCancel(true)
notificationManager.notify(COMPLETION_NOTIFICATION_ID, builder.build())
}
private fun createPendingIntent(action: String): PendingIntent {
val intent = Intent(action).setPackage(context.androidContext.packageName)
return PendingIntent.getBroadcast(
context.androidContext,
action.hashCode(),
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}
private fun acquireScheduledSendBackground(): () -> Unit {
if (!context.config.messaging.scheduledSendAllowRunningInBackground.get()) return {}
var enableFailed = false
@@ -196,7 +290,35 @@ class SendOverride : Feature("Send Override") {
@OptIn(ExperimentalLayoutApi::class)
override fun init() {
createNotificationChannel()
createContinuousNotificationChannel()
val actionReceiver = object : BroadcastReceiver() {
override fun onReceive(ctx: Context?, intent: Intent?) {
when (intent?.action) {
ACTION_PAUSE_RESUME -> {
isPaused.set(!isPaused.get())
updateContinuousSendNotification()
}
ACTION_STOP -> {
isStopped.set(true)
if (isPaused.get()) {
isPaused.set(false)
}
updateContinuousSendNotification()
}
}
}
}
val filter = IntentFilter().apply {
addAction(ACTION_PAUSE_RESUME)
addAction(ACTION_STOP)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.androidContext.registerReceiver(actionReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
} else {
context.androidContext.registerReceiver(actionReceiver, filter)
}
val stripMediaMetadata = context.config.messaging.stripMediaMetadata.get()
var postSavePolicy: Int? = null
@@ -1429,6 +1551,11 @@ class SendOverride : Feature("Send Override") {
invokeOriginalAndRestoreResult(event)
}
} else if (MediaFilePicker.hasReusableOriginalItem()) {
totalRepeatCount = repeatCount
processedRepeatCount = 1
currentRecipientName = recipientNameForTask
updateContinuousSendNotification()
queueOriginalItemRepeats(repeatCount - 1, finalSelectedType, selectedSnapDurationMs)
attachQueuedRepeatCallbacks(event)
if (sendMedia(finalSelectedType, selectedSnapDurationMs)) {
@@ -1437,6 +1564,11 @@ class SendOverride : Feature("Send Override") {
clearQueuedOriginalItemRepeats()
}
} else {
totalRepeatCount = repeatCount
processedRepeatCount = 0
currentRecipientName = recipientNameForTask
updateContinuousSendNotification()
sendRepeatedMediaManual(
repeatCount,
finalSelectedType,

View File

@@ -2,29 +2,35 @@ package me.eternal.purrfectsnap.core.features.impl.tweaks
import android.animation.ValueAnimator
import android.app.Activity
import android.app.Dialog
import android.database.sqlite.SQLiteDatabase
import android.hardware.camera2.CaptureRequest
import android.media.MediaRecorder
import android.os.Build
import android.transition.Transition
import android.os.HandlerThread
import android.os.Process
import android.transition.Transition
import android.util.Range
import android.view.View
import android.view.TextureView
import android.view.ViewPropertyAnimator
import android.view.WindowManager
import android.view.animation.Animation
import android.widget.OverScroller
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import java.lang.Thread
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.ThreadPoolExecutor
import me.eternal.purrfectsnap.core.features.Feature
import me.eternal.purrfectsnap.core.event.events.impl.NetworkApiRequestEvent
import me.eternal.purrfectsnap.core.util.hook.HookStage
import me.eternal.purrfectsnap.core.util.hook.findRestrictedMethod
import me.eternal.purrfectsnap.core.util.hook.hook
import me.eternal.purrfectsnap.core.util.hook.hookConstructor
import okhttp3.Dispatcher
import java.lang.Thread
import java.lang.reflect.Method
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.atomic.AtomicBoolean
class PerformanceMode : Feature("Performance Mode") {
override fun init() {
@@ -43,7 +49,7 @@ class PerformanceMode : Feature("Performance Mode") {
val minimumCoreThreads = if (isMaxProfile) 16 else 8
val prefetchItemCount = if (isMaxProfile) 24 else 12
val maxAnimationDurationMs = if (isMaxProfile) 90L else 140L
val maxScrollDurationMs = if (isMaxProfile) 120 else 180
val maxScrollDurationMs = if (isMaxProfile) 72 else 180
val preferredRefreshRate = if (isMaxProfile) 120f else 90f
context.log.info(
@@ -53,52 +59,33 @@ class PerformanceMode : Feature("Performance Mode") {
runCatching {
ValueAnimator.setFrameDelay(0L)
context.log.info("Applied ValueAnimator frame delay override: 0ms", "PerformanceMode")
}
fun firstHitLogger(name: String): (String) -> Unit {
val didLog = AtomicBoolean(false)
return { details ->
if (didLog.compareAndSet(false, true)) {
context.log.info("First hit: $name | $details", "PerformanceMode")
}
}
}
val handlerThreadConstructorLog = firstHitLogger("HandlerThread.constructor")
val handlerThreadStartLog = firstHitLogger("HandlerThread.start")
val threadStartLog = firstHitLogger("Thread.start")
val executorLog = firstHitLogger("ThreadPoolExecutor.constructor")
val dispatcherLog = firstHitLogger("OkHttp.Dispatcher.constructor")
val animatorLog = firstHitLogger("ValueAnimator.getDurationScale")
val animatorDurationLog = firstHitLogger("ValueAnimator.setDuration")
val viewAnimatorDurationLog = firstHitLogger("ViewPropertyAnimator.setDuration")
val transitionDurationLog = firstHitLogger("Transition.setDuration")
val animationDurationLog = firstHitLogger("Animation.setDuration")
val recyclerCtorLog = firstHitLogger("RecyclerView.constructor")
val recyclerAdapterLog = firstHitLogger("RecyclerView.setAdapter")
val recyclerLayoutManagerLog = firstHitLogger("RecyclerView.setLayoutManager")
val sqliteOpenLog = firstHitLogger("SQLiteDatabase.openDatabase")
val sqliteCreateLog = firstHitLogger("SQLiteDatabase.openOrCreateDatabase")
val mediaRecorderLog = firstHitLogger("MediaRecorder.setVideoFrameRate")
val captureRequestLog = firstHitLogger("CaptureRequest.Builder.set")
val sustainedModeLog = firstHitLogger("Window.setSustainedPerformanceMode")
val refreshRateLog = firstHitLogger("Activity.preferredRefreshRate")
val overScrollerLog = firstHitLogger("OverScroller.startScroll")
fun isPerformanceSensitiveThread(name: String?): Boolean {
val normalizedName = name?.lowercase() ?: return false
return listOf("camera", "preview", "codec", "render", "gl", "transcod", "lens", "feed", "story", "opera", "messag", "network", "db", "disk").any {
return listOf("camera", "preview", "codec", "render", "gl", "transcod", "lens", "feed", "story", "opera", "messag", "network", "db", "disk", "map", "mapbox", "snapmap", "viewport").any {
normalizedName.contains(it)
}
}
fun clampPositiveDuration(durationMs: Long, maxDurationMs: Long): Long {
if (durationMs <= 0L) return durationMs
return durationMs.coerceAtMost(maxDurationMs)
}
context.event.subscribe(NetworkApiRequestEvent::class) { event ->
if (!isMaxProfile) return@subscribe
val url = event.url
if (url.contains("mapbox") && (url.contains("events.") || url.contains("telemetry"))) {
event.canceled = true
}
}
HandlerThread::class.java.hookConstructor(HookStage.BEFORE) { param ->
if (param.args().size < 2) return@hookConstructor
val threadName = param.argNullable<String>(0)
if (!isPerformanceSensitiveThread(threadName)) return@hookConstructor
param.setArg(1, threadPriority)
handlerThreadConstructorLog("name=$threadName priority=$threadPriority")
}
HandlerThread::class.java.hook("start", HookStage.AFTER) { param ->
@@ -110,16 +97,14 @@ class PerformanceMode : Feature("Performance Mode") {
Process.setThreadPriority(tid, threadPriority)
}
}
handlerThreadStartLog("name=${thread.name} tid=${thread.threadId} priority=$threadPriority")
}
Thread::class.java.hook("start", HookStage.AFTER) { param ->
val thread = param.thisObject<Thread>()
if (!isPerformanceSensitiveThread(thread.name)) return@hook
runCatching {
thread.priority = Thread.MAX_PRIORITY
thread.priority = if (isMaxProfile) Thread.MAX_PRIORITY else Thread.NORM_PRIORITY + 1
}
threadStartLog("name=${thread.name} priority=${thread.priority}")
}
ThreadPoolExecutor::class.java.hookConstructor(HookStage.AFTER) { param ->
@@ -131,7 +116,6 @@ class PerformanceMode : Feature("Performance Mode") {
}
executor.allowCoreThreadTimeOut(false)
executor.prestartAllCoreThreads()
executorLog("core=${executor.corePoolSize} max=${executor.maximumPoolSize} active=${executor.activeCount}")
}
}
@@ -140,13 +124,11 @@ class PerformanceMode : Feature("Performance Mode") {
runCatching {
dispatcher.maxRequests = maxRequests
dispatcher.maxRequestsPerHost = maxRequestsPerHost
dispatcherLog("maxRequests=${dispatcher.maxRequests} maxRequestsPerHost=${dispatcher.maxRequestsPerHost}")
}
}
ValueAnimator::class.java.hook("getDurationScale", HookStage.AFTER) { param ->
param.setResult(durationScale)
animatorLog("durationScale=$durationScale")
}
ValueAnimator::class.java.hook("setDuration", HookStage.BEFORE) { param ->
@@ -155,7 +137,6 @@ class PerformanceMode : Feature("Performance Mode") {
if (updated != original) {
param.setArg(0, updated)
}
animatorDurationLog("requested=$original applied=${param.arg<Long>(0)}")
}
ViewPropertyAnimator::class.java.hook("setDuration", HookStage.BEFORE) { param ->
@@ -164,7 +145,6 @@ class PerformanceMode : Feature("Performance Mode") {
if (updated != original) {
param.setArg(0, updated)
}
viewAnimatorDurationLog("requested=$original applied=${param.arg<Long>(0)}")
}
Transition::class.java.hook("setDuration", HookStage.BEFORE) { param ->
@@ -173,7 +153,6 @@ class PerformanceMode : Feature("Performance Mode") {
if (updated != original) {
param.setArg(0, updated)
}
transitionDurationLog("requested=$original applied=${param.arg<Long>(0)}")
}
Animation::class.java.hook("setDuration", HookStage.BEFORE) { param ->
@@ -182,28 +161,25 @@ class PerformanceMode : Feature("Performance Mode") {
if (updated != original) {
param.setArg(0, updated)
}
animationDurationLog("requested=$original applied=${param.arg<Long>(0)}")
}
RecyclerView::class.java.hookConstructor(HookStage.AFTER) { param ->
val recyclerView = param.thisObject<RecyclerView>()
recyclerView.setItemViewCacheSize(recyclerViewCacheSize)
recyclerView.overScrollMode = View.OVER_SCROLL_NEVER
recyclerView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
recyclerView.recycledViewPool.setMaxRecycledViews(0, 20)
if (isMaxProfile) {
recyclerView.itemAnimator = null
}
recyclerCtorLog("cache=$recyclerViewCacheSize max=$isMaxProfile class=${recyclerView::class.java.name}")
}
RecyclerView::class.java.hook("setAdapter", HookStage.AFTER) { param ->
val recyclerView = param.thisObject<RecyclerView>()
recyclerView.setItemViewCacheSize(recyclerViewCacheSize)
recyclerView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
recyclerView.recycledViewPool.setMaxRecycledViews(0, 20)
if (isMaxProfile) {
recyclerView.itemAnimator = null
}
recyclerAdapterLog("cache=$recyclerViewCacheSize adapter=${param.argNullable<Any>(0)?.javaClass?.name}")
}
RecyclerView::class.java.hook("setLayoutManager", HookStage.AFTER) { param ->
@@ -212,15 +188,13 @@ class PerformanceMode : Feature("Performance Mode") {
when (layoutManager) {
is LinearLayoutManager -> {
layoutManager.isItemPrefetchEnabled = true
layoutManager.initialPrefetchItemCount = prefetchItemCount
layoutManager.initialPrefetchItemCount = prefetchItemCount.coerceAtLeast(12)
}
is StaggeredGridLayoutManager -> {
layoutManager.isItemPrefetchEnabled = true
layoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
}
}
recyclerView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
recyclerLayoutManagerLog("layoutManager=${layoutManager?.javaClass?.name} prefetch=$prefetchItemCount")
}
fun SQLiteDatabase.applyPerformancePragmas() {
@@ -233,25 +207,21 @@ class PerformanceMode : Feature("Performance Mode") {
}
SQLiteDatabase::class.java.hook("openDatabase", HookStage.AFTER) { param ->
(param.getResult() as? SQLiteDatabase)?.also {
it.applyPerformancePragmas()
sqliteOpenLog("path=${param.argNullable<Any>(0)}")
}
(param.getResult() as? SQLiteDatabase)?.applyPerformancePragmas()
}
SQLiteDatabase::class.java.hook("openOrCreateDatabase", HookStage.AFTER) { param ->
(param.getResult() as? SQLiteDatabase)?.also {
it.applyPerformancePragmas()
sqliteCreateLog("path=${param.argNullable<Any>(0)}")
}
(param.getResult() as? SQLiteDatabase)?.applyPerformancePragmas()
}
MediaRecorder::class.java.hook("setVideoFrameRate", HookStage.BEFORE) { param ->
val currentRate = param.arg<Int>(0)
if (currentRate < minimumFrameRate) {
param.setArg(0, minimumFrameRate)
val applied = currentRate
.coerceAtLeast(if (isMaxProfile) 30 else 24)
.coerceAtMost(if (isMaxProfile) 60 else 45)
if (applied != currentRate) {
param.setArg(0, applied)
}
mediaRecorderLog("requested=$currentRate applied=${param.arg<Int>(0)}")
}
OverScroller::class.java.hook("startScroll", HookStage.BEFORE) { param ->
@@ -261,29 +231,84 @@ class PerformanceMode : Feature("Performance Mode") {
if (updated != original) {
param.setArg(4, updated)
}
overScrollerLog("requested=$original applied=${param.arg<Int>(4)}")
}
}
CaptureRequest.Builder::class.java.hook("set", HookStage.BEFORE) { param ->
val key = param.arg<CaptureRequest.Key<*>>(0)
when (key) {
CaptureRequest.EDGE_MODE -> param.setArg(1, CaptureRequest.EDGE_MODE_FAST)
CaptureRequest.NOISE_REDUCTION_MODE -> param.setArg(1, CaptureRequest.NOISE_REDUCTION_MODE_FAST)
CaptureRequest.HOT_PIXEL_MODE -> param.setArg(1, CaptureRequest.HOT_PIXEL_MODE_FAST)
CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE -> param.setArg(1, CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE_FAST)
CaptureRequest.CONTROL_AF_MODE -> param.setArg(1, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE -> param.setArg(1, CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF)
CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE -> {
val currentRange = param.argNullable<Any>(1) as? Range<*>
val lower = (currentRange?.lower as? Int) ?: minimumFrameRate
val upper = (currentRange?.upper as? Int) ?: minimumFrameRate
if (upper < minimumFrameRate) {
param.setArg(1, Range(lower.coerceAtMost(minimumFrameRate), minimumFrameRate))
}
OverScroller::class.java.hook("fling", HookStage.BEFORE) { param ->
if (param.args().size >= 10) {
val overX = param.arg<Int>(8)
val overY = param.arg<Int>(9)
if (overX != 0) param.setArg(8, 0)
if (overY != 0) param.setArg(9, 0)
}
}
runCatching {
val nativeMapViewClass = findClass("com.mapbox.mapboxsdk.maps.NativeMapView")
val transitionOptionsClass = findClass("com.mapbox.mapboxsdk.style.layers.TransitionOptions")
val transitionOptionsCtor = transitionOptionsClass.getDeclaredConstructor(Long::class.javaPrimitiveType, Long::class.javaPrimitiveType, Boolean::class.javaPrimitiveType).apply {
isAccessible = true
}
fun findNativeMapMethod(name: String, predicate: (Method) -> Boolean): Method? {
return nativeMapViewClass.findRestrictedMethod { method ->
method.name == name && predicate(method)
}?.apply {
isAccessible = true
}
}
captureRequestLog("key=${key.name} value=${param.argNullable<Any>(1)}")
val nativeCancelTransitions = findNativeMapMethod("nativeCancelTransitions") { it.parameterCount == 0 }
val nativeSetPrefetchTiles = findNativeMapMethod("nativeSetPrefetchTiles") { it.parameterCount == 1 && it.parameterTypes[0] == Boolean::class.javaPrimitiveType }
val nativeSetPrefetchZoomDelta = findNativeMapMethod("nativeSetPrefetchZoomDelta") { it.parameterCount == 1 && it.parameterTypes[0] == Int::class.javaPrimitiveType }
val nativeSetTransitionDelay = findNativeMapMethod("nativeSetTransitionDelay") { it.parameterCount == 1 && it.parameterTypes[0] == Long::class.javaPrimitiveType }
val nativeSetTransitionDuration = findNativeMapMethod("nativeSetTransitionDuration") { it.parameterCount == 1 && it.parameterTypes[0] == Long::class.javaPrimitiveType }
val nativeSetTransitionOptions = findNativeMapMethod("nativeSetTransitionOptions") { it.parameterCount == 1 && it.parameterTypes[0].name == transitionOptionsClass.name }
nativeMapViewClass.hookConstructor(HookStage.AFTER) { param ->
val nativeMapView = param.thisObject<Any>()
runCatching {
nativeSetPrefetchTiles?.invoke(nativeMapView, true)
nativeSetPrefetchZoomDelta?.invoke(nativeMapView, 6)
nativeSetTransitionDelay?.invoke(nativeMapView, 0L)
nativeSetTransitionDuration?.invoke(nativeMapView, 0L)
nativeSetTransitionOptions?.invoke(
nativeMapView,
transitionOptionsCtor.newInstance(0L, 0L, false)
)
nativeCancelTransitions?.invoke(nativeMapView)
}
}
nativeMapViewClass.findRestrictedMethod { method ->
method.name == "g" &&
method.parameterCount == 6 &&
method.parameterTypes.last() == Long::class.javaPrimitiveType
}?.hook(HookStage.BEFORE) { param ->
val original = param.arg<Long>(5)
val applied = original.coerceAtMost(16L)
if (applied != original) {
param.setArg(5, applied)
}
runCatching { nativeCancelTransitions?.invoke(param.thisObject<Any>()) }
}
nativeMapViewClass.findRestrictedMethod { method ->
method.name == "v" &&
method.parameterCount == 3 &&
method.parameterTypes[0] == Double::class.javaPrimitiveType &&
method.parameterTypes[1] == Double::class.javaPrimitiveType &&
method.parameterTypes[2] == Long::class.javaPrimitiveType
}?.hook(HookStage.BEFORE) { param ->
val original = param.arg<Long>(2)
val applied = original.coerceAtMost(8L)
if (applied != original) {
param.setArg(2, applied)
}
runCatching { nativeCancelTransitions?.invoke(param.thisObject<Any>()) }
}
}.onFailure {
context.log.error("Failed to install Snap Map transition hooks", it, "PerformanceMode")
}
fun applyActivityPerformanceTuning(activity: Activity) {
@@ -295,12 +320,10 @@ class PerformanceMode : Feature("Performance Mode") {
activity.window.attributes = activity.window.attributes.apply {
this.preferredRefreshRate = targetRefreshRate
}
refreshRateLog("activity=${activity::class.java.name} refreshRate=$targetRefreshRate")
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isMaxProfile) {
runCatching {
activity.window.setSustainedPerformanceMode(true)
sustainedModeLog("activity=${activity::class.java.name}")
}
}
}
@@ -308,5 +331,24 @@ class PerformanceMode : Feature("Performance Mode") {
onNextActivityCreate {
applyActivityPerformanceTuning(it)
}
Dialog::class.java.hook("show", HookStage.AFTER) { param ->
val dialog = param.nullableThisObject<Any>() as? Dialog ?: return@hook
val window = dialog.window ?: return@hook
runCatching {
window.setWindowAnimations(0)
window.decorView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
window.attributes = window.attributes.apply {
flags = flags or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
}
}
}
TextureView::class.java.hookConstructor(HookStage.AFTER) { param ->
val textureView = param.thisObject<TextureView>()
runCatching {
textureView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
}
}
}
}