6.7.0 - Alpha17 - 修复 Android 16+ 自定义返回逻辑失效导致返回功能异常的问题

This commit is contained in:
SuperMonster003
2026-01-22 00:20:42 +08:00
parent 082f5c0129
commit 0e2141a2ea
27 changed files with 839 additions and 340 deletions

View File

@@ -12,7 +12,6 @@ import android.provider.Settings
import android.text.util.Linkify
import android.util.Log
import android.view.Gravity
import android.view.KeyEvent
import android.view.View
import android.view.WindowManager
import android.widget.CheckBox
@@ -22,6 +21,7 @@ import com.afollestad.materialdialogs.MaterialDialog
import org.autojs.autojs.annotation.ReservedForCompatibility
import org.autojs.autojs.theme.preference.LongClickablePreferenceLike
import org.autojs.autojs.ui.explorer.ExplorerView
import org.autojs.autojs.event.BackCompat
import org.autojs.autojs.util.ViewUtils.showSnack
import org.autojs.autojs6.R
@@ -215,28 +215,40 @@ object DialogUtils {
dialog.getActionButton(actionButton)?.isEnabled = !dialog.items.isNullOrEmpty()
}
@JvmStatic
fun MaterialDialog.installBackHandler(onBack: (DialogInterface) -> Boolean): MaterialDialog =
BackCompat.installDialogBackHandler(this, onBack = onBack)
@JvmStatic
fun adaptToExplorer(dialog: MaterialDialog, explorerView: ExplorerView): MaterialDialog {
val time = object : Any() {
var lastPressed: Long = 0
val minPressInterval: Long = 1000
val time = object {
var lastPressed = 0L
val minPressInterval = 1000L
}
dialog.setOnKeyListener { dialogInterface: DialogInterface, keyCode: Int, event: KeyEvent ->
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
if (explorerView.canGoBack()) {
explorerView.goBack()
return@setOnKeyListener true
}
if (System.currentTimeMillis() - time.lastPressed < time.minPressInterval) {
dialogInterface.dismiss()
} else {
time.lastPressed = System.currentTimeMillis()
showSnack(explorerView, R.string.text_press_again_to_dismiss_dialog)
}
return BackCompat.installDialogBackHandler(
dialog = dialog,
// Overlay dialog priority is inferred automatically by context.
// zh-CN: Overlay/普通对话框优先级由 context 自动推断.
priority = BackCompat.inferDialogPriority(dialog),
// Always consumes the event, so the fallback will never be reached.
// zh-CN: 事件总是会消费, fallback 实际永不可达.
fallback = BackCompat.Fallback.NOOP,
legacyKeyListener = true,
) { di ->
if (explorerView.canGoBack()) {
explorerView.goBack()
return@installDialogBackHandler true
}
false
val now = System.currentTimeMillis()
if (now - time.lastPressed >= time.minPressInterval) {
time.lastPressed = now
showSnack(explorerView, R.string.text_press_again_to_dismiss_dialog)
return@installDialogBackHandler true
}
di.dismiss()
return@installDialogBackHandler true
}
return dialog
}
fun applyLongClickability(preference: LongClickablePreferenceLike, holder: PreferenceViewHolder) {

View File

@@ -58,6 +58,8 @@ public class JsDialog {
private final MaterialDialog mDialog;
private final JsDialogBuilder mBuilder;
private OnBackPressedFromJs mOnBackPressedFromJs;
public JsDialog(JsDialogBuilder builder, EventEmitter emitter, UiHandler uiHandler) {
mBuilder = builder;
mDialog = builder.build();
@@ -76,6 +78,7 @@ public class JsDialog {
return this;
}
@SuppressWarnings("deprecation")
private void checkWindowType() {
Context context = mDialog.getContext();
if (!DialogUtils.isActivityContext(context)) {
@@ -512,8 +515,15 @@ public class JsDialog {
return mDialog.onKeyMultiple(keyCode, repeatCount, event);
}
public void setOnBackPressedFromJs(OnBackPressedFromJs callback) {
mOnBackPressedFromJs = callback;
}
@SuppressWarnings("deprecation")
public void onBackPressed() {
mDialog.onBackPressed();
if (!mOnBackPressedFromJs.onBackPressed(mDialog)) {
mDialog.onBackPressed();
}
}
public boolean onKeyShortcut(int keyCode, @NonNull KeyEvent event) {

View File

@@ -0,0 +1,9 @@
package org.autojs.autojs.core.ui.dialog;
import com.afollestad.materialdialogs.MaterialDialog;
public interface OnBackPressedFromJs {
boolean onBackPressed(MaterialDialog dialog);
}

View File

@@ -0,0 +1,308 @@
package org.autojs.autojs.event
import android.app.Activity
import android.app.Dialog
import android.content.Context
import android.content.ContextWrapper
import android.content.DialogInterface
import android.os.Build
import android.view.KeyEvent
import android.view.View
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
import androidx.annotation.RequiresApi
import androidx.lifecycle.LifecycleOwner
import org.autojs.autojs6.R
/**
* Provides a unified back handling layer for:
* - Activity/Fragment: OnBackPressedDispatcher (AndroidX)
* - Dialog: OnBackInvokedDispatcher (API 33+) + KEYCODE_BACK fallback
* - Overlay View: View#findOnBackInvokedDispatcher (API 33+) + dispatchKeyEvent fallback
*
* zh-CN:
*
* 提供统一的返回键/返回手势处理层, 覆盖:
* - Activity/Fragment: AndroidX OnBackPressedDispatcher
* - Dialog: API 33+ OnBackInvokedDispatcher + KEYCODE_BACK fallback
* - Overlay View: API 33+ View.findOnBackInvokedDispatcher + dispatchKeyEvent fallback
*
* Created by JetBrains AI Assistant (GPT-5.2) on Jan 21, 2026.
* Modified by SuperMonster003 as of Apr 2, 2024.
*/
object BackCompat {
enum class Priority { DEFAULT, OVERLAY }
/**
* Fallback policy when back is NOT consumed in API 33+ callback path.
*
* Note:
* - Platform OnBackInvokedCallback has no return value, so we cannot "let system handle it".
* We provide a best-effort fallback strategy.
*
* zh-CN:
*
* 当 API 33+ 回调路径中返回键未被消费时的备用策略.
*
* 注:
* - OnBackInvokedCallback 没有返回值, 无法像 KeyListener 那样把 back 继续交给系统.
* 因此提供一个尽力而为的 fallback 策略.
*/
enum class Fallback {
NOOP,
DISMISS_IF_CANCELABLE,
ALWAYS_DISMISS,
}
/** Disposable handle. zh-CN: 可释放句柄. */
fun interface Handle { fun dispose() }
private val NOOP_HANDLE = Handle { /* No-op. */ }
// Use View tag to prevent duplicate installation on the same decorView/rootView.
// zh-CN: 用 View tag 防止重复安装.
val TAG_KEY_INSTALLED: Int = R.id.tag_backcompat_dialog_back_callback
/**
* Install back handler for Activity/Fragment scope via AndroidX dispatcher.
*
* zh-CN: 通过 AndroidX dispatcher 为 Activity/Fragment 安装 back handler.
*/
@JvmStatic
@JvmOverloads
fun install(
activity: ComponentActivity,
owner: LifecycleOwner = activity,
enabled: Boolean = true,
onBack: () -> Unit,
): Handle {
val cb = object : OnBackPressedCallback(enabled) {
override fun handleOnBackPressed() = onBack()
}
activity.onBackPressedDispatcher.addCallback(owner, cb)
return Handle { cb.remove() }
}
/**
* Install a back handler that works on both legacy key events and Android 13+ predictive back.
*
* Notes:
* - Legacy path: Dialog#setOnKeyListener for KEYCODE_BACK.
* - Android 13+: OnBackInvokedDispatcher is the actual entry point when targetSdk >= 33.
* - Cleanup: unregister callback on decorView detach to avoid leaks.
*
* zh-CN:
*
* 安装适用于旧版按键事件和 Android 13+ 预测返回的 back handler.
*
* 注:
* - 旧链路: Dialog#setOnKeyListener 处理 KEYCODE_BACK.
* - Android 13+: targetSdk >= 33 时, OnBackInvokedDispatcher 才是实际入口.
* - 清理: decorView detach 时自动反注册, 避免泄漏.
*/
@JvmStatic
@JvmOverloads
fun <T : Dialog> installDialogBackHandler(
dialog: T,
priority: Priority = inferDialogPriority(dialog),
fallback: Fallback = Fallback.DISMISS_IF_CANCELABLE,
legacyKeyListener: Boolean = true,
onBack: (DialogInterface) -> Boolean,
): T = dialog.also { d ->
// 1) Legacy fallback: key event path.
// zh-CN: 旧系统 fallback: 按键事件链路.
if (legacyKeyListener) {
d.setOnKeyListener { di, keyCode, event ->
keyCode == KeyEvent.KEYCODE_BACK &&
event.action == KeyEvent.ACTION_UP &&
onBack(di)
}
}
// 2) Android 13+: predictive back path.
// zh-CN: Android 13+: 预测返回链路.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Api33.installDialogBackHandler(d, priority, fallback, onBack)
}
}
@JvmStatic
fun inferDialogPriority(dialog: Dialog): Priority =
when {
isActivityContext(dialog.context) -> {
Priority.DEFAULT
}
else -> Priority.OVERLAY
}
/**
* Install back handler for an attached View via View#findOnBackInvokedDispatcher() (API 33+).
*
* Notes:
* - Works best for overlay / WindowManager-added views that want to respond to system back.
* - Automatically registers on attach, unregisters on detach.
* - Does NOT require you to override dispatchKeyEvent (but you may keep it as legacy fallback).
*
* zh-CN:
*
* 通过 View#findOnBackInvokedDispatcher() 为已附加的 View 安装返回处理器 (API 33+).
*
* 注:
* - 适用于 overlay / WindowManager.addView 的根视图.
* - attach 时注册, detach 时反注册.
* - 不强依赖 dispatchKeyEvent (你可以保留作为旧系统 fallback).
*/
@JvmStatic
@JvmOverloads
fun installViewBackHandler(
view: View,
priority: Priority = Priority.OVERLAY,
onBack: () -> Unit,
): Handle {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return NOOP_HANDLE
return Api33.installViewBackHandler(view, priority, onBack)
}
private fun isActivityContext(context: Context?): Boolean =
when (context) {
null -> false
is Activity -> true
is ContextWrapper -> isActivityContext(context.baseContext)
else -> false
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private object Api33 {
private fun toPlatformPriority(p: Priority): Int = when (p) {
Priority.DEFAULT -> OnBackInvokedDispatcher.PRIORITY_DEFAULT
Priority.OVERLAY -> OnBackInvokedDispatcher.PRIORITY_OVERLAY
}
fun installDialogBackHandler(
dialog: Dialog,
priority: Priority,
fallback: Fallback,
onBack: (DialogInterface) -> Boolean,
) {
// Ensure window/decorView exists (best effort).
// zh-CN: 尽力确保 window/decorView 可用.
runCatching { if (dialog.window == null) dialog.create() }
val decor = dialog.window?.decorView ?: run {
// Worst-case: no decorView, register directly (no auto cleanup).
// zh-CN: 极端情况: 没拿到 decorView, 直接注册 (无法自动清理).
val dispatcher = dialog.onBackInvokedDispatcher
val cb = OnBackInvokedCallback {
val consumed = onBack(dialog)
if (!consumed) applyFallback(dialog, fallback)
}
dispatcher.registerOnBackInvokedCallback(toPlatformPriority(priority), cb)
return
}
// Prevent duplicate installation on same decor view.
// zh-CN: 防重复安装.
if (decor.getTag(TAG_KEY_INSTALLED) == true) return
decor.setTag(TAG_KEY_INSTALLED, true)
var dispatcher: OnBackInvokedDispatcher? = null
var callback: OnBackInvokedCallback? = null
fun unregister() {
val d = dispatcher
val c = callback
if (d != null && c != null) runCatching { d.unregisterOnBackInvokedCallback(c) }
dispatcher = null
callback = null
}
// Register on attach, unregister on detach.
// zh-CN: attach 注册, detach 反注册.
val listener = object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
unregister()
val d = dialog.onBackInvokedDispatcher
val c = OnBackInvokedCallback {
val consumed = onBack(dialog)
if (!consumed) applyFallback(dialog, fallback)
}
d.registerOnBackInvokedCallback(toPlatformPriority(priority), c)
dispatcher = d
callback = c
}
override fun onViewDetachedFromWindow(v: View) {
unregister()
// Keep listener for potential re-show; decorView may re-attach.
// zh-CN: 保留 listener, 以支持可能的重复 show().
}
}
decor.addOnAttachStateChangeListener(listener)
if (decor.isAttachedToWindow) listener.onViewAttachedToWindow(decor)
}
fun installViewBackHandler(
view: View,
priority: Priority,
onBack: () -> Unit,
): Handle {
// Prevent duplicate installation on same view.
// zh-CN: 防重复安装.
if (view.getTag(TAG_KEY_INSTALLED) == true) return NOOP_HANDLE
view.setTag(TAG_KEY_INSTALLED, true)
var dispatcher: OnBackInvokedDispatcher? = null
var callback: OnBackInvokedCallback? = null
fun unregister() {
val d = dispatcher
val c = callback
if (d != null && c != null) runCatching { d.unregisterOnBackInvokedCallback(c) }
dispatcher = null
callback = null
}
val listener = object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
unregister()
val d = v.findOnBackInvokedDispatcher() ?: return
val c = OnBackInvokedCallback { onBack() }
d.registerOnBackInvokedCallback(toPlatformPriority(priority), c)
dispatcher = d
callback = c
}
override fun onViewDetachedFromWindow(v: View) {
unregister()
}
}
view.addOnAttachStateChangeListener(listener)
if (view.isAttachedToWindow) listener.onViewAttachedToWindow(view)
return Handle {
view.removeOnAttachStateChangeListener(listener)
unregister()
view.setTag(TAG_KEY_INSTALLED, null)
}
}
private fun applyFallback(dialog: Dialog, fallback: Fallback) {
when (fallback) {
Fallback.NOOP -> Unit
Fallback.ALWAYS_DISMISS -> runCatching { dialog.dismiss() }
Fallback.DISMISS_IF_CANCELABLE -> {
if (dialog.isShowing) {
runCatching { dialog.cancel() }.onFailure { runCatching { dialog.dismiss() } }
}
}
}
}
}
}

View File

@@ -16,6 +16,7 @@ import android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
import android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
import android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE
import android.view.WindowManager
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.drawable.toDrawable
import androidx.core.view.ViewCompat
@@ -42,10 +43,32 @@ import org.mozilla.javascript.ContinuationPending
/**
* Created by Stardust on Feb 5, 2017.
* Modified by SuperMonster003 as of Nov 15, 2023.
* Modified by SuperMonster003 as of Jan 20, 2026.
*/
class ScriptExecuteActivity : AppCompatActivity(), OnActivityResultDelegate.DelegateHost {
private val mOnBackPressedCallback = object : OnBackPressedCallback(true) {
// override fun onBackPressed() {
// val event = SimpleEvent()
// emit("back_pressed", event)
// if (!event.consumed) {
// @Suppress("DEPRECATION", "KotlinRedundantDiagnosticSuppress")
// super.onBackPressed()
// }
// }
override fun handleOnBackPressed() {
val event = SimpleEvent()
emit("back_pressed", event)
if (event.consumed) return
isEnabled = false
onBackPressedDispatcher.onBackPressed()
isEnabled = true
}
}
private var mRuntime: ScriptRuntime? = null
private var mExecutionListener: ScriptExecutionListener? = null
private var mScriptSource: ScriptSource? = null
@@ -100,6 +123,8 @@ class ScriptExecuteActivity : AppCompatActivity(), OnActivityResultDelegate.Dele
ViewUtils.setNavigationBarBackgroundColor(this, getColor(R.color.black_alpha_44))
}
onBackPressedDispatcher.addCallback(this, mOnBackPressedCallback)
val executionId = intent.getIntExtra(EXTRA_EXECUTION_ID, ScriptExecution.NO_ID)
if (executionId == ScriptExecution.NO_ID) {
super.finish()
@@ -196,16 +221,6 @@ class ScriptExecuteActivity : AppCompatActivity(), OnActivityResultDelegate.Dele
emit("save_instance_state", outState)
}
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
val event = SimpleEvent()
emit("back_pressed", event)
if (!event.consumed) {
@Suppress("DEPRECATION", "KotlinRedundantDiagnosticSuppress")
super.onBackPressed()
}
}
override fun onPause() {
emit("pause")
super.onPause()

View File

@@ -1,14 +1,18 @@
package org.autojs.autojs.runtime.api.augment.dialogs
import android.os.Build
import android.text.util.Linkify
import android.view.KeyEvent
import android.view.View
import android.view.WindowManager.LayoutParams
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import com.afollestad.materialdialogs.Theme
import org.autojs.autojs.annotation.RhinoRuntimeFunctionInterface
import org.autojs.autojs.core.ui.dialog.JsDialog
import org.autojs.autojs.core.ui.dialog.JsDialogBuilder
import org.autojs.autojs.core.ui.nativeview.NativeView
import org.autojs.autojs.event.BackCompat.TAG_KEY_INSTALLED
import org.autojs.autojs.extension.AnyExtensions.isJsNullish
import org.autojs.autojs.extension.AnyExtensions.isJsString
import org.autojs.autojs.extension.AnyExtensions.isJsXml
@@ -619,15 +623,76 @@ class Dialogs(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
}
}
properties.prop("onBackKey")?.let { onBackKey ->
properties.inquire(listOf("onBackKey", "onBackPressed"))?.let { onBackKey ->
val isFunction = onBackKey is BaseFunction
val isDisabled = onBackKey == false || (onBackKey is String && onBackKey.matches(Regex("^disabled?$", RegexOption.IGNORE_CASE)))
if (isDisabled || isFunction) {
dialog.setOnKeyListener { _, keyCode, event ->
when {
event.action != KeyEvent.ACTION_UP || keyCode != KeyEvent.KEYCODE_BACK -> false
else -> true.also { if (onBackKey is BaseFunction) callFunction(scriptRuntime, onBackKey, arrayOf(dialog)) }
dialog.setOnBackPressedFromJs {
if (onBackKey is BaseFunction) {
val result = callFunction(scriptRuntime, onBackKey, arrayOf(dialog))
return@setOnBackPressedFromJs coerceBoolean(result, false)
}
return@setOnBackPressedFromJs false
}
dialog.setOnKeyListener { _, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_BACK) {
dialog.onBackPressed()
return@setOnKeyListener true
}
return@setOnKeyListener false
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
runCatching { if (dialog.window == null) dialog.create() }
val decor = dialog.window?.decorView ?: run {
val dispatcher = dialog.window?.onBackInvokedDispatcher
dispatcher?.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_OVERLAY) {
runCatching {
dialog.onBackPressed()
}.onFailure {
runCatching { dialog.cancel() }.onFailure { runCatching { dialog.dismiss() } }
}
}
return
}
if (decor.getTag(TAG_KEY_INSTALLED) == true) return
decor.setTag(TAG_KEY_INSTALLED, true)
var dispatcher: OnBackInvokedDispatcher? = null
var callback: OnBackInvokedCallback? = null
fun unregister() {
val d = dispatcher
val c = callback
if (d != null && c != null) runCatching { d.unregisterOnBackInvokedCallback(c) }
dispatcher = null
callback = null
}
val listener = object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
unregister()
val d = dialog.window?.onBackInvokedDispatcher
val c = OnBackInvokedCallback {
runCatching {
dialog.onBackPressed()
}.onFailure {
runCatching { dialog.cancel() }.onFailure { runCatching { dialog.dismiss() } }
}
}
d?.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_OVERLAY, c)
dispatcher = d
callback = c
}
override fun onViewDetachedFromWindow(v: View) {
unregister()
}
}
decor.addOnAttachStateChangeListener(listener)
if (decor.isAttachedToWindow) listener.onViewAttachedToWindow(decor)
}
}
}

View File

@@ -1,8 +1,8 @@
package org.autojs.autojs.ui.doc
import android.annotation.SuppressLint
import android.os.Bundle
import android.webkit.WebView
import androidx.activity.OnBackPressedCallback
import org.autojs.autojs.ui.BaseActivity
import org.autojs.autojs.util.DocsUtils.getUrl
import org.autojs.autojs.util.ViewUtils
@@ -17,12 +17,32 @@ import org.intellij.lang.annotations.Language
*/
class DocumentationActivity : BaseActivity() {
private val mOnBackPressedCallback = object : OnBackPressedCallback(true) {
// override fun onBackPressed() {
// if (mWebView.canGoBack()) {
// mWebView.goBack()
// } else {
// onBackPressedDispatcher.onBackPressed()
// }
// }
override fun handleOnBackPressed() {
if (mWebView.canGoBack()) {
mWebView.goBack()
} else {
onBackPressedDispatcher.onBackPressed()
}
}
}
override val handleStatusBarThemeColorAutomatically = false
private lateinit var mWebView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ActivityDocumentationBinding.inflate(layoutInflater).also { binding ->
setContentView(binding.root)
binding.ewebView.also { ewebView ->
@@ -39,6 +59,8 @@ class DocumentationActivity : BaseActivity() {
}
}
}
onBackPressedDispatcher.addCallback(this, mOnBackPressedCallback)
}
override fun onStart() {
@@ -46,16 +68,6 @@ class DocumentationActivity : BaseActivity() {
setUpStatusBarIconLightByNightMode()
}
@SuppressLint("MissingSuperCall")
@Suppress("OVERRIDE_DEPRECATION")
override fun onBackPressed() {
if (mWebView.canGoBack()) {
mWebView.goBack()
} else {
onBackPressedDispatcher.onBackPressed()
}
}
companion object {
const val EXTRA_URL = "url"

View File

@@ -16,6 +16,7 @@ import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.core.view.get
import androidx.core.view.size
import com.afollestad.materialdialogs.MaterialDialog
@@ -50,6 +51,24 @@ import java.io.IOException
*/
open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyActivity {
private val mOnBackPressedCallback = object : OnBackPressedCallback(true) {
// override fun onBackPressed() {
// if (!mEditorView.onBackPressed()) {
// super.onBackPressed()
// }
// }
override fun handleOnBackPressed() {
if (mEditorView.onBackPressed()) {
return
}
isEnabled = false
onBackPressedDispatcher.onBackPressed()
isEnabled = true
}
}
override val handleContentViewFromHorizontalNavigationBarAutomatically = false
private var mToolbar: ThemeColorToolbar? = null
@@ -63,6 +82,7 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc
@SuppressLint("CheckResult")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityEditBinding.inflate(layoutInflater).also { setContentView(it.root) }
@@ -82,6 +102,8 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc
mEditorMenu = EditorMenu(mEditorView)
mNewTask = intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK != 0
setUpToolbar()
onBackPressedDispatcher.addCallback(this, mOnBackPressedCallback)
}
private fun onLoadFileError(message: String?) {
@@ -249,13 +271,6 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc
}
}
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
override fun onBackPressed() {
if (!mEditorView.onBackPressed()) {
super.onBackPressed()
}
}
override fun finish() {
if (mEditorView.isTextChanged) {
showExitConfirmDialog()

View File

@@ -3,6 +3,7 @@ package org.autojs.autojs.ui.error
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import androidx.activity.OnBackPressedCallback
import org.autojs.autojs.runtime.api.augment.util.VersionCodesInfo.briefOfCurrentVersionInt
import org.autojs.autojs.ui.BaseActivity
import org.autojs.autojs.ui.main.MainActivity
@@ -15,10 +16,19 @@ import org.autojs.autojs6.databinding.ActivityErrorReportBinding
/**
* Created by Stardust on Feb 2, 2017.
* Transformed by SuperMonster003 on Mar 10, 2025.
* Transformed by SuperMonster003 on Jan 20, 2026.
*/
class CrashReportActivity : BaseActivity() {
private val mOnBackPressedCallback = object : OnBackPressedCallback(true) {
// override fun onBackPressed() = exit()
override fun handleOnBackPressed() {
exit()
}
}
private lateinit var crashMessage: String
@SuppressLint("ClickableViewAccessibility")
@@ -56,13 +66,9 @@ class CrashReportActivity : BaseActivity() {
binding.restart.setOnClickListener { restart() }
binding.exit.setOnClickListener { exit() }
// savedInstanceState ?: copy()
onBackPressedDispatcher.addCallback(this, mOnBackPressedCallback)
}
@Deprecated("Deprecated in Java")
@SuppressLint("MissingSuperCall")
override fun onBackPressed() = exit()
private fun copy() = ClipboardUtils.setClip(this, crashMessage)
private fun exit() = finishAffinity()

View File

@@ -7,6 +7,7 @@ import org.autojs.autojs.core.accessibility.Capture
import org.autojs.autojs.core.accessibility.NodeInfo
import org.autojs.autojs.ui.enhancedfloaty.FloatyService
import org.autojs.autojs.ui.floating.LayoutFloatyWindow
import org.autojs.autojs.event.BackCompat
import org.autojs.autojs.util.EventUtils
import org.autojs.autojs6.R
@@ -38,12 +39,16 @@ open class LayoutBoundsFloatyWindow @JvmOverloads constructor(
onCreate(floatyService)
return object : LayoutBoundsView(context) {
@Suppress("DEPRECATION")
override fun dispatchKeyEvent(e: KeyEvent) = when {
EventUtils.isKeyBackAndActionUp(e) -> true.also { close() }
EventUtils.isKeyVolumeDownAndActionDown(e) -> true.also { close() }
else -> super.dispatchKeyEvent(e)
}
}.also { mLayoutBoundsView = it }
}.also { view ->
mLayoutBoundsView = view
BackCompat.installViewBackHandler(view, BackCompat.Priority.OVERLAY) { close() }
}
}
override fun onViewCreated(v: View) {
@@ -57,10 +62,8 @@ open class LayoutBoundsFloatyWindow @JvmOverloads constructor(
val x = bounds.centerX() - width / 2
val y = bounds.bottom - view.statusBarHeight
if (width <= 0) {
try {
runCatching {
menu.preMeasure()
} catch (e: Exception) {
/* Ignored. */
}
}
menu.showAsDropDownAtLocation(view, bounds.height(), x, y)

View File

@@ -7,6 +7,7 @@ import org.autojs.autojs.core.accessibility.Capture
import org.autojs.autojs.ui.enhancedfloaty.FloatyService
import org.autojs.autojs.core.accessibility.NodeInfo
import org.autojs.autojs.ui.floating.LayoutFloatyWindow
import org.autojs.autojs.event.BackCompat
import org.autojs.autojs.util.EventUtils
import org.autojs.autojs6.R
@@ -35,12 +36,16 @@ open class LayoutHierarchyFloatyWindow @JvmOverloads constructor(
onCreate(floatyService)
return object : LayoutHierarchyView(context) {
@Suppress("DEPRECATION")
override fun dispatchKeyEvent(e: KeyEvent) = when {
EventUtils.isKeyBackAndActionUp(e) -> true.also { close() }
EventUtils.isKeyVolumeDownAndActionDown(e) -> true.also { close() }
else -> super.dispatchKeyEvent(e)
}
}.also { mLayoutHierarchyView = it }
}.also { view ->
mLayoutHierarchyView = view
BackCompat.installViewBackHandler(view, BackCompat.Priority.OVERLAY) { close() }
}
}
override fun onViewCreated(v: View) {

View File

@@ -12,6 +12,7 @@ import android.view.Gravity
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.widget.SearchView
import androidx.core.view.forEach
@@ -68,13 +69,49 @@ import org.autojs.autojs6.databinding.ActivityMainBinding
import org.greenrobot.eventbus.EventBus
/**
* Modified by SuperMonster003 as of Dec 1, 2021.
* Transformed by SuperMonster003 on May 11, 2023.
* Modified by SuperMonster003 as of Jan 20, 2026.
*/
class MainActivity : BaseActivity(), DelegateHost, HostActivity {
override val handleStatusBarThemeColorAutomatically = false
private val mBackPressedCallback = object : OnBackPressedCallback(true) {
// override fun onBackPressed() {
// val fragment = mPagerAdapter.getStoredFragment(mViewPager.currentItem)
// if ((fragment as? BackPressedHandler)?.onBackPressed(this) == true) {
// return
// }
// if (!mBackPressObserver.onBackPressed(this)) {
// @Suppress("DEPRECATION")
// super.onBackPressed()
// }
// }
override fun handleOnBackPressed() {
val fragment = mPagerAdapter.getStoredFragment(mViewPager.currentItem)
// 1. First, let the current page Fragment handle it.
// zh-CN: 先给当前页 Fragment 处理.
if ((fragment as? BackPressedHandler)?.onBackPressed(this@MainActivity) == true) {
return
}
// 2. Then, let the global Observer handle it (DrawerAutoClose / DoublePressExit).
// zh-CN: 再给全局 Observer 处理 (DrawerAutoClose / DoublePressExit).
if (mBackPressObserver.onBackPressed(this@MainActivity)) {
return
}
// 3. Return to the system default back behavior (finish / popBackStack, etc.).
// zh-CN: 交还系统默认返回 (finish / popBackStack 等).
isEnabled = false
onBackPressedDispatcher.onBackPressed()
isEnabled = true
}
}
private lateinit var mViewPager: ViewPager
private lateinit var mFab: ThemeColorFloatingActionButton
private lateinit var mTab: TabLayout
@@ -132,6 +169,8 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity {
setUpToolbar(drawerLayout)
setUpTabViewPager(it)
registerBackPressHandlers(drawerLayout)
onBackPressedDispatcher.addCallback(this, mBackPressedCallback)
}
Pref.registerOnSharedPreferenceChangeListener { _, key ->
@@ -339,18 +378,6 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity {
override fun getOnActivityResultDelegateMediator() = mActivityResultMediator
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
val fragment = mPagerAdapter.getStoredFragment(mViewPager.currentItem)
if ((fragment as? BackPressedHandler)?.onBackPressed(this) == true) {
return
}
if (!mBackPressObserver.onBackPressed(this)) {
@Suppress("DEPRECATION")
super.onBackPressed()
}
}
override fun getBackPressedObserver() = mBackPressObserver
override fun onCreateOptionsMenu(menu: Menu): Boolean {

View File

@@ -4,6 +4,10 @@ import android.view.KeyEvent
object EventUtils {
@Deprecated(
message = "KEYCODE_BACK is not a reliable system-back entry on Android 13+ when targetSdk >= 33. Use BackCompat instead.",
replaceWith = ReplaceWith("BackCompat.installViewBackHandler(view)")
)
fun isKeyBackAndActionUp(e: KeyEvent) = e.keyCode == KeyEvent.KEYCODE_BACK && e.action == KeyEvent.ACTION_UP
fun isKeyVolumeDownAndActionDown(e: KeyEvent) = e.keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && e.action == KeyEvent.ACTION_DOWN

View File

@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="rename" type="id"/>
<item name="open_by_other_apps" type="id"/>
<item name="create_shortcut" type="id"/>
<item name="delete" type="id"/>
<item name="package_name" type="id"/>
<item name="view_tag_view_extras" type="id"/>
<item type="id" name="rename" />
<item type="id" name="open_by_other_apps" />
<item type="id" name="create_shortcut" />
<item type="id" name="delete" />
<item type="id" name="package_name" />
<item type="id" name="view_tag_view_extras" />
<item type="id" name="tag_backcompat_dialog_back_callback" />
</resources>