6.7.0 - Alpha17 - 修复 Android 16+ 自定义返回逻辑失效导致返回功能异常的问题
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.autojs.autojs.core.ui.dialog;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
|
||||
public interface OnBackPressedFromJs {
|
||||
|
||||
boolean onBackPressed(MaterialDialog dialog);
|
||||
|
||||
}
|
||||
308
app/src/main/java/org/autojs/autojs/event/BackCompat.kt
Normal file
308
app/src/main/java/org/autojs/autojs/event/BackCompat.kt
Normal 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() } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user