6.3.4 Alpha2 - 新增 Paddle Lite / WebSocket; 修复屏幕旋转浮动按钮异常; 优化日志复制与导出

This commit is contained in:
SuperMonster003
2023-10-03 18:08:36 +08:00
parent 4674f94520
commit 4481f539f9
164 changed files with 3149 additions and 1428 deletions

View File

@@ -50,7 +50,6 @@ abstract class AbstractAutoJs protected constructor(protected val application: A
private set
val context: Context = application.applicationContext
val appUtils by lazy { createAppUtils(context) }
val globalConsole by lazy { createGlobalConsole() }
@@ -108,7 +107,7 @@ abstract class AbstractAutoJs protected constructor(protected val application: A
application.registerActivityLifecycleCallbacks(object : SimpleActivityLifecycleCallbacks() {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
ScreenMetrics.initIfNeeded(activity)
ScreenMetrics.init(activity)
appUtils.setCurrentActivity(activity)
registerOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
if (key == StringUtils.key(R.string.key_keep_screen_on_when_in_foreground)) {

View File

@@ -1,12 +1,15 @@
package org.autojs.autojs
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.util.Log
import android.view.View
import android.widget.ImageView
import androidx.localbroadcastmanager.content.LocalBroadcastManager
@@ -31,10 +34,13 @@ import org.autojs.autojs.timing.TimedTaskManager
import org.autojs.autojs.timing.TimedTaskScheduler
import org.autojs.autojs.tool.CrashHandler
import org.autojs.autojs.ui.error.ErrorReportActivity
import org.autojs.autojs.ui.floating.FloatyWindowManger
import org.autojs.autojs.util.ViewUtils
import org.autojs.autojs6.BuildConfig
import org.autojs.autojs6.R
import org.lsposed.hiddenapibypass.HiddenApiBypass
import java.lang.ref.WeakReference
import java.lang.reflect.Method
/**
@@ -61,7 +67,7 @@ class App : MultiDexApplication() {
setUpDebugEnvironment()
AutoJs.initInstance(this)
GlobalKeyObserver.init()
GlobalKeyObserver.initIfNeeded()
setupDrawableImageLoader()
TimedTaskScheduler.init(this)
initDynamicBroadcastReceivers()
@@ -73,7 +79,21 @@ class App : MultiDexApplication() {
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(localeAppDelegate.attachBaseContext(base))
// @Caution by SuperMonster003 on Aug 2, 2023.
// ! This will cause AutoJs6 not being aware of
// ! configuration changes (like orientation and so forth).
// super.attachBaseContext(localeAppDelegate.attachBaseContext(base))
super.attachBaseContext(base)
// @Dubious by SuperMonster003 on Aug 2, 2023.
// ! Locale helper may be not work as expected ?
localeAppDelegate.attachBaseContext(base)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
HiddenApiBypass.addHiddenApiExemptions("L");
}
Log.d("Shizuku", "${App::class.java.getSimpleName()} attachBaseContext | Process=${getProcessNameCompat()}")
}
private fun setUpStaticsTool() {
@@ -216,10 +236,11 @@ class App : MultiDexApplication() {
override fun onConfigurationChanged(newConfig: Configuration) {
localeAppDelegate.onConfigurationChanged(this)
ViewUtils.onConfigurationChanged(newConfig)
FloatyWindowManger.getCircularMenu()?.savePosition(newConfig)
super.onConfigurationChanged(newConfig)
}
override fun getApplicationContext() = LocaleHelper.onAttach(super.getApplicationContext())
override fun getApplicationContext() = LocaleHelper.onAttach(super.getApplicationContext())
companion object {
@@ -230,6 +251,19 @@ class App : MultiDexApplication() {
val app: App
get() = instance.get()!!
fun getProcessNameCompat(): String {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) Application.getProcessName() else {
try {
@SuppressLint("PrivateApi") val activityThread = Class.forName("android.app.ActivityThread")
@SuppressLint("DiscouragedPrivateApi") val method: Method = activityThread.getDeclaredMethod("currentProcessName")
method.invoke(null) as String
} catch (e: ClassNotFoundException) {
e.printStackTrace()
"Unknown process name"
}
}
}
}
}

View File

@@ -57,12 +57,12 @@ class AutoJs private constructor(private val appContext: Application) : Abstract
when {
action.equals(LayoutBoundsFloatyWindow::class.java.name, true) -> {
capture(object : LayoutInspectFloatyWindow {
override fun create(nodeInfo: NodeInfo?) = LayoutBoundsFloatyWindow(nodeInfo, context)
override fun create(nodeInfo: NodeInfo?) = LayoutBoundsFloatyWindow(nodeInfo, context, true)
})
}
action.equals(LayoutHierarchyFloatyWindow::class.java.name, true) -> {
capture(object : LayoutInspectFloatyWindow {
override fun create(nodeInfo: NodeInfo?) = LayoutHierarchyFloatyWindow(nodeInfo, context)
override fun create(nodeInfo: NodeInfo?) = LayoutHierarchyFloatyWindow(nodeInfo, context, true)
})
}
}
@@ -167,9 +167,11 @@ class AutoJs private constructor(private val appContext: Application) : Abstract
/* Broadcasts. */
putProperty(BroadcastShortForm.INSPECT_LAYOUT_BOUNDS.fullName, LayoutBoundsFloatyWindow::class.java.name)
putProperty(BroadcastShortForm.LAYOUT_BOUNDS.fullName, LayoutBoundsFloatyWindow::class.java.name)
putProperty(BroadcastShortForm.BOUNDS.fullName, LayoutBoundsFloatyWindow::class.java.name)
putProperty(BroadcastShortForm.INSPECT_LAYOUT_HIERARCHY.fullName, LayoutHierarchyFloatyWindow::class.java.name)
putProperty(BroadcastShortForm.LAYOUT_HIERARCHY.fullName, LayoutHierarchyFloatyWindow::class.java.name)
putProperty(BroadcastShortForm.HIERARCHY.fullName, LayoutHierarchyFloatyWindow::class.java.name)
}
@@ -181,6 +183,7 @@ class AutoJs private constructor(private val appContext: Application) : Abstract
private set
@Synchronized
@JvmStatic
fun initInstance(application: Application) {
if (!isInitialized) {
instance = AutoJs(application)

View File

@@ -18,7 +18,6 @@ import pxb.android.tinysign.TinySign;
/**
* Created by Stardust on 2017/10/23.
*/
public class ApkPackager {
private InputStream mApkInputStream;

View File

@@ -3,7 +3,6 @@ package org.autojs.autojs.apkbuilder.util;
/**
* Created by Stardust on 2017/10/23.
*/
public interface BoolFunction<T> {
boolean accept(T t);

View File

@@ -1,6 +1,5 @@
package org.autojs.autojs.apkbuilder.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -9,10 +8,8 @@ import java.io.OutputStream;
/**
* Created by Stardust on 2017/10/23.
*/
public class StreamUtils {
public static void write(InputStream inputStream, OutputStream out) throws IOException {
byte[] buffer = new byte[4096];
int len;
@@ -30,4 +27,5 @@ public class StreamUtils {
}
return outputStream.toByteArray();
}
}

View File

@@ -102,14 +102,14 @@ object DialogUtils {
MaterialDialog.Builder(context)
.title(context.getString(R.string.dialog_button_more))
.content(contentMore)
.positiveText(R.string.dialog_button_back)
.positiveText(R.string.dialog_button_dismiss)
.build()
.also { preference.longClickPromptMoreDialogHandler(it) }
.show()
}
}
}
.positiveText(R.string.dialog_button_back)
.positiveText(R.string.dialog_button_dismiss)
.onPositive { dialog, _ -> dialog.dismiss() }
.autoDismiss(false)
.build()

View File

@@ -55,7 +55,7 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) {
val host = Pref.getServerAddress()
if (isAutoConnect) {
devPlugin
.connectToRemoteServer(host, mClientModeItem, true)
.connectToRemoteServer(context, host, mClientModeItem, true)
.subscribe(Observers.emptyConsumer(), Observers.emptyConsumer())
return
}
@@ -96,14 +96,14 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) {
.show()
}
}
.negativeText(R.string.dialog_button_back)
.negativeText(R.string.dialog_button_cancel)
.negativeColorRes(R.color.dialog_button_default)
.onNegative { dHistories, _ -> dHistories.dismiss() }
.autoDismiss(false)
.show()
.also { DialogUtils.toggleContentViewByItems(it) }
}
.negativeText(R.string.text_back)
.negativeText(R.string.text_cancel)
.onNegative { dialog, _ -> dialog.dismiss() }
.autoDismiss(false)
.dismissListener(onConnectionDialogDismissed)
@@ -120,15 +120,15 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) {
dialog.inputEditText!!.filters += InputFilter { source, start, end, dest, dstart, dend ->
if (end > start) {
val fullText = dest.substring(0, dstart) +
source.subSequence(start, end) +
dest.substring(dend)
source.subSequence(start, end) +
dest.substring(dend)
if (dstart > 0) {
val prevNearest = dest[dstart - 1]
if (Regex(rexDot).matches(prevNearest.toString()) && Regex(rexDot).matches(source)) {
if (Regex(REGEX_DOT).matches(prevNearest.toString()) && Regex(REGEX_DOT).matches(source)) {
showSnack(dialog, R.string.error_repeated_dot_symbol)
return@InputFilter ""
}
if (Regex(rexColon).matches(prevNearest.toString()) && Regex(rexColon).matches(source)) {
if (Regex(REGEX_COLON).matches(prevNearest.toString()) && Regex(REGEX_COLON).matches(source)) {
showSnack(dialog, R.string.error_repeated_colon_symbol)
return@InputFilter ""
}
@@ -141,8 +141,8 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) {
showSnack(dialog, R.string.error_invalid_ip_address)
return@InputFilter ""
}
if (!fullText.contains(Regex(rexColon))) {
fullText.split(Regex(rexDot)).dropLastWhile { it.isEmpty() }.forEach { s ->
if (!fullText.contains(Regex(REGEX_COLON))) {
fullText.split(Regex(REGEX_DOT)).dropLastWhile { it.isEmpty() }.forEach { s ->
if (s.toIntOrNull()?.let { it <= 255 } != true) {
showSnack(dialog, R.string.error_dot_decimal_notation_num_over_255)
return@InputFilter ""
@@ -150,14 +150,14 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) {
}
} else {
if (!fullText.matches(rexFullIpWithColon)) {
if (!dest.substring(0, dstart).contains(Regex(rexColon)) && dend == dest.length) {
if (!dest.substring(0, dstart).contains(Regex(REGEX_COLON)) && dend == dest.length) {
showSnack(dialog, R.string.error_colon_must_follow_a_valid_ip_address)
} else {
showSnack(dialog, R.string.error_invalid_ip_address)
}
return@InputFilter ""
}
fullText.split(Regex("$rexDot|$rexColon")).dropLastWhile { it.isEmpty() }.forEachIndexed { index, s ->
fullText.split(Regex("$REGEX_DOT|$REGEX_COLON")).dropLastWhile { it.isEmpty() }.forEachIndexed { index, s ->
if (index < 4 && s.toIntOrNull()?.let { it <= 255 } != true) {
showSnack(dialog, R.string.error_dot_decimal_notation_num_over_255)
return@InputFilter ""
@@ -170,8 +170,8 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) {
}
}
return@InputFilter source
.replace(Regex("$rexDot+"), ".")
.replace(Regex("$rexColon+"), ":")
.replace(Regex("$REGEX_DOT+"), ".")
.replace(Regex("$REGEX_COLON+"), ":")
}
}
}
@@ -193,22 +193,22 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) {
}
dialog.dismiss()
devPlugin
.connectToRemoteServer(input, mClientModeItem)
.connectToRemoteServer(context, input, mClientModeItem)
.subscribe({ Pref.setServerAddress(input) }, onConnectionException)
}
companion object {
const val rexDot = "[,.,。\\u0020]"
const val rexColon = "[:]"
const val REGEX_DOT = "[,.,。\\u0020]"
const val REGEX_COLON = "[:]"
private const val rexIpDec = "\\d{1,3}"
private const val rexPort = "\\d{1,5}"
private const val REGEX_IP_DEC = "\\d{1,3}"
private const val REGEX_PORT = "\\d{1,5}"
val rexPartialIp = Regex("^$rexIpDec($rexDot($rexIpDec($rexDot($rexIpDec($rexDot($rexIpDec)?)?)?)?)?)?")
val rexFullIpWithColon = Regex("\\d+$rexDot\\d+$rexDot\\d+$rexDot\\d+$rexColon\\d*")
val rexValidIp = Regex("$rexIpDec$rexDot$rexIpDec$rexDot$rexIpDec$rexDot$rexIpDec($rexColon$rexPort)?")
val rexAcceptable = Regex("($rexDot|$rexColon|\\d)+")
val rexPartialIp = Regex("^$REGEX_IP_DEC($REGEX_DOT($REGEX_IP_DEC($REGEX_DOT($REGEX_IP_DEC($REGEX_DOT($REGEX_IP_DEC)?)?)?)?)?)?")
val rexFullIpWithColon = Regex("\\d+$REGEX_DOT\\d+$REGEX_DOT\\d+$REGEX_DOT\\d+$REGEX_COLON\\d*")
val rexValidIp = Regex("$REGEX_IP_DEC$REGEX_DOT$REGEX_IP_DEC$REGEX_DOT$REGEX_IP_DEC$REGEX_DOT$REGEX_IP_DEC($REGEX_COLON$REGEX_PORT)?")
val rexAcceptable = Regex("($REGEX_DOT|$REGEX_COLON|\\d)+")
}

View File

@@ -97,6 +97,8 @@ class AccessibilityTool(val context: Context) {
private fun enableWithRoot(timeout: Long? = null): Boolean = when (timeout != null) {
true -> enableWithRoot() && AccessibilityService.waitForEnabled(timeout.toLong())
else -> try {
disableWithRoot()
val services = getEnabledWithRoot(true)
val cmdServices = "settings put secure enabled_accessibility_services $services"
@@ -116,8 +118,12 @@ class AccessibilityTool(val context: Context) {
private fun enableWithSecure(timeout: Long? = null): Boolean = when (timeout != null) {
true -> enableWithSecure() && AccessibilityService.waitForEnabled(timeout)
else -> try {
Secure.putString(context.contentResolver, Secure.ENABLED_ACCESSIBILITY_SERVICES, getEnabledWithSecure(true))
disableWithSecure()
val enabledWithSecure = getEnabledWithSecure(true)
Secure.putString(context.contentResolver, Secure.ENABLED_ACCESSIBILITY_SERVICES, enabledWithSecure)
Secure.putInt(context.contentResolver, Secure.ACCESSIBILITY_ENABLED, 1)
isEnabled()
} catch (e: Exception) {
false

View File

@@ -171,7 +171,7 @@ class SimpleActionAutomator(private val accessibilityBridge: AccessibilityBridge
fun longClick(x: Int, y: Int) = globalActionAutomatorForGesture.longClick(x, y)
@ScriptInterface
fun swipe(x1: Int, y1: Int, x2: Int, y2: Int, delay: Int) = globalActionAutomatorForGesture.swipe(x1, y1, x2, y2, delay.toLong())
fun swipe(x1: Int, y1: Int, x2: Int, y2: Int, duration: Int) = globalActionAutomatorForGesture.swipe(x1, y1, x2, y2, duration.toLong())
@ScriptInterface
fun paste(target: ActionTarget) = performAction(target.createAction(AccessibilityNodeInfo.ACTION_PASTE))

View File

@@ -184,6 +184,6 @@ class GlobalActionAutomator(private val mHandler: Handler?, private val serviceP
private fun scaleY(y: Int) = mScreenMetrics?.scaleX(y) ?: y
fun swipe(x1: Int, y1: Int, x2: Int, y2: Int, delay: Long) = gesture(0, delay, intArrayOf(x1, y1), intArrayOf(x2, y2))
fun swipe(x1: Int, y1: Int, x2: Int, y2: Int, duration: Long) = gesture(0, duration, intArrayOf(x1, y1), intArrayOf(x2, y2))
}

View File

@@ -61,6 +61,10 @@ class UiObjectCollection private constructor(private val nodes: List<UiObject?>)
return success
}
override fun toString(): String {
return "${UiObjectCollection::class.java.name}@${hashCode()}"
}
companion object {
val EMPTY = of(emptyList())

View File

@@ -1,30 +1,40 @@
package org.autojs.autojs.core.console
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.CountDownTimer
import android.os.Looper
import android.util.Log
import androidx.annotation.ColorInt
import com.afollestad.materialdialogs.DialogAction
import com.afollestad.materialdialogs.MaterialDialog
import org.autojs.autojs.annotation.ScriptInterface
import org.autojs.autojs.permission.DisplayOverOtherAppsPermission
import org.autojs.autojs.pref.Language
import org.autojs.autojs.runtime.ScriptRuntime
import org.autojs.autojs.runtime.api.AbstractConsole
import org.autojs.autojs.runtime.exception.ScriptInterruptedException
import org.autojs.autojs.tool.UiHandler
import org.autojs.autojs.ui.common.NotAskAgainDialog
import org.autojs.autojs.ui.enhancedfloaty.FloatyService
import org.autojs.autojs.ui.enhancedfloaty.ResizableExpandableFloatyWindow
import org.autojs.autojs.ui.enhancedfloaty.gesture.DragGesture
import org.autojs.autojs.util.ClipboardUtils
import org.autojs.autojs.util.StringUtils.key
import org.autojs.autojs.util.ViewUtils
import org.autojs.autojs.util.ViewUtils.setViewMeasure
import org.autojs.autojs6.R
import org.joda.time.format.DateTimeFormat
import org.opencv.core.Point
import org.opencv.core.Size
import java.io.IOException
import java.lang.ref.WeakReference
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.BlockingQueue
import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.ceil
import kotlin.math.pow
/**
* Created by Stardust on 2017/5/2.
@@ -36,6 +46,8 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() {
var configurator = Configurator()
private val mDefaultSafeDelay: Long = 360
private val mSafeSizeToSend: Int = 2.0.pow(16).toInt()
private val mSafeSizeToCopy: Int = 2.0.pow(19).toInt()
private var mLogListeners = ArrayList<WeakReference<LogListener?>>()
private var mConsoleView: WeakReference<ConsoleView?>? = null
@@ -43,7 +55,9 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() {
@get:Synchronized
private var mCountDownTimer: CountDownTimer? = null
private val context = uiHandler.context
private val context: Context
get() = mConsoleView?.get()?.context ?: uiHandler.context
private val mLockWindowShow = Object()
private val mLockWindowCreated = Object()
private val mLockConsoleView = Object()
@@ -51,7 +65,7 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() {
private val mFloatyWindow: ResizableExpandableFloatyWindow
private val mConsoleFloaty: ConsoleFloaty
private val mInput: BlockingQueue<String> = ArrayBlockingQueue(1)
private val mDisplayOverOtherAppsPerm = DisplayOverOtherAppsPermission(context)
private val mDisplayOverOtherAppsPerm = DisplayOverOtherAppsPermission(uiHandler.context)
val logEntries = ArrayList<LogEntry>()
@@ -60,7 +74,9 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() {
private set
private val logEntriesJoint
get() = logEntries.joinToString("\n") { it.content }
get() = synchronized(logEntries) {
logEntries.joinToString("\n") { it.content }
}
// val size: Size
// get() = configurator.size ?: Size()
@@ -123,26 +139,174 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() {
mLogListeners.forEach { it.get()?.onLogClear() }
}
fun copyAll() {
fun copyAll() = copyAll(logEntriesJoint)
private fun copyAll(text: String, cutOutEntriesSize: Int = -1) {
if (text.isEmpty()) {
ViewUtils.showToast(context, R.string.text_no_log_entries_to_copy)
return
}
try {
ClipboardUtils.setClip(context, logEntriesJoint)
ViewUtils.showToast(context, R.string.text_already_copied_to_clip)
} catch (_: Exception) {
ViewUtils.showToast(context, R.string.text_failed)
ClipboardUtils.setClip(context, text)
if (cutOutEntriesSize >= 0) {
ViewUtils.showToast(
context, context.getString(
R.string.text_already_copied_to_clip_but_only_latest_few_items,
cutOutEntriesSize
), true
)
} else {
ViewUtils.showToast(context, R.string.text_already_copied_to_clip)
}
} catch (e: Exception) {
e.printStackTrace()
if (text.length < mSafeSizeToCopy || cutOutEntriesSize >= 0) {
ViewUtils.showToast(context, R.string.text_failed_to_copy)
return
}
try {
val cutOutEntries = cutOutEntries(mSafeSizeToCopy)
copyAll(cutOutEntries.joinToString("\n") { it.content }, cutOutEntries.size)
} catch (e: Exception) {
e.printStackTrace()
ViewUtils.showToast(context, R.string.text_failed_to_copy)
}
}
}
fun export() {
val sendIntent: Intent = Intent().apply {
@Suppress("SpellCheckingInspection")
mConsoleView?.get()?.export("autojs6-log-${DateTimeFormat.forPattern("yyyyMMdd-HHmmss").print(System.currentTimeMillis())}.txt")
}
fun export(uri: Uri?) {
val message = logEntriesJoint
if (message.isEmpty()) {
ViewUtils.showToast(context, R.string.text_no_log_entries_to_export)
return
}
try {
context.contentResolver.openOutputStream(uri ?: return)?.use {
it.write(message.toByteArray())
it.flush()
}
ViewUtils.showToast(
context,
context.getString(R.string.text_some_items_exported, logEntries.size),
true,
)
} catch (e: IOException) {
e.printStackTrace()
MaterialDialog.Builder(context)
.title(R.string.text_prompt)
.content(R.string.text_failed_to_export_log_entries)
.cancelable(false)
.autoDismiss(false)
.neutralText(R.string.text_details)
.neutralColorRes(R.color.dialog_button_hint)
.onNeutral { dialog, _ ->
dialog.setActionButton(DialogAction.NEUTRAL, null)
dialog.setActionButton(DialogAction.POSITIVE, R.string.dialog_button_dismiss)
dialog.contentView?.text = e.stackTraceToString()
dialog.titleView?.text = context.getString(R.string.text_details)
}
.positiveText(R.string.dialog_button_dismiss)
.onPositive { dialog, _ -> dialog.dismiss() }
.show()
}
}
fun send() {
val message = logEntriesJoint
if (message.isEmpty()) {
ViewUtils.showToast(context, R.string.text_no_log_entries_to_send)
return
}
try {
send(message)
} catch (e: Exception) {
e.printStackTrace()
if (message.length < mSafeSizeToSend) {
throw Exception(e)
}
try {
val cutOutEntries = cutOutEntries(mSafeSizeToSend)
val msgReason = context.getString(R.string.text_num_of_log_entries_exceeds_limit_for_sending, logEntries.size)
val msgAction = context.getString(R.string.text_only_latest_few_items_will_be_sent, cutOutEntries.size)
NotAskAgainDialog.Builder(
context,
key(R.string.key_dialog_num_of_log_entries_exceeds_limit_for_sending),
).apply {
title(R.string.text_prompt)
content("$msgReason, $msgAction.")
cancelable(false)
negativeText(R.string.dialog_button_quit)
positiveText(R.string.dialog_button_continue)
positiveColorRes(R.color.dialog_button_attraction)
onPositive { _, _ -> send(cutOutEntries) }
if (show() == null) {
ViewUtils.showToast(context, msgAction.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Language.getPrefLanguage().locale)
else it.toString()
}, true)
send(cutOutEntries)
}
}
} catch (e: Exception) {
e.printStackTrace()
MaterialDialog.Builder(context)
.title(R.string.text_prompt)
.content(R.string.text_failed_to_send_log_entries)
.cancelable(false)
.autoDismiss(false)
.neutralText(R.string.text_details)
.neutralColorRes(R.color.dialog_button_hint)
.onNeutral { dialog, _ ->
dialog.setActionButton(DialogAction.NEUTRAL, null)
dialog.setActionButton(DialogAction.POSITIVE, R.string.text_close)
dialog.contentView?.text = e.stackTraceToString()
dialog.titleView?.text = context.getString(R.string.text_details)
}
.positiveText(R.string.dialog_button_dismiss)
.onPositive { dialog, _ -> dialog.dismiss() }
.show()
}
}
}
private fun send(entries: MutableList<LogEntry>) {
send(entries.joinToString("\n") { it.content })
}
private fun send(message: String) {
val sendIntent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, logEntriesJoint)
putExtra(Intent.EXTRA_TEXT, message)
type = "text/plain"
}
context.startActivity(Intent.createChooser(sendIntent, null).apply {
uiHandler.context.startActivity(Intent.createChooser(sendIntent, null).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
})
}
private fun cutOutEntries(maxLength: Int): MutableList<LogEntry> {
var accLength = 0
val chosenEntries = mutableListOf<LogEntry>()
synchronized(logEntries) {
for (entry in logEntries.reversed()) {
if (accLength >= maxLength) {
break
}
accLength += entry.content.length
chosenEntries.add(0, entry)
}
}
return chosenEntries
}
override fun show() = show(false)
@ScriptInterface
@@ -153,7 +317,7 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() {
synchronized(mLockWindowShow) {
if (!mDisplayOverOtherAppsPerm.has()) {
mDisplayOverOtherAppsPerm.config()
uiHandler.toast(R.string.error_no_draw_overlays_permission)
uiHandler.toast(R.string.error_no_display_over_other_apps_permission)
return
}
if (isReset) reset()
@@ -168,7 +332,7 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() {
// SecurityException: https://github.com/hyb1996-guest/AutoJsIssueReport/issues/4781
} catch (e: Exception) {
e.printStackTrace()
uiHandler.toast(R.string.error_no_draw_overlays_permission)
uiHandler.toast(R.string.error_no_display_over_other_apps_permission)
}
}
synchronized(mLockWindowCreated) {
@@ -214,7 +378,7 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() {
}
private fun startFloatyService() {
context.startService(Intent(context, FloatyService::class.java))
uiHandler.context.startService(Intent(uiHandler.context, FloatyService::class.java))
}
@ScriptInterface
@@ -234,7 +398,7 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() {
configurator.setSize(w, h)
if (isShowing) {
mConsoleFloaty.expandedView?.let {
setViewMeasure(it, w.toInt(), h.toInt())
ViewUtils.setViewMeasure(it, w.toInt(), h.toInt())
}
}
}

View File

@@ -22,6 +22,7 @@ import androidx.recyclerview.widget.RecyclerView;
import org.autojs.autojs.tool.MapBuilder;
import org.autojs.autojs.ui.enhancedfloaty.ResizableExpandableFloatyWindow;
import org.autojs.autojs.ui.log.LogActivity;
import org.autojs.autojs.util.DisplayUtils;
import org.autojs.autojs6.R;
import org.jetbrains.annotations.NotNull;
@@ -40,6 +41,7 @@ public class ConsoleView extends FrameLayout implements ConsoleImpl.LogListener
private final static int sRefreshInterval = 100;
private final Map<Integer, Integer> mColors = new MapBuilder<Integer, Integer>().build();
private ConsoleImpl mConsole;
private LogActivity mLogActivity;
private RecyclerView mLogListRecyclerView;
private EditText mEditText;
private ResizableExpandableFloatyWindow mWindow;
@@ -240,6 +242,16 @@ public class ConsoleView extends FrameLayout implements ConsoleImpl.LogListener
mConsole.setConsoleView(this);
}
public void setLogActivity(LogActivity activity) {
mLogActivity = activity;
}
public void export(String fileName) {
if (mLogActivity != null) {
mLogActivity.export(fileName);
}
}
@Override
public void onNewLog(ConsoleImpl.LogEntry logEntry) {

View File

@@ -26,7 +26,7 @@ import java.util.Locale;
*/
@SuppressLint("ConstantLocale")
public class GlobalConsole extends ConsoleImpl {
private static final String TAG = "GlobalConsole";
private static final String TAG = GlobalConsole.class.getSimpleName();
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault());
private static final Logger LOGGER = Logger.getLogger(GlobalConsole.class);
@@ -45,7 +45,7 @@ public class GlobalConsole extends ConsoleImpl {
return log;
}
private Priority toLog4jLevel(int level) {
protected Priority toLog4jLevel(int level) {
switch (level) {
case VERBOSE, DEBUG -> {
return Level.DEBUG;
@@ -66,7 +66,7 @@ public class GlobalConsole extends ConsoleImpl {
}
}
private String getLevelChar(int level) {
protected String getLevelChar(int level) {
return switch (level) {
case VERBOSE -> "V";
case DEBUG -> "D";

View File

@@ -1,19 +0,0 @@
package org.autojs.autojs.core.console
import android.content.Context
import android.util.AttributeSet
import org.autojs.autojs.AutoJs
class JsConsoleView : ConsoleView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
init {
setConsole(AutoJs.instance.globalConsole)
}
}

View File

@@ -3,7 +3,6 @@ package org.autojs.autojs.core.http
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import java.net.SocketTimeoutException
import java.util.concurrent.TimeUnit
/**
@@ -14,31 +13,28 @@ class MutableOkHttp : OkHttpClient() {
private var mOkHttpClient: OkHttpClient
private val maxRetries = 3
private var mTimeout = (30 * 1000).toLong()
private var mTimeout = 30 * 1000L
private val mRetryInterceptor = Interceptor { chain: Interceptor.Chain ->
val request = chain.request()
var response: Response? = null
var tryCount = 0
do {
var succeed: Boolean
var isSuccessful: Boolean
try {
response?.close()
chain.proceed(request).apply {
response = this
succeed = isSuccessful
}
} catch (e: SocketTimeoutException) {
succeed = false
response = chain.proceed(request).also { isSuccessful = it.isSuccessful }
} catch (e: Exception) {
isSuccessful = false
if (tryCount >= maxRetries) {
throw e
}
}
if (succeed || tryCount >= maxRetries) {
if (isSuccessful || tryCount >= maxRetries) {
break
}
tryCount++
} while (true)
response!!
response ?: throw Exception("Failed to make a request")
}
init {

View File

@@ -130,7 +130,7 @@ public class ColorFinder {
@Deprecated
@ScriptInterface
@SuppressWarnings("deprecation")
@CodeAuthor(name = "LYS", homepage = "https://github.com/LYS86")
@CodeAuthor(name = "LYS86", homepage = "https://github.com/LYS86")
public Point[] findAllMultiColors(ImageWrapper image, int firstColor, int threshold, Rect rect, int[] points) {
Point[] firstPoints = findAllPointsForColor(image, firstColor, threshold, rect);
List<Point> resultPoints = new ArrayList<>();

View File

@@ -1,7 +1,9 @@
package org.autojs.autojs.core.inputevent;
import android.content.Context;
import androidx.annotation.NonNull;
import android.text.TextUtils;
import org.autojs.autojs6.R;
@@ -67,7 +69,6 @@ public class InputEventObserver {
void onInputEvent(@NonNull InputEvent e);
}
private static InputEventObserver sGlobal;
private final CopyOnWriteArrayList<InputEventListener> mInputEventListeners = new CopyOnWriteArrayList<>();
private final Context mContext;
private Shell mShell;
@@ -76,18 +77,10 @@ public class InputEventObserver {
mContext = context;
}
public static InputEventObserver getGlobal(Context context) {
if (sGlobal == null) {
initGlobal(context);
}
return sGlobal;
}
private static void initGlobal(Context context) {
if (sGlobal != null)
return;
sGlobal = new InputEventObserver(context);
sGlobal.observe();
public static InputEventObserver initObserver(Context context) {
var inputEventObserver = new InputEventObserver(context);
inputEventObserver.observe();
return inputEventObserver;
}
public void observe() {

View File

@@ -1,128 +1,45 @@
package org.autojs.autojs.core.looper
import android.os.Handler
import android.os.Looper
import android.os.MessageQueue
import android.os.MessageQueue.IdleHandler
import android.util.Log
import org.autojs.autojs.lang.ThreadCompat
import org.autojs.autojs.rhino.AutoJsContext
import org.autojs.autojs.runtime.ScriptRuntime
import org.autojs.autojs.runtime.api.Threads
import org.autojs.autojs.runtime.api.Timers
import org.autojs.autojs.runtime.exception.ScriptInterruptedException
import org.mozilla.javascript.Context
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
/**
* Created by Stardust on 2017/7/29.
* Modified by SuperMonster003 as of Jul 12, 2023.
* Transformed by SuperMonster003 on Jul 12, 2023.
* Modified by aiselp as of Jun 4, 2023.
* ! 调整内容:
* ! 使此类只负责单 loop 线程生命周期管理, 移除繁琐的调用链
* ! 调整 timer 由此类创建
* ! 通过向此类添加 AsyncTask 以监听线程退出事件
* Modified by SuperMonster003 as of Aug 28, 2023.
*/
// @Overruled by SuperMonster003 on Jul 12, 2023.
// ! Author: aiselp
// ! Related PR:
// ! http://pr.autojs6.com/75
// ! Reason:
// ! Sorry but my current capabilities are not sufficient
// ! to fully understand everything from above pull request(s),
// ! so most of the code will remain as is. :)
class Loopers(runtime: ScriptRuntime) : IdleHandler {
interface LooperQuitHandler {
fun shouldQuit(): Boolean
}
private val waitWhenIdle: ThreadLocal<Boolean> = object : ThreadLocal<Boolean>() {
override fun initialValue(): Boolean {
return Looper.myLooper() == Looper.getMainLooper()
}
}
private val waitIds: ThreadLocal<HashSet<Int>> = object : ThreadLocal<HashSet<Int>>() {
override fun initialValue(): HashSet<Int> {
return HashSet()
}
}
private val maxWaitId: ThreadLocal<Int> = object : ThreadLocal<Int>() {
override fun initialValue(): Int {
return 0
}
}
private val looperQuitHandlers = ThreadLocal<CopyOnWriteArrayList<LooperQuitHandler>>()
class Loopers(val runtime: ScriptRuntime) {
@Volatile
private var mServantLooper: Looper? = null
private val mTimers: Timers
@Suppress("DEPRECATION")
private var mMainLooperQuitHandler: LooperQuitHandler? = null
private val mMainHandler: Handler
private val mMainLooper: Looper?
private val mThreads: Threads
private val mMainMessageQueue: MessageQueue
private var waitWhenIdle: Boolean
private val allTasks = ConcurrentLinkedQueue<AsyncTask>()
init {
mTimers = runtime.timers
mThreads = runtime.threads
prepare()
mMainLooper = Looper.myLooper()
mMainHandler = Handler(Looper.getMainLooper())
mMainMessageQueue = Looper.myQueue()
}
fun addLooperQuitHandler(handler: LooperQuitHandler) {
var handlers = looperQuitHandlers.get()
if (handlers == null) {
handlers = CopyOnWriteArrayList()
looperQuitHandlers.set(handlers)
}
handlers.add(handler)
}
fun removeLooperQuitHandler(handler: LooperQuitHandler): Boolean {
val handlers = looperQuitHandlers.get()
return handlers != null && handlers.remove(handler)
}
private fun shouldQuitLooper(): Boolean {
if (Thread.currentThread().isInterrupted) {
return true
}
if (mTimers.hasPendingCallbacks()) {
return false
}
if (waitWhenIdle.get() == true || waitIds.get()?.isNotEmpty() == true) {
return false
}
if ((Context.getCurrentContext() as AutoJsContext).hasPendingContinuation()) {
return false
}
val handlers = looperQuitHandlers.get() ?: return true
for (handler in handlers) {
if (!handler.shouldQuit()) {
return false
}
}
return true
}
private fun initServantThread() {
ThreadCompat {
Looper.prepare()
mServantLooper = Looper.myLooper()
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
synchronized(this@Loopers as Object) {
notifyAll()
}
Looper.loop()
}.start()
}
private val lock = ReentrantLock()
private val condition = lock.newCondition()
val servantLooper: Looper
get() {
if (mServantLooper == null) {
initServantThread()
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
synchronized(this@Loopers as Object) {
lock.withLock {
try {
wait()
condition.await()
} catch (e: InterruptedException) {
throw ScriptInterruptedException(e)
}
@@ -131,69 +48,145 @@ class Loopers(runtime: ScriptRuntime) : IdleHandler {
return mServantLooper!!
}
private fun quitServantLooper() {
mServantLooper?.quit()
val timer: Timer
val myLooper: Looper
init {
prepare()
myLooper = Looper.myLooper()!!
timer = Timer(runtime, myLooper)
waitWhenIdle = myLooper == Looper.getMainLooper()
}
fun waitWhenIdle(): Int {
val id = maxWaitId.get()!!
Log.d(LOG_TAG, "waitWhenIdle: $id")
maxWaitId.set(id + 1)
waitIds.get()!!.add(id)
return id
@Deprecated("Deprecated in Java", ReplaceWith("AsyncTask"))
interface LooperQuitHandler {
fun shouldQuit(): Boolean
}
fun doNotWaitWhenIdle(waitId: Int) {
Log.d(LOG_TAG, "doNotWaitWhenIdle: $waitId")
waitIds.get()!!.remove(waitId)
open class AsyncTask(private val desc: String) {
private val allBind = ConcurrentLinkedQueue<Loopers>()
var isEnd: Boolean = false
private set
// 线程即将退出时调用, 返回 true 阻止线程退出, 只要有一个 task 返回 true 线程就不会退出
open fun onFinish(loopers: Loopers) = true
fun end() {
isEnd = true
}
// 线程正在退出, 这里应该结束任务的执行, 回收资源
open fun onStop(loopers: Loopers) = Unit
override fun toString() = "AsyncTask: $desc"
}
fun createAndAddAsyncTask(desc: String): AsyncTask {
return AsyncTask(desc).also { allTasks.add(it) }
}
fun addAsyncTask(task: AsyncTask) {
synchronized(myLooper) {
allTasks.add(task)
}
}
fun removeAsyncTask(task: AsyncTask) {
synchronized(myLooper) {
allTasks.remove(task)
timer.post(EMPTY_RUNNABLE)
}
}
private fun checkTask(): Boolean {
allTasks.removeAll(allTasks.filter { it.isEnd }.toSet())
return allTasks.any { it.onFinish(this) }
}
private fun shouldQuitLooper(): Boolean {
synchronized(myLooper) {
return when {
Thread.currentThread().isInterrupted -> true
timer.hasPendingCallbacks() -> false
// 检查是否有运行中的线程
checkTask() -> false
waitWhenIdle -> false
(Context.getCurrentContext() as AutoJsContext).hasPendingContinuation() -> false
else -> true
}
}
}
private fun initServantThread() {
ThreadCompat {
Looper.prepare()
mServantLooper = Looper.myLooper()
lock.withLock {
condition.signalAll()
}
Looper.loop()
}.start()
}
@Deprecated("Deprecated in Java", ReplaceWith("AsyncTask"))
fun waitWhenIdle(b: Boolean) {
waitWhenIdle.set(b)
waitWhenIdle = b
}
fun recycle() {
quitServantLooper()
mMainMessageQueue.removeIdleHandler(this)
Log.d(LOG_TAG, "recycle")
for (task in allTasks.filter { !it.isEnd }) {
try {
task.onStop(this)
} catch (e: Exception) {
Log.w(LOG_TAG, e)
}
}
mServantLooper?.quit()
}
fun setMainLooperQuitHandler(mainLooperQuitHandler: LooperQuitHandler?) {
@Deprecated("Deprecated in Java", ReplaceWith("AsyncTask"))
fun setMainLooperQuitHandler(@Suppress("DEPRECATION") mainLooperQuitHandler: LooperQuitHandler?) {
mMainLooperQuitHandler = mainLooperQuitHandler
}
override fun queueIdle(): Boolean {
val l = Looper.myLooper() ?: return true
if (l == mMainLooper) {
Log.d(LOG_TAG, "main looper queueIdle")
if (shouldQuitLooper() && !mThreads.hasRunningThreads() && mMainLooperQuitHandler != null && mMainLooperQuitHandler!!.shouldQuit()) {
Log.d(LOG_TAG, "main looper quit")
l.quit()
}
} else {
Log.d(LOG_TAG, "looper queueIdle: $l")
if (shouldQuitLooper()) {
l.quit()
}
}
return true
}
fun prepare() {
private fun prepare() {
if (Looper.myLooper() == null) {
LooperHelper.prepare()
}
Looper.myQueue().addIdleHandler(this)
Looper.myQueue().addIdleHandler(MessageQueue.IdleHandler {
if (this == runtime.loopers) {
Log.d(LOG_TAG, "main looper queueIdle")
if (shouldQuitLooper() && mMainLooperQuitHandler?.shouldQuit() == true) {
Log.d(LOG_TAG, "main looper quit")
Looper.myLooper()?.quitSafely()
}
} else {
Log.d(LOG_TAG, "looper queueIdle $this")
if (shouldQuitLooper()) {
Log.d(LOG_TAG, "looper quit $this")
Looper.myLooper()?.quitSafely()
}
}
return@IdleHandler true
})
}
fun notifyThreadExit(thread: TimerThread) {
Log.d(LOG_TAG, "notifyThreadExit: $thread")
// 当子线程退成时主线程需要检查自身是否退出主线程在所有子线程执行完成后才能退出如果主线程已经执行完任务仍然要等待所有子线程
// 此时通过向主线程发送一个空的Runnable主线程执行完这个Runnable后会触发IdleHandler从而检查自身是否退出
mMainHandler.post(EMPTY_RUNNABLE)
// 当子线程退成时, 主线程需要检查自身是否退出 (主线程在所有子线程执行完成后才能退出, 如果主线程已经执行完任务仍然要等待所有子线程),
// 此时通过向主线程发送一个空的 Runnable, 主线程执行完这个 Runnable 后会触发 IdleHandler, 从而检查自身是否退出
// mHandler.post(EMPTY_RUNNABLE)
}
companion object {
private const val LOG_TAG = "Loopers"
private val EMPTY_RUNNABLE = Runnable {}
}
}

View File

@@ -3,120 +3,137 @@ package org.autojs.autojs.core.looper
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import android.util.SparseArray
import org.autojs.autojs.concurrent.VolatileBox
import org.autojs.autojs.runtime.ScriptRuntime
import kotlin.math.max
import org.mozilla.javascript.BaseFunction
import org.mozilla.javascript.Context
import org.mozilla.javascript.Scriptable
import org.mozilla.javascript.Undefined
import java.util.concurrent.ConcurrentHashMap
import kotlin.random.Random
/**
* Created by Stardust on 2017/12/27.
* Modified by aiselp as of Jun 14, 2023.
* Modified by SuperMonster003 as of Jul 12, 2023.
* Transformed by SuperMonster003 on Jul 12, 2023.
*/
// @Overruled by SuperMonster003 on Jul 12, 2023.
// ! Author: aiselp
// ! Related PR:
// ! http://pr.autojs6.com/75
// ! http://pr.autojs6.com/78
// ! Reason:
// ! Sorry but my current capabilities are not sufficient
// ! to fully understand everything from above pull request(s),
// ! so most of the code will remain as is. :)
class Timer @JvmOverloads constructor(runtime: ScriptRuntime, maxCallbackMillisForAllThread: VolatileBox<Long>, private val looper: Looper? = Looper.myLooper()) {
class Timer(runtime: ScriptRuntime, looper: Looper) {
private val mHandlerCallbacks = SparseArray<Runnable?>()
private var mCallbackMaxId = 0
private val myLooper: Looper = looper
private val mHandlerCallbacks = ConcurrentHashMap<Int, Runnable?>()
private val mRuntime: ScriptRuntime = runtime
@Suppress("DEPRECATION")
private val mHandler = looper?.let { Handler(it) } ?: Handler()
private var mMaxCallbackUptimeMillis: Long = 0
private val mMaxCallbackMillisForAllThread: VolatileBox<Long> = maxCallbackMillisForAllThread
private val mHandler: Handler = Handler(looper)
private val isUiLoop: Boolean = looper == Looper.getMainLooper()
private val context: Context? by lazy { Context.getCurrentContext() }
fun setTimeout(callback: Any, delay: Long, vararg args: Array<out Any?>): Int {
mCallbackMaxId++
val id = mCallbackMaxId
constructor(runtime: ScriptRuntime) : this(runtime, Looper.myLooper()!!)
fun setTimeout(callback: Any, delay: Long, vararg args: Any?): Int {
val id = createTimerId()
val r = Runnable {
callFunction(callback, args)
callFunction(callback, null, args)
mHandlerCallbacks.remove(id)
}
mHandlerCallbacks.put(id, r)
mHandlerCallbacks[id] = r
postDelayed(r, delay)
return id
}
private fun callFunction(callback: Any, args: Array<out Any>) {
private fun callFunction(callback: Any, thiz: Any?, args: Any?) {
val func = callback as BaseFunction
val map: Array<Any> =
(args as? Array<*>)?.map { Context.javaToJS(it, callback.parentScope) }
?.toTypedArray() ?: emptyArray()
try {
mRuntime.bridges.callFunction(callback, null, args)
func.call(
context ?: Context.enter(), func.parentScope,
thiz as? Scriptable ?: Undefined.SCRIPTABLE_UNDEFINED, map
)
} catch (e: Exception) {
if (Looper.myLooper() == Looper.getMainLooper()) {
if (isUiLoop) {
mRuntime.exit(e)
} else {
throw e
}
} else throw e
} finally {
context ?: Context.exit()
}
}
fun clearTimeout(id: Int) = clearCallback(id)
@Synchronized
private fun createTimerId(): Int {
var id: Int
do {
id = Random.nextInt()
} while (mHandlerCallbacks.containsKey(id))
mHandlerCallbacks[id] = EMPTY_RUNNABLE
return id
}
fun setInterval(listener: Any, interval: Long, vararg args: Any): Int {
mCallbackMaxId++
val id = mCallbackMaxId
val r = object : Runnable {
fun setInterval(listener: Any, interval: Long, vararg args: Any?): Int {
val id = createTimerId()
val r: Runnable = object : Runnable {
override fun run() {
mHandlerCallbacks[id] ?: return
callFunction(listener, args)
if (mHandlerCallbacks[id] == null) return
callFunction(listener, null, args)
postDelayed(this, interval)
}
}
mHandlerCallbacks.put(id, r)
mHandlerCallbacks[id] = r
postDelayed(r, interval)
return id
}
fun postDelayed(r: Runnable, interval: Long) {
val uptime = SystemClock.uptimeMillis() + interval
mHandler.postAtTime(r, uptime)
mMaxCallbackUptimeMillis = mMaxCallbackUptimeMillis.coerceAtLeast(uptime)
synchronized(mMaxCallbackMillisForAllThread) { mMaxCallbackMillisForAllThread.set(max(mMaxCallbackMillisForAllThread.get(), uptime)) }
}
// @Reference to aiselp (https://github.com/aiselp) on Jul 18, 2023.
fun post(r: Runnable) {
looper?.let {
synchronized(it) {
mHandler.post(r)
}
synchronized(myLooper) {
val uptime = SystemClock.uptimeMillis() + interval
mHandler.postAtTime(r, uptime)
}
}
fun clearInterval(id: Int) = clearCallback(id)
fun post(r: Runnable) {
synchronized(myLooper) {
mHandler.post(r)
}
}
fun setImmediate(listener: Any, vararg args: Any): Int {
mCallbackMaxId++
val id = mCallbackMaxId
fun clearInterval(id: Int): Boolean = clearCallback(id)
fun clearImmediate(id: Int): Boolean = clearCallback(id)
fun clearTimeout(id: Int): Boolean = clearCallback(id)
fun setImmediate(listener: Any, vararg args: Any?): Int {
val id = createTimerId()
val r = Runnable {
callFunction(listener, args)
callFunction(listener, null, args)
mHandlerCallbacks.remove(id)
}
mHandlerCallbacks.put(id, r)
postDelayed(r, 0)
mHandlerCallbacks[id] = r
post(r)
return id
}
fun clearImmediate(id: Int) = clearCallback(id)
private fun clearCallback(id: Int): Boolean {
val callback = mHandlerCallbacks[id]
if (callback != null) {
mHandler.removeCallbacks(callback)
mHandlerCallbacks.remove(id)
if (mHandlerCallbacks.isEmpty()) mHandler.post(EMPTY_RUNNABLE)
return true
}
return false
}
fun hasPendingCallbacks() = mMaxCallbackUptimeMillis > SystemClock.uptimeMillis()
fun hasPendingCallbacks(): Boolean {
return mHandlerCallbacks.size > 0
}
fun removeAllCallbacks() = mHandler.removeCallbacksAndMessages(null)
fun removeAllCallbacks() {
mHandler.removeCallbacksAndMessages(null)
}
companion object {
private const val LOG_TAG = "Timer"
private val EMPTY_RUNNABLE = Runnable {}
}
}

View File

@@ -1,60 +1,45 @@
package org.autojs.autojs.core.looper
import android.os.Handler
import android.os.Looper
import androidx.annotation.CallSuper
import org.autojs.autojs.concurrent.VolatileBox
import org.autojs.autojs.engine.RhinoJavaScriptEngine
import org.autojs.autojs.lang.ThreadCompat
import org.autojs.autojs.runtime.ScriptRuntime
import org.autojs.autojs.runtime.exception.ScriptInterruptedException
import org.autojs.autojs.util.StringUtils.str
import org.autojs.autojs6.R
import org.mozilla.javascript.Context
import java.util.concurrent.ConcurrentHashMap
/**
* Created by Stardust on 2017/12/27.
* Modified by SuperMonster003 as of Jul 12, 2023.
* Transformed by SuperMonster003 on Jul 12, 2023.
*/
// @Overruled by SuperMonster003 on Jul 12, 2023.
// ! Author: aiselp
// ! Related PR:
// ! http://pr.autojs6.com/75
// ! Reason:
// ! Sorry but my current capabilities are not sufficient
// ! to fully understand everything from above pull request(s),
// ! so most of the code will remain as is. :)
@Suppress("unused")
open class TimerThread(
private val scriptRuntime: ScriptRuntime,
private val maxCallbackUptimeMillisForAllThreads: VolatileBox<Long>,
private val target: Runnable
) : ThreadCompat(target) {
open class TimerThread(private val mRuntime: ScriptRuntime, private val mTarget: Runnable) : ThreadCompat(mTarget) {
private var mTimer: Timer? = null
private var mRunning = false
private val mRunningLock = Object()
private val mAsyncTask = Loopers.AsyncTask("TimerThread")
var loopers: Loopers? = null
init {
mRuntime.loopers.addAsyncTask(mAsyncTask)
}
override fun run() {
scriptRuntime.loopers.prepare()
mTimer = Timer(scriptRuntime, maxCallbackUptimeMillisForAllThreads).also {
sTimerMap[currentThread()] = it
}
(scriptRuntime.engines.myEngine() as? RhinoJavaScriptEngine)?.enterContext()
loopers = Loopers(mRuntime)
mTimer = loopers!!.timer
sTimerMap[currentThread()] = mTimer!!
(mRuntime.engines.myEngine() as RhinoJavaScriptEngine).enterContext()
notifyRunning()
@Suppress("DEPRECATION")
Looper.myLooper()?.let {
Handler(it).post(target)
} ?: Handler().post(target)
mTimer!!.post(mTarget)
try {
Looper.loop()
} catch (e: Throwable) {
if (!ScriptInterruptedException.causedByInterrupted(e)) {
scriptRuntime.console.error("${currentThread()}: $e")
mRuntime.console.error(currentThread().toString() + ": ", e)
}
} finally {
// mRuntime.console.log("TimerThread exit");
onExit()
mTimer = null
Context.exit()
@@ -76,49 +61,69 @@ open class TimerThread(
@CallSuper
protected open fun onExit() {
scriptRuntime.loopers.notifyThreadExit(this)
mRuntime.loopers.removeAsyncTask(mAsyncTask)
mRuntime.loopers.notifyThreadExit(this)
}
fun setTimeout(callback: Any, delay: Long, vararg args: Any): Int {
return timer.setTimeout(callback, delay, *args)
}
fun setTimeout(callback: Any): Int {
return setTimeout(callback, 1)
}
val timer: Timer
get() {
checkNotNull(mTimer) { str(R.string.error_thread_is_not_alive) }
return mTimer!!
checkNotNull(mTimer) { "thread is not alive" }
return mTimer as Timer
}
fun setTimeout(callback: Any, delay: Long, vararg args: Array<out Any?>) = timer.setTimeout(callback, delay, *args)
fun clearTimeout(id: Int): Boolean {
return timer.clearTimeout(id)
}
fun clearTimeout(id: Int) = timer.clearTimeout(id)
fun setInterval(listener: Any?, interval: Long, vararg args: Any): Int {
return timer.setInterval(listener!!, interval, *args)
}
fun setInterval(listener: Any, interval: Long, vararg args: Array<out Any?>) = timer.setInterval(listener, interval, *args)
fun setInterval(listener: Any?): Int {
return setInterval(listener, 1)
}
fun clearInterval(id: Int) = timer.clearInterval(id)
fun clearInterval(id: Int): Boolean {
return timer.clearInterval(id)
}
fun setImmediate(listener: Any, vararg args: Array<out Any?>) = timer.setImmediate(listener, *args)
fun setImmediate(listener: Any, vararg args: Any): Int {
return timer.setImmediate(listener, *args)
}
fun clearImmediate(id: Int) = timer.clearImmediate(id)
fun clearImmediate(id: Int): Boolean {
return timer.clearImmediate(id)
}
@Throws(InterruptedException::class)
fun waitFor() {
synchronized(mRunningLock) {
if (!mRunning) {
mRunningLock.wait()
}
if (mRunning) return
mRunningLock.wait()
}
}
override fun toString() = "Thread[$name,$priority]"
override fun toString(): String {
return "Thread[$name,$priority]"
}
companion object {
private val sTimerMap = ConcurrentHashMap<Thread, Timer?>()
@JvmStatic
fun getTimerForThread(thread: Thread) = sTimerMap[thread]
fun getTimerForThread(thread: Thread): Timer? {
return sTimerMap[thread]
}
@JvmStatic
val timerForCurrentThread
val timerForCurrentThread: Timer?
get() = getTimerForThread(currentThread())
}
}

View File

@@ -4,7 +4,6 @@ import android.content.Context;
import android.widget.EditText;
import com.afollestad.materialdialogs.MaterialDialog;
import org.autojs.autojs.core.eventloop.EventEmitter;
import org.autojs.autojs.core.looper.Loopers;
import org.autojs.autojs.core.looper.Timer;
@@ -13,6 +12,8 @@ import org.autojs.autojs.tool.UiHandler;
/**
* Created by Stardust on 2018/4/17.
* Modified by SuperMonster003 as of Mar 20, 2022.
* Modified by aiselp as of Jun 10, 2023.
*/
public class JsDialogBuilder extends MaterialDialog.Builder {
@@ -21,7 +22,7 @@ public class JsDialogBuilder extends MaterialDialog.Builder {
private final Timer mTimer;
private final Loopers mLoopers;
private JsDialog mDialog;
private volatile int mWaitId = -1;
private volatile Loopers.AsyncTask task;
public JsDialogBuilder(Context context, ScriptRuntime runtime) {
super(context);
@@ -55,14 +56,14 @@ public class JsDialogBuilder extends MaterialDialog.Builder {
}
});
dismissListener(dialog -> {
mTimer.postDelayed(() -> mLoopers.doNotWaitWhenIdle(mWaitId), 0);
mTimer.postDelayed(() -> mLoopers.removeAsyncTask(task), 0);
emit("dismiss", dialog);
});
cancelListener(dialog -> emit("cancel", dialog));
}
public void onShowCalled() {
mTimer.postDelayed(() -> mWaitId = mLoopers.waitWhenIdle(), 0);
mTimer.postDelayed(() -> task = mLoopers.createAndAddAsyncTask("js-dialog"), 0);
}
public JsDialog getDialog() {

View File

@@ -2,19 +2,42 @@ package org.autojs.autojs.core.ui.widget
import android.content.Context
import android.util.AttributeSet
import org.autojs.autojs.AutoJs
import android.util.Log
import org.apache.log4j.Logger
import org.autojs.autojs.core.console.ConsoleImpl
import org.autojs.autojs.core.console.ConsoleView
import org.autojs.autojs.core.console.GlobalConsole
import org.autojs.autojs.tool.UiHandler
import org.autojs.autojs.util.ViewUtils
import java.util.Locale
class JsConsoleView : ConsoleView {
private val TAG = JsConsoleView::class.java.simpleName
private val LOGGER = Logger.getLogger(JsConsoleView::class.java)
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
init {
setConsole(AutoJs.instance.globalConsole)
}
// init {
// setConsole(object : GlobalConsole(UiHandler(context)) {
// override fun println(level: Int, charSequence: CharSequence): String {
// val log = String.format(Locale.getDefault(), "%s", charSequence)
// LOGGER.log(toLog4jLevel(level), log)
// Log.d(TAG, log)
// super.println(level, log)
// return log
// }
// })
// }
//
// override fun onNewLog(logEntry: ConsoleImpl.LogEntry?) {
// if (logEntry != null) {
// ViewUtils.showToast(context, logEntry.content.toString())
// }
// }
}

View File

@@ -8,19 +8,20 @@ import okhttp3.WebSocketListener
import okio.ByteString
import org.autojs.autojs.AutoJs
import org.autojs.autojs.core.eventloop.EventEmitter
val runtime = AutoJs.instance.runtime
import java.lang.ref.WeakReference
/**
* Created by SuperMonster003 on Apr 30, 2023.
*/
// @Reference to kkevsekk1/AutoX (https://github.com/kkevsekk1/AutoX) on Apr 30, 2023.
class WebSocket @JvmOverloads constructor(val client: OkHttpClient, val url: String, isInCurrentThread: Boolean = true) : EventEmitter(
runtime.bridges, runtime.timers.timerForCurrentThread.takeIf { isInCurrentThread }
AutoJs.instance.runtime.bridges, AutoJs.instance.runtime.timers.timerForCurrentThread.takeIf { isInCurrentThread }
), okhttp3.WebSocket {
private var maxRebuildTimes = Int.MAX_VALUE
private var currentRebuildTimes = 0
private var isExitOnClose = false
private var exitOnCloseTimeout = DEFAULT_EXIT_ON_CLOSE_TIMEOUT
private var listener: WebSocketMessageListener
private lateinit var webSocket: okhttp3.WebSocket
@@ -32,16 +33,17 @@ class WebSocket @JvmOverloads constructor(val client: OkHttpClient, val url: Str
private fun build() {
webSocket = client.newWebSocket(Builder().url(url).build(), listener)
instances.add(WeakReference(this))
}
fun rebuild() {
cancel()
if (currentRebuildTimes < maxRebuildTimes) {
build()
currentRebuildTimes += 1
} else {
emit("max_rebuild", maxRebuildTimes, this)
emit(EVENT_MAX_REBUILDS, maxRebuildTimes, this)
}
currentRebuildTimes += 1
}
fun rebuild(maxRebuildTimes: Int) {
@@ -53,7 +55,10 @@ class WebSocket @JvmOverloads constructor(val client: OkHttpClient, val url: Str
override fun close(code: Int, reason: String?) = webSocket.close(code, reason)
fun close(code: Int) = webSocket.close(code, null)
@JvmOverloads
fun close(code: Int = CODE_CLOSE_NORMAL) = webSocket.close(code, null)
fun close(reason: String?) = webSocket.close(CODE_CLOSE_NORMAL, reason)
override fun queueSize() = webSocket.queueSize()
@@ -63,34 +68,47 @@ class WebSocket @JvmOverloads constructor(val client: OkHttpClient, val url: Str
override fun send(bytes: ByteString) = webSocket.send(bytes)
override fun on(eventName: String, listener: Any) = this.also { super.on(eventName, listener) }
override fun once(eventName: String, listener: Any) = this.also { super.once(eventName, listener) }
@JvmOverloads
fun exitOnClose(isExitOnClose: Boolean = true) {
this.isExitOnClose = isExitOnClose
}
fun exitOnClose(timeout: Long) {
exitOnClose(true)
exitOnCloseTimeout = maxOf(0, timeout)
}
inner class WebSocketMessageListener(private val ws: WebSocket) : WebSocketListener() {
override fun onClosed(webSocket: okhttp3.WebSocket, code: Int, reason: String) {
ws.emit("closed", code, reason, ws)
}
override fun onClosing(webSocket: okhttp3.WebSocket, code: Int, reason: String) {
ws.emit("closing", code, reason, ws)
}
override fun onFailure(webSocket: okhttp3.WebSocket, t: Throwable, response: Response?) {
Log.w(Companion.TAG, "onFailure")
t.printStackTrace()
ws.emit("failure", t, response, ws)
override fun onOpen(webSocket: okhttp3.WebSocket, response: Response) {
ws.emit(EVENT_OPEN, response, ws)
}
override fun onMessage(webSocket: okhttp3.WebSocket, text: String) {
ws.emit("message", text, ws)
ws.emit("text", text, ws)
ws.emit(EVENT_MESSAGE, text, ws)
ws.emit(EVENT_TEXT, text, ws)
}
override fun onMessage(webSocket: okhttp3.WebSocket, bytes: ByteString) {
ws.emit("message", bytes, ws)
ws.emit("bytes", bytes, ws)
ws.emit(EVENT_MESSAGE, bytes, ws)
ws.emit(EVENT_BYTES, bytes, ws)
}
override fun onOpen(webSocket: okhttp3.WebSocket, response: Response) {
ws.emit("open", response, ws)
override fun onClosing(webSocket: okhttp3.WebSocket, code: Int, reason: String) {
ws.emit(EVENT_CLOSING, code, reason, ws)
}
override fun onClosed(webSocket: okhttp3.WebSocket, code: Int, reason: String) {
ws.emit(EVENT_CLOSED, code, reason, ws)
}
override fun onFailure(webSocket: okhttp3.WebSocket, t: Throwable, response: Response?) {
Log.w(TAG, "onFailure")
t.printStackTrace()
ws.emit(EVENT_FAILURE, t, response, ws)
}
}
@@ -99,6 +117,139 @@ class WebSocket @JvmOverloads constructor(val client: OkHttpClient, val url: Str
private val TAG = WebSocket::class.java.simpleName
private val instances = ArrayList<WeakReference<WebSocket>>()
/**
* Successful operation / regular socket shutdown.
*
* zh-CN: 成功操作或常规的 Socket 关闭.
*/
const val CODE_CLOSE_NORMAL = 1000
/**
* Client is leaving (browser tab closing).
*
* zh-CN: 终端正在处于移除状态, 服务端或客户端即将不可用.
*/
const val CODE_CLOSE_GOING_AWAY = 1001
/**
* Endpoint received a malformed frame.
*
* zh-CN: 终端因协议错误或无效帧而即将终止连接.
*/
const val CODE_CLOSE_PROTOCOL_ERROR = 1002
/**
* Endpoint received an unsupported frame (e.g. binary-only endpoint received text frame).
*
* zh-CN: 终端因帧数据类型不支持而即将终止连接.
*/
const val CODE_CLOSE_UNSUPPORTED = 1003
/**
* Expected close status, received none.
*
* zh-CN: 不包含错误原因, 仅代表已经关闭的状态.
*/
const val CODE_CLOSED_NO_STATUS = 1005
/**
* No close code frame has been receieved.
*
* zh-CN: 异常关闭 (如浏览器关闭).
*/
const val CODE_CLOSE_ABNORMAL = 1006
/**
* Endpoint received inconsistent message (e.g. malformed UTF-8).
*
* zh-CN: 终端接收到不一致的报文 (如异常格式的 UTF-8).
*/
const val CODE_UNSUPPORTED_PAYLOAD = 1007
/**
* Generic code used for situations other than 1003 and 1009.
*
* zh-CN: 终端因收到了违反其策略的报文而即将终止连接.
*/
const val CODE_POLICY_VIOLATION = 1008
/**
* Endpoint won't process large frame.
*
* zh-CN: 终端因无法处理长度过大的报文而即将终止连接.
*/
const val CODE_CLOSE_TOO_LARGE = 1009
/**
* Client wanted an extension which server did not negotiate.
*
* zh-CN: 终端因期望与服务端进行扩展协商而即将终止连接.
*/
const val CODE_MANDATORY_EXTENSION = 1010
/**
* Internal server error while operating.
*
* zh-CN: 服务端因发生内部错误而即将终止连接.
*/
const val CODE_SERVER_ERROR = 1011
/**
* Server/service is restarting.
*
* zh-CN: 服务端正在重启过程中.
*/
const val CODE_SERVICE_RESTART = 1012
/**
* Temporary server condition forced blocking client's request.
*
* zh-CN: 服务端临时拒绝了终端请求.
*/
const val CODE_TRY_AGAIN_LATER = 1013
/**
* Server acting as gateway received an invalid response.
*
* zh-CN: 网关服务器接收到无效的请求.
*/
const val CODE_BAD_GATEWAY = 1014
/**
* Transport Layer Security handshake failure.
*
* zh-CN: TLS 握手失败 (如服务端证书未通过验证等).
*/
const val CODE_TLS_HANDSHAKE_FAIL = 1015
const val EVENT_CLOSED = "closed"
const val EVENT_CLOSING = "closing"
const val EVENT_FAILURE = "failure"
const val EVENT_TEXT = "text"
const val EVENT_MESSAGE = "message"
const val EVENT_BYTES = "bytes"
const val EVENT_OPEN = "open"
const val EVENT_MAX_REBUILDS = "max_rebuilds"
private const val DEFAULT_EXIT_ON_CLOSE_TIMEOUT = 0L
@JvmStatic
fun onExit(reason: String?) {
val wsList = instances.mapNotNull { ref -> ref.get() }
if (wsList.isNotEmpty()) {
Log.d(TAG, "onExit ready")
wsList.forEach {
val r = {
Log.d(TAG, "onExit triggered after delayed")
if (it.isExitOnClose) it.close(CODE_CLOSE_NORMAL, reason)
}
AutoJs.instance.runtime.uiHandler.postDelayed(r, it.exitOnCloseTimeout)
}
}
}
}
}

View File

@@ -179,11 +179,12 @@ public class ScriptEngineService {
return mScriptEngineManager.stopAll();
}
public void stopAllAndToast() {
public int stopAllAndToast() {
int n = stopAll();
if (n > 0) {
mUiHandler.toast(mContext.getResources().getQuantityString(R.plurals.text_already_stop_n_scripts, n, n));
}
return n;
}
public Set<ScriptEngine> getEngines() {

View File

@@ -27,23 +27,18 @@ public class GlobalKeyObserver implements OnKeyListener, ShellKeyObserver.KeyLis
private boolean mVolumeDownFromShell, mVolumeDownFromAccessibility;
private boolean mVolumeUpFromShell, mVolumeUpFromAccessibility;
GlobalKeyObserver() {
public GlobalKeyObserver() {
AccessibilityService.Companion.getStickOnKeyObserver()
.addListener(this);
ShellKeyObserver observer = new ShellKeyObserver();
observer.setKeyListener(this);
InputEventObserver.getGlobal(GlobalAppContext.get()).addListener(observer);
InputEventObserver.initObserver(GlobalAppContext.get()).addListener(observer);
}
public static GlobalKeyObserver getSingleton() {
public static void initIfNeeded() {
if (sSingleton == null) {
sSingleton = new GlobalKeyObserver();
}
return sSingleton;
}
public static void init() {
if (Pref.isUseVolumeControlRunningEnabled()) getSingleton();
}
public void onVolumeUp() {

View File

@@ -1,5 +1,6 @@
package org.autojs.autojs.external.receiver;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
@@ -30,13 +31,22 @@ public class DynamicBroadcastReceivers {
private final Context mContext;
@SuppressLint("UnspecifiedRegisterReceiverFlag")
public DynamicBroadcastReceivers(Context context) {
mContext = context;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mContext.registerReceiver(mDefaultActionReceiver, createIntentFilter(StaticBroadcastReceiver.ACTIONS));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
mContext.registerReceiver(mDefaultActionReceiver, createIntentFilter(StaticBroadcastReceiver.ACTIONS), Context.RECEIVER_NOT_EXPORTED);
} else {
mContext.registerReceiver(mDefaultActionReceiver, createIntentFilter(StaticBroadcastReceiver.ACTIONS));
}
IntentFilter filter = createIntentFilter(StaticBroadcastReceiver.PACKAGE_ACTIONS);
filter.addDataScheme("package");
mContext.registerReceiver(mPackageActionReceiver, filter);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
mContext.registerReceiver(mPackageActionReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
} else {
mContext.registerReceiver(mPackageActionReceiver, filter);
}
}
}
@@ -120,6 +130,7 @@ public class DynamicBroadcastReceivers {
}
}
@SuppressLint("UnspecifiedRegisterReceiverFlag")
boolean register() {
if (actions.isEmpty())
return false;
@@ -128,7 +139,11 @@ public class DynamicBroadcastReceivers {
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(mContext);
broadcastManager.registerReceiver(receiver, intentFilter);
} else {
mContext.registerReceiver(receiver, intentFilter);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
mContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_NOT_EXPORTED);
} else {
mContext.registerReceiver(receiver, intentFilter);
}
}
Log.d(LOG_TAG, "register: " + actions);
return true;

View File

@@ -316,7 +316,7 @@ public class UpdateChecker {
neutralButton.setOnClickListener(v -> new MaterialDialog.Builder(context)
.title(R.string.text_prompt)
.content(R.string.prompt_add_ignored_version)
.negativeText(R.string.dialog_button_back)
.negativeText(R.string.dialog_button_cancel)
.positiveText(R.string.dialog_button_confirm)
.positiveColorRes(R.color.dialog_button_warn)
.onPositive((dPrompt, which) -> {
@@ -525,7 +525,7 @@ public class UpdateChecker {
this
.title(title)
.content(content)
.positiveText(R.string.text_back)
.positiveText(R.string.dialog_button_dismiss)
.cancelable(false);
}
@@ -540,7 +540,7 @@ public class UpdateChecker {
.content(R.string.text_getting_release_notes)
.neutralText(R.string.dialog_button_ignore_current_update)
.neutralColor(context.getColor(R.color.dialog_button_warn))
.negativeText(R.string.dialog_button_back)
.negativeText(R.string.dialog_button_cancel)
.negativeColor(context.getColor(R.color.dialog_button_default))
.positiveText(R.string.dialog_button_update_now)
.positiveColor(context.getColor(R.color.dialog_button_unavailable))

View File

@@ -157,9 +157,9 @@ public class DownloadManager {
.onNeutral((dialog, which) -> new MaterialDialog.Builder(context)
.title(R.string.text_prompt)
.content(R.string.text_download_interruption_warning)
.negativeText(R.string.text_back)
.negativeText(R.string.dialog_button_back)
.negativeColorRes(R.color.dialog_button_hint)
.positiveText(R.string.text_continue)
.positiveText(R.string.dialog_button_continue)
.positiveColorRes(R.color.dialog_button_caution)
.onPositive((d2, which2) -> {
dialog.getActionButton(DialogAction.POSITIVE).performClick();

View File

@@ -46,7 +46,7 @@ class DisplayOverOtherAppsPermission(override val context: Context) : Permission
}
val r = Runnable {
toggle()
ViewUtils.showToast(context, R.string.error_no_draw_overlays_permission)
ViewUtils.showToast(context, R.string.error_no_display_over_other_apps_permission)
}
if (Looper.myLooper() != Looper.getMainLooper()) {
Handler(Looper.getMainLooper()).post(r)

View File

@@ -206,7 +206,7 @@ public class DevPluginResponseHandler implements Handler {
private void copyDir(File fromDir, File toDir) throws FileNotFoundException {
toDir.mkdirs();
File[] files = fromDir.listFiles();
if (files == null || files.length == 0) {
if (files == null) {
return;
}
for (File file : files) {

View File

@@ -88,10 +88,10 @@ class DevPluginService(val context: Context) {
}
@AnyThread
fun connectToRemoteServer(host: String, clientModeItem: DrawerMenuDisposableItem?) = connectToRemoteServer(host, clientModeItem, false)
fun connectToRemoteServer(context: Context, host: String, clientModeItem: DrawerMenuDisposableItem?) = connectToRemoteServer(context, host, clientModeItem, false)
@AnyThread
fun connectToRemoteServer(host: String, clientModeItem: DrawerMenuDisposableItem?, ignoreExceptions: Boolean): Observable<JsonSocketClient> {
fun connectToRemoteServer(context: Context, host: String, clientModeItem: DrawerMenuDisposableItem?, ignoreExceptions: Boolean): Observable<JsonSocketClient> {
try {
var port = Port.PC_SERVER
var ip = host
@@ -101,7 +101,7 @@ class DevPluginService(val context: Context) {
ip = host.substring(0, i)
}
return Observable
.just(JsonSocketClient(this, ip, port))
.just(JsonSocketClient(this, context, ip, port))
.observeOn(Schedulers.newThread())
.doOnNext { jsonSocketClient ->
try {
@@ -179,9 +179,10 @@ class DevPluginService(val context: Context) {
}
}
@AnyThread // FIXME by SuperMonster003 as of Dec 29, 2021.
// FIXME by SuperMonster003 as of Dec 29, 2021.
// ! Would print double (may be even more times) the amount of
// ! messages on VSCode when multi connection were established.
@AnyThread
fun print(log: String?) {
jsonSocketClient?.writeLog(log)
jsonSocketServer?.writeLog(log)

View File

@@ -1,6 +1,7 @@
package org.autojs.autojs.pluginclient
import android.app.Activity
import android.content.Context
import android.text.util.Linkify
import android.util.Log
import androidx.annotation.MainThread
@@ -24,7 +25,7 @@ import java.net.SocketTimeoutException
import java.util.concurrent.Executors
class JsonSocketClient(service: DevPluginService?, host: String?, port: Int) : JsonSocket(service) {
class JsonSocketClient(service: DevPluginService?, private val ctx: Context, host: String?, port: Int) : JsonSocket(service) {
private val jsonSocketExecutor = Executors.newSingleThreadExecutor()
@@ -83,6 +84,8 @@ class JsonSocketClient(service: DevPluginService?, host: String?, port: Int) : J
}, HANDSHAKE_TIMEOUT.toLong())
}
override fun getContext() = ctx
private fun onHello(message: JsonObject) {
var currentVersion: String? = null
val requiredVersion = BuildConfig.VSCODE_EXT_REQUIRED_VERSION
@@ -127,7 +130,7 @@ class JsonSocketClient(service: DevPluginService?, host: String?, port: Int) : J
MaterialDialog.Builder(activity)
.title(activity.getString(R.string.text_connection_cannot_be_established))
.content(msg)
.positiveText(R.string.dialog_button_back)
.positiveText(R.string.dialog_button_dismiss)
.build()
.also {
it.contentView?.apply {

View File

@@ -37,9 +37,6 @@ object Pref {
if (key == key(R.string.key_guard_mode)) {
AccessibilityConfig.refreshUnintendedGuardState()
}
if (key == key(R.string.key_use_volume_control_record) || key == key(R.string.key_use_volume_control_running) && isUseVolumeControlRunningEnabled) {
GlobalKeyObserver.init()
}
}
init {
@@ -249,9 +246,15 @@ object Pref {
@JvmStatic
fun putBoolean(@KeyRes keyRes: Int, value: Boolean) = putBoolean(key(keyRes), value)
@JvmStatic
fun putBooleanSync(@KeyRes keyRes: Int, value: Boolean) = putBooleanSync(key(keyRes), value)
@JvmStatic
fun putBoolean(key: String?, value: Boolean) = sPref.edit().putBoolean(key, value).apply()
@JvmStatic
fun putBooleanSync(key: String?, value: Boolean) = sPref.edit().putBoolean(key, value).commit()
@JvmStatic
fun getBoolean(@KeyRes keyRes: Int, defValue: Boolean) = getBoolean(key(keyRes), defValue)
@@ -264,12 +267,24 @@ object Pref {
@JvmStatic
fun putInt(key: String?, value: Int) = sPref.edit().putInt(key, value).apply()
@JvmStatic
fun putIntSync(key: String?, value: Int) = sPref.edit().putInt(key, value).commit()
@JvmStatic
fun putFloat(key: String?, value: Float) = sPref.edit().putFloat(key, value).apply()
@JvmStatic
fun putFloatSync(key: String?, value: Float) = sPref.edit().putFloat(key, value).commit()
@JvmStatic
fun getInt(@KeyRes keyRes: Int, defValue: Int): Int = getInt(key(keyRes), defValue)
@JvmStatic
fun getInt(key: String?, defValue: Int): Int = sPref.getInt(key, defValue)
@JvmStatic
fun getFloat(key: String?, defValue: Float): Float = sPref.getFloat(key, defValue)
@JvmStatic
fun putLong(@KeyRes keyRes: Int, value: Long) = sPref.edit().putLong(key(keyRes), value).apply()

View File

@@ -22,6 +22,7 @@ import org.autojs.autojs.core.image.Colors;
import org.autojs.autojs.core.image.ImageWrapper;
import org.autojs.autojs.core.image.capture.ScreenCaptureRequester;
import org.autojs.autojs.core.looper.Loopers;
import org.autojs.autojs.core.web.WebSocket;
import org.autojs.autojs.engine.ScriptEngineService;
import org.autojs.autojs.lang.ThreadCompat;
import org.autojs.autojs.pio.PFiles;
@@ -39,11 +40,12 @@ import org.autojs.autojs.runtime.api.Files;
import org.autojs.autojs.runtime.api.Floaty;
import org.autojs.autojs.runtime.api.Images;
import org.autojs.autojs.runtime.api.Media;
import org.autojs.autojs.runtime.api.MlKitOCR;
import org.autojs.autojs.runtime.api.PaddleOCR;
import org.autojs.autojs.runtime.api.OcrMLKit;
import org.autojs.autojs.runtime.api.OcrPaddle;
import org.autojs.autojs.runtime.api.Plugins;
import org.autojs.autojs.runtime.api.ProcessShell;
import org.autojs.autojs.runtime.api.ScreenMetrics;
import org.autojs.autojs.runtime.api.ScriptToast;
import org.autojs.autojs.runtime.api.Sensors;
import org.autojs.autojs.runtime.api.Threads;
import org.autojs.autojs.runtime.api.Timers;
@@ -242,10 +244,13 @@ public class ScriptRuntime {
private final Images images;
@ScriptVariable
public final MlKitOCR mlKitOCR;
public final ScriptToast toast;
@ScriptVariable
public final PaddleOCR paddleOCR;
public final OcrMLKit ocrMLKit;
@ScriptVariable
public final OcrPaddle ocrPaddle;
private static WeakReference<Context> applicationContext;
private final Map<String, Object> mProperties = new ConcurrentHashMap<>();
@@ -281,8 +286,9 @@ public class ScriptRuntime {
media = new Media(context, this);
plugins = new Plugins(context, this);
mlKitOCR = new MlKitOCR();
paddleOCR = new PaddleOCR();
ocrMLKit = new OcrMLKit();
ocrPaddle = new OcrPaddle();
toast = new ScriptToast(context, this);
}
public void init() {
@@ -327,10 +333,6 @@ public class ScriptRuntime {
return accessibilityBridge;
}
public void toast(final String text) {
uiHandler.toast(text);
}
public void sleep(long millis) {
try {
Thread.sleep(millis);
@@ -536,6 +538,8 @@ public class ScriptRuntime {
}
});
ignoresException(() -> WebSocket.onExit("Triggered by " + ScriptRuntime.class.getSimpleName()));
// @Hint by 抠脚本人 on Jul 10, 2023.
// ! 清空无障碍事件.
ignoresException(AccessibilityService::clearAccessibilityEventCallback);
@@ -558,8 +562,8 @@ public class ScriptRuntime {
ignoresException(loopers::recycle);
ignoresException(this::recycleShell);
ignoresException(images::releaseScreenCapturer);
ignoresException(mlKitOCR::release);
ignoresException(paddleOCR::release);
ignoresException(ocrMLKit::release);
ignoresException(ocrPaddle::release);
ignoresException(sensors::unregisterAll);
ignoresException(timers::recycle);
ignoresException(ui::recycle);

View File

@@ -44,8 +44,8 @@ public abstract class AbstractShell {
this(false);
}
public AbstractShell(boolean root) {
this(null, root);
public AbstractShell(boolean isExecWithRoot) {
this(null, isExecWithRoot);
}
public AbstractShell(Context context, boolean isExecWithRoot) {
@@ -67,9 +67,9 @@ public abstract class AbstractShell {
public abstract void exit();
public void SetTouchDevice(int touchDevice) {
if (mTouchDevice > 0)
return;
mTouchDevice = touchDevice;
if (mTouchDevice <= 0) {
mTouchDevice = touchDevice;
}
}
public void SendEvent(int type, int code, int value) {

View File

@@ -219,8 +219,10 @@ class AppUtils {
enum class BroadcastShortForm(val shortName: String) {
INSPECT_LAYOUT_BOUNDS("inspect_layout_bounds"),
LAYOUT_BOUNDS("layout_bounds"),
BOUNDS("bounds"),
INSPECT_LAYOUT_HIERARCHY("inspect_layout_hierarchy"),
LAYOUT_HIERARCHY("layout_hierarchy"),
HIERARCHY("hierarchy"),
;

View File

@@ -1,5 +1,7 @@
package org.autojs.autojs.runtime.api;
import static android.content.Context.WINDOW_SERVICE;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@@ -7,6 +9,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
@@ -17,6 +20,7 @@ import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.Settings;
import android.view.Display;
import android.view.Surface;
import android.view.WindowManager;
import androidx.annotation.NonNull;
@@ -25,6 +29,8 @@ import androidx.annotation.Nullable;
import org.autojs.autojs.pio.PFiles;
import org.autojs.autojs.pio.UncheckedIOException;
import org.autojs.autojs.util.DeviceUtils;
import org.autojs.autojs.util.RomUtils;
import org.autojs.autojs.util.RomUtils.Brand;
import org.autojs.autojs6.R;
import java.net.NetworkInterface;
@@ -36,6 +42,7 @@ import ezy.assist.compat.SettingsCompat;
/**
* Created by Stardust on 2017/12/2.
* Modified by SuperMonster003 as of Jan 1, 2022.
*/
public class Device {
@@ -87,6 +94,10 @@ public class Device {
public static String imei;
public final Manufacturers manufacturers;
public final RomUtils roms;
public final Brand brands;
private final Context mContext;
private final Vibrator mVibrator;
private PowerManager.WakeLock mWakeLock;
@@ -95,6 +106,9 @@ public class Device {
public Device(Context context) {
mContext = context;
mVibrator = context.getSystemService(Vibrator.class);
manufacturers = new Manufacturers();
roms = RomUtils.INSTANCE;
brands = Brand.INSTANCE;
imei = DeviceUtils.getIMEI(context);
serial = DeviceUtils.getSerial();
}
@@ -237,7 +251,7 @@ public class Device {
}
public boolean isScreenOn() {
return ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getState() == Display.STATE_ON;
return getDefaultDisplay().getState() == Display.STATE_ON;
}
public void wakeUpIfNeeded() {
@@ -303,6 +317,30 @@ public class Device {
mVibrator.cancel();
}
public int getOrientation() {
if (isScreenLandscape()) {
return Configuration.ORIENTATION_LANDSCAPE;
}
if (isScreenPortrait()) {
return Configuration.ORIENTATION_PORTRAIT;
}
return Configuration.ORIENTATION_UNDEFINED;
}
public int getRotation() {
return getDefaultDisplay().getRotation();
}
public boolean isScreenPortrait() {
int rotation = getRotation();
return rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180;
}
public boolean isScreenLandscape() {
int rotation = getRotation();
return rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270;
}
private void checkWriteSettingsPermission() {
if (SettingsCompat.canWriteSettings(mContext)) {
return;
@@ -311,12 +349,16 @@ public class Device {
throw new SecurityException(mContext.getString(R.string.error_no_write_settings_permission));
}
private void checkReadPhoneStatePermission() {
if (mContext.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
public void ensureReadPhoneStatePermission() {
if (!hasReadPhoneStatePermission()) {
throw new SecurityException(mContext.getString(R.string.error_no_read_phone_state_permission));
}
}
public boolean hasReadPhoneStatePermission() {
return mContext.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED;
}
// just to avoid warning of null pointer to make android studio happy..
@NonNull
@SuppressWarnings("unchecked")
@@ -412,4 +454,140 @@ public class Device {
'}';
}
private Display getDefaultDisplay() {
return ((WindowManager) getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
}
public boolean isManufacturer(String manufacturer) {
return manufacturers.is(manufacturer);
}
public class Manufacturers {
private boolean is(String manufacturer) {
return manufacturer.equalsIgnoreCase(Build.MANUFACTURER);
}
/**
* HTC.
*/
public boolean isHtc() {
return is("htc");
}
/**
* LG.
*/
public boolean isLG() {
return is("lg");
}
/**
* 一加.
*/
public boolean isOnePlus() {
return is("oneplus");
}
/**
* 三星.
*/
public boolean isSamsung() {
return is("samsung");
}
/**
* 中兴.
*/
public boolean isZte() {
return is("zte");
}
/**
* 乐视.
*/
public boolean isLetv() {
return is("letv");
}
/**
* 华为.
*/
public boolean isHuawei() {
return is("huawei") || isNova() || isHonor();
}
/**
* Huawei (华为) Nova.
*/
public boolean isNova() {
return is("nova");
}
/**
* Huawei (华为) 荣耀.
*/
public boolean isHonor() {
return is("honor");
}
/**
* 小米.
*/
public boolean isXiaomi() {
return is("xiaomi") || brands.isRedmi() || brands.isMiMix();
}
/**
* 欧珀.
*/
public boolean isOppo() {
return is("oppo");
}
/**
* 索尼.
*/
public boolean isSony() {
return is("sony") || brands.isXperia();
}
/**
* 维沃.
*/
public boolean isVivo() {
return is("vivo");
}
/**
* 联想.
*/
public boolean isLenovo() {
return is("lenovo");
}
/**
* 酷派.
* 宇龙计算机通信科技 (深圳) 有限公司.
*/
public boolean isCoolpad() {
return is("yulong");
}
/**
* 锤子.
*/
public boolean isSmartisan() {
return is("smartisan");
}
/**
* 魅族.
*/
public boolean isMeizu() {
return is("meizu");
}
}
}

View File

@@ -133,7 +133,7 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
return;
ensureHandler();
mLoopers.waitWhenIdle(true);
mTouchObserver = new TouchObserver(InputEventObserver.getGlobal(mContext));
mTouchObserver = new TouchObserver(InputEventObserver.initObserver(mContext));
mTouchObserver.setOnTouchEventListener(this);
mTouchObserver.observe();
}

View File

@@ -18,6 +18,7 @@ import org.autojs.autojs.runtime.exception.ScriptInterruptedException
import org.autojs.autojs.tool.UiHandler
import org.autojs.autojs.util.ViewUtils.setViewMeasure
import org.autojs.autojs6.R
import java.lang.Exception
import java.util.concurrent.CopyOnWriteArraySet
/**
@@ -73,6 +74,13 @@ class Floaty(private val mUiHandler: UiHandler, private val mRuntime: ScriptRunt
@ScriptInterface
fun requestPermission() = mDisplayOverOtherAppsPerm.request()
@ScriptInterface
fun ensurePermission() {
if (!hasPermission()) {
throw Exception(mContext.getString(R.string.error_no_display_over_other_apps_permission))
}
}
@Synchronized
fun closeAll() {
mWindows.apply {

View File

@@ -0,0 +1,74 @@
package org.autojs.autojs.runtime.api
import java.lang.Exception
interface IPermissionToggleable {
val description: String
fun has(): Boolean
fun toggle(forcible: Boolean = false) {
try {
if (!has()) request(forcible) else revoke()
} catch (e: Exception) {
when (e) {
is PermissionRequestException -> {
throw PermissionToggleException("$description can't ..., may be not able to request")
}
is PermissionRevokeException -> {
throw PermissionToggleException("$description can't ..., may be not able to revoke")
}
else -> throw e
}
}
}
fun request(forcible: Boolean) {
if (forcible) {
try {
revoke()
} catch (e: PermissionRevokeException) {
throw PermissionRequestException("$description can't ...")
}
}
request()
}
fun request() {
try {
config()
} catch (e: PermissionConfigException) {
throw PermissionRequestException("$description can't ...")
}
}
fun requestIfNeeded(forcible: Boolean = false) {
if (!has()) request(forcible)
}
fun revoke() {
try {
config()
} catch (e: PermissionConfigException) {
throw PermissionRevokeException("$description can't ...")
}
}
fun revokeIfNeeded() {
if (has()) revoke()
}
fun config() {
throw PermissionConfigException("$description can't ...")
}
class PermissionRequestException(e: String) : Exception(e)
class PermissionRevokeException(e: String) : Exception(e)
class PermissionConfigException(e: String) : Exception(e)
class PermissionToggleException(e: String) : Exception(e)
}

View File

@@ -11,7 +11,7 @@ import org.autojs.autojs.core.image.ImageWrapper
* Created by SuperMonster003 on Mar 18, 2023.
*/
// @Reference to TonyJiangWJ/Auto.js (https://github.com/TonyJiangWJ/Auto.js) on Mar 18, 2023.
class MlKitOCR {
class OcrMLKit {
private var recognizer: TextRecognizer? = null

View File

@@ -6,6 +6,7 @@ import android.util.Log;
import com.baidu.paddle.lite.ocr.OcrResult;
import com.baidu.paddle.lite.ocr.Predictor;
import org.autojs.autojs.app.GlobalAppContext;
import org.autojs.autojs.concurrent.VolatileDispose;
import org.autojs.autojs.core.image.ImageWrapper;
@@ -17,7 +18,7 @@ import java.util.List;
* @author TonyJiangWJ
* @since 2023-08-06
*/
public class PaddleOCR {
public class OcrPaddle {
private final Predictor mPredictor = new Predictor();
public synchronized boolean init(boolean useSlim) {

View File

@@ -0,0 +1,49 @@
package org.autojs.autojs.runtime.api
import android.content.Context
import android.content.Intent
import org.autojs.autojs.util.RomUtils
class Permissions(private val context: Context) {
var postNotifications: IPermissionToggleable? = null
var notificationAccess: IPermissionToggleable? = null
var usageStatesAccess: IPermissionToggleable? = null
var ignoreBatteryOptimizations: IPermissionToggleable? = null
var backgroundStart = object : IPermissionToggleable {
override val description = "后台弹出界面 / Start in background"
override fun has() = RomUtils.isBackgroundStartGranted(context)
override fun config() = when {
RomUtils.isMiui() -> {
Intent("miui.intent.action.APP_PERM_EDITOR").apply {
setClassName(
"com.miui.securitycenter",
"com.miui.permcenter.permissions.PermissionsEditorActivity",
)
putExtra("extra_pkgname", context.packageName)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.let { context.startActivity(it) }
}
else -> super.config()
}
}
var displayOverOtherApps: IPermissionToggleable? = null
var writeSystemSettings: IPermissionToggleable? = null
var writeSecuritySettings: IPermissionToggleable? = null
var projectMediaAccess: IPermissionToggleable? = null
val isBackgroundStartGranted: Boolean
get() = RomUtils.isBackgroundStartGranted(context)
// TODO by SuperMonster003 on Aug 28, 2023.
/* permissions.postNotifications */
/* permissions.notificationAccess */
/* permissions.usageStatesAccess */
/* permissions.ignoreBatteryOptimizations */
/* permissions.backgroundStart */
/* permissions.displayOverOtherApps */
/* permissions.writeSystemSettings */
/* permissions.writeSecuritySettings */
/* permissions.projectMediaAccess */
}

View File

@@ -1,15 +1,18 @@
package org.autojs.autojs.runtime.api
import android.app.Activity
import android.content.res.Configuration
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.content.res.Resources
import android.graphics.Point
import android.util.DisplayMetrics
import android.view.Display
import android.view.Surface
import android.view.Surface.ROTATION_0
import android.view.WindowManager
/**
* Created by Stardust on 2017/4/26.
*/
@Suppress("unused")
class ScreenMetrics(private var designWidth: Int, private var designHeight: Int) {
constructor() : this(0, 0)
@@ -21,79 +24,111 @@ class ScreenMetrics(private var designWidth: Int, private var designHeight: Int)
@JvmOverloads
fun scaleX(x: Int, width: Int = designWidth) = when {
width == 0 || activity == null -> x
width == 0 || !isInitialized -> x
else -> x * deviceScreenWidth / width
}
@JvmOverloads
fun scaleY(y: Int, height: Int = designHeight) = when {
height == 0 || activity == null -> y
height == 0 || !isInitialized -> y
else -> y * deviceScreenHeight / height
}
@JvmOverloads
fun rescaleX(x: Int, width: Int = designWidth) = when {
width == 0 || activity == null -> x
width == 0 || !isInitialized -> x
else -> x * width / deviceScreenWidth
}
@JvmOverloads
fun rescaleY(y: Int, height: Int = designHeight) = when {
height == 0 || activity == null -> y
height == 0 || !isInitialized -> y
else -> y * height / deviceScreenHeight
}
companion object {
private val metrics: DisplayMetrics = DisplayMetrics()
private var mWindowManager: WindowManager? = null
private var mResources: Resources? = null
private var activity: Activity? = null
private var isInitialized = false
@Suppress("DEPRECATION")
private val defaultDisplay: Display?
get() = activity?.windowManager?.defaultDisplay
@JvmStatic
val rotation: Int
get() = defaultDisplay?.rotation ?: 0
get() = mWindowManager?.defaultDisplay?.rotation ?: ROTATION_0
@JvmStatic
val orientation: Int
get() = mResources?.configuration?.orientation ?: ORIENTATION_PORTRAIT
@JvmStatic
val isScreenPortrait: Boolean
get() = orientation == ORIENTATION_PORTRAIT
@JvmStatic
val isScreenLandscape: Boolean
get() = orientation == ORIENTATION_LANDSCAPE
@JvmStatic
@Suppress("DEPRECATION")
val deviceScreenWidth: Int
get() = toOriAwarePoint(metrics.widthPixels, metrics.heightPixels).x
get() {
mResources?.displayMetrics?.widthPixels?.takeIf { it > 0 }?.let { return it }
@JvmStatic
val deviceScreenHeight: Int
get() = toOriAwarePoint(metrics.widthPixels, metrics.heightPixels).y
val metricsLegacy = DisplayMetrics()
val display = mWindowManager?.defaultDisplay?.apply { getRealMetrics(metricsLegacy) }
metricsLegacy.widthPixels.takeIf { it > 0 }?.let { return it }
@JvmStatic
val deviceScreenDensity: Int
get() = metrics.densityDpi
display?.width?.takeIf { it > 0 }?.let { return it }
@JvmStatic
fun initIfNeeded(activity: Activity) {
this.activity ?: let {
this.activity = activity
@Suppress("DEPRECATION")
defaultDisplay?.getRealMetrics(this.metrics)
return 0
}
@JvmStatic
@Suppress("DEPRECATION")
val deviceScreenHeight: Int
get() {
mResources?.displayMetrics?.heightPixels?.takeIf { it > 0 }?.let { return it }
val metricsLegacy = DisplayMetrics()
val display = mWindowManager?.defaultDisplay?.apply { getRealMetrics(metricsLegacy) }
metricsLegacy.heightPixels.takeIf { it > 0 }?.let { return it }
display?.height?.takeIf { it > 0 }?.let { return it }
return 0
}
@JvmStatic
@Suppress("DEPRECATION")
val deviceScreenDensity: Int
get() {
val metricsLegacy = DisplayMetrics()
mWindowManager?.defaultDisplay?.apply { getRealMetrics(metricsLegacy) }
return metricsLegacy.densityDpi
}
@JvmStatic
fun init(activity: Activity) {
mWindowManager = activity.windowManager
mResources = activity.resources
isInitialized = true
}
@JvmStatic
fun isScreenLandscape() = listOf(Surface.ROTATION_90, Surface.ROTATION_270).contains(rotation)
private fun toOriAwarePoint(a: Int, b: Int) = arrayOf(minOf(a, b), maxOf(a, b))
.apply { if (isScreenLandscape()) reverse() }
.apply { if (isScreenLandscape) reverse() }
.let { Point(it[0], it[1]) }
@JvmStatic
fun getOrientationAwareScreenWidth(orientation: Int) = when (orientation) {
Configuration.ORIENTATION_LANDSCAPE -> deviceScreenHeight
ORIENTATION_LANDSCAPE -> deviceScreenHeight
else -> deviceScreenWidth
}
@JvmStatic
fun getOrientationAwareScreenHeight(orientation: Int) = when (orientation) {
Configuration.ORIENTATION_LANDSCAPE -> deviceScreenWidth
ORIENTATION_LANDSCAPE -> deviceScreenWidth
else -> deviceScreenHeight
}

View File

@@ -0,0 +1,96 @@
package org.autojs.autojs.runtime.api;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import android.widget.Toast;
import org.autojs.autojs.runtime.ScriptRuntime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by SuperMonster003 on Aug 3, 2023.
*/
public class ScriptToast {
private static final String TAG = ScriptToast.class.getSimpleName();
private final Context mContext;
private final ScriptRuntime mScriptRuntime;
private final Handler mUiHandler;
private static final ConcurrentHashMap<Toast, ScriptRuntime> pool = new ConcurrentHashMap<>();
public ScriptToast(Context context, ScriptRuntime runtime) {
mContext = context;
mScriptRuntime = runtime;
mUiHandler = mScriptRuntime.uiHandler;
}
// @Caution by SuperMonster003 on Oct 11, 2022.
// ! android.widget.ScriptToast.makeText() doesn't work well on Android API Level 28 (Android 9) [P].
// ! There hasn't been a solution for this so far.
// ! Tested devices:
// ! 1. SONY XPERIA XZ1 Compact (G8441)
// ! 2. Android Studio AVD (Android 9.0 x86)
public void makeToast(String msg, boolean isLong, boolean isForcible) {
synchronized (ScriptToast.class) {
if (isForcible) {
dismissAll();
}
Toast toast = Toast.makeText(mContext, msg, isLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT);
Log.d(TAG + " new toast", toast.toString());
toast.show();
Log.d(TAG + " before put", pool.toString());
pool.put(toast, mScriptRuntime);
Log.d(TAG + " after put", pool.toString());
addCallback(toast);
}
}
private void addCallback(Toast toast) {
// FIXME by SuperMonster003 on Aug 3, 2023.
// ! A more graceful way is needed.
// @Hint by SuperMonster003 on Aug 3, 2023.
// ! It is proved that Toast.Callback for Android API Level 30+
// ! was not a good replacement.
mUiHandler.postDelayed(() -> dismiss(toast), getAccumulatedDuration());
}
private long getAccumulatedDuration() {
var sum = 200L;
for (Map.Entry<Toast, ScriptRuntime> entry : pool.entrySet()) {
Toast toast = entry.getKey();
sum += toast.getDuration() == Toast.LENGTH_SHORT ? 2_000L : 3_500L;
}
return sum;
}
private void dismiss(Toast aim) {
Log.d(TAG + " before dismiss", pool.toString());
for (Map.Entry<Toast, ScriptRuntime> entry : pool.entrySet()) {
Toast toast = entry.getKey();
ScriptRuntime scriptRuntime = entry.getValue();
if (scriptRuntime == mScriptRuntime && toast == aim) {
toast.cancel();
pool.remove(toast);
}
}
Log.d(TAG + " after dismiss", pool.toString());
}
public void dismissAll() {
Log.d(TAG + " before disAll", pool.toString());
for (Map.Entry<Toast, ScriptRuntime> entry : pool.entrySet()) {
Toast toast = entry.getKey();
ScriptRuntime scriptRuntime = entry.getValue();
if (scriptRuntime == mScriptRuntime) {
toast.cancel();
pool.remove(toast);
}
}
Log.d(TAG + " after disAll", pool.toString());
}
}

View File

@@ -10,7 +10,6 @@ import androidx.annotation.NonNull;
import org.autojs.autojs.core.eventloop.EventEmitter;
import org.autojs.autojs.core.looper.Loopers;
import org.autojs.autojs.pref.Language;
import org.autojs.autojs.runtime.ScriptBridges;
import org.autojs.autojs.runtime.ScriptRuntime;
import org.autojs.autojs.tool.MapBuilder;
@@ -22,8 +21,10 @@ import java.util.Set;
/**
* Created by Stardust on 2018/2/5.
* Modified by SuperMonster003 as of Dec 5, 2021.
* Modified by aiselp as of Jun 10, 2023.
*/
public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
public class Sensors extends EventEmitter {
public class SensorEventEmitter extends EventEmitter implements SensorEventListener {
@@ -83,6 +84,13 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
private final ScriptBridges mScriptBridges;
private final SensorEventEmitter mNoOpSensorEventEmitter;
private final ScriptRuntime mScriptRuntime;
private final Loopers.AsyncTask mAsyncTask = new Loopers.AsyncTask("Sensors") {
@Override
public boolean onFinish(@NonNull Loopers loopers) {
return !mSensorEventEmitters.isEmpty();
}
};
public Sensors(Context context, ScriptRuntime runtime) {
super(runtime.bridges);
@@ -90,7 +98,7 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
mScriptBridges = runtime.bridges;
mNoOpSensorEventEmitter = new SensorEventEmitter(runtime.bridges);
mScriptRuntime = runtime;
runtime.loopers.addLooperQuitHandler(this);
runtime.loopers.addAsyncTask(mAsyncTask);
}
public SensorEventEmitter register(String sensorName) {
@@ -122,13 +130,8 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
return emitter;
}
@Override
public boolean shouldQuit() {
return mSensorEventEmitters.isEmpty();
}
public Sensor getSensor(String sensorName) {
sensorName = sensorName.toUpperCase(Language.getPrefLanguage().getLocale());
sensorName = sensorName.toUpperCase();
Integer type = SENSORS.get(sensorName);
type = type == null ? getSensorTypeByReflect(sensorName) : type;
return type == null ? null : mSensorManager.getDefaultSensor(type);
@@ -159,6 +162,6 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
}
mSensorEventEmitters.clear();
}
mScriptRuntime.loopers.removeLooperQuitHandler(this);
mScriptRuntime.loopers.removeAsyncTask(mAsyncTask);
}
}

View File

@@ -0,0 +1,20 @@
package org.autojs.autojs.runtime.api;
import android.content.Context;
import org.autojs.autojs.util.RomUtils;
public class Services {
private final Context mContext;
public Services(Context context) {
mContext = context;
}
// TODO by SuperMonster003 on Aug 28, 2023.
/* services.a11y (a11y) */
/* services.foregroundService */
}

View File

@@ -0,0 +1,32 @@
package org.autojs.autojs.runtime.api;
import android.content.Context;
import org.autojs.autojs.util.RomUtils;
public class Settings {
private final Context mContext;
public Settings(Context context) {
mContext = context;
}
// TODO by SuperMonster003 on Aug 28, 2023.
/* settings.floatingButton */
/* settings.clientMode */
/* settings.serverMode */
/* settings.autoNightMode */
/* settings.nightMode */
/* settings.themeColor */
/* settings.language */
/* settings.documentationSource */
/* settings.isKeepScreenOnWhenInForeground */
/* settings.isShowDocumentationLauncherIcon */
/* settings.isShowHiddenFilesAndFolders */
/* settings.workingDirectory */
/* settings.forcibleRootCheck */
/* ... ... */
}

View File

@@ -1,28 +1,73 @@
package org.autojs.autojs.runtime.api
import org.autojs.autojs.concurrent.VolatileDispose
import org.autojs.autojs.core.looper.Loopers
import org.autojs.autojs.core.looper.MainThreadProxy
import org.autojs.autojs.core.looper.TimerThread
import org.autojs.autojs.runtime.ScriptRuntime
import org.autojs.autojs.runtime.exception.ScriptInterruptedException
import org.autojs.autojs.util.StringUtils.str
import org.autojs.autojs6.R
import org.mozilla.javascript.BaseFunction
import org.mozilla.javascript.Context
import java.util.concurrent.Executors
import java.util.concurrent.ThreadFactory
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.locks.ReentrantLock
/**
* Created by Stardust on 2017/12/3.
* Modified by aiselp as of Jun 10, 2023.
* Modified by SuperMonster003 as of Aug 28, 2023.
*/
class Threads(private val mRuntime: ScriptRuntime) {
private val mThreads = HashSet<Thread>()
private val mMainThreadProxy = MainThreadProxy(Thread.currentThread(), mRuntime)
private var mSpawnCount = 0
private var mTaskCount = AtomicLong(0)
private var mExit = false
private val looperTask = Loopers.AsyncTask("AsyncTaskThreadPool")
private val threadPool = Executors.newFixedThreadPool(20, ThreadFactory {
val thread = Thread(fun() {
Context.enter()
try {
it.run()
} finally {
Context.exit()
}
})
thread.name = mainThread.name + " (AsyncThread)"
thread
})
val mainThread: Thread = Thread.currentThread()
fun currentThread(): Any = Thread.currentThread().let { thread ->
if (thread === mainThread) mMainThreadProxy else thread
fun currentThread(): Any {
val thread = Thread.currentThread()
return if (thread === mainThread) mMainThreadProxy else thread
}
fun runTaskForThreadPool(runnable: BaseFunction) {
if (mTaskCount.addAndGet(1) == 1L) mRuntime.loopers.addAsyncTask(looperTask)
threadPool.execute {
try {
runnable.call(
Context.getCurrentContext(), runnable.parentScope, runnable,
emptyArray()
)
} catch (e: Throwable) {
if (!ScriptInterruptedException.causedByInterrupted(e)) {
mRuntime.console.error("$this: ", e)
}
} finally {
if (mTaskCount.addAndGet(-1) == 0L) {
mRuntime.loopers.removeAsyncTask(looperTask)
}
}
}
}
fun start(runnable: Runnable): TimerThread {
@@ -40,14 +85,11 @@ class Threads(private val mRuntime: ScriptRuntime) {
}
private fun createThread(runnable: Runnable): TimerThread {
val millis = mRuntime.timers.maxCallbackUptimeMillisForAllThreads
return object : TimerThread(mRuntime, millis, runnable) {
return object : TimerThread(mRuntime, runnable) {
override fun onExit() {
synchronized(mThreads) { mThreads.remove(currentThread()) }
super.onExit()
}
}
}
@@ -60,6 +102,7 @@ class Threads(private val mRuntime: ScriptRuntime) {
fun lock() = ReentrantLock()
fun shutDownAll() {
threadPool.shutdownNow()
synchronized(mThreads) {
mThreads.apply {
forEach { it.interrupt() }

View File

@@ -1,36 +1,33 @@
package org.autojs.autojs.runtime.api;
import android.os.Looper;
import android.os.SystemClock;
import org.autojs.autojs.concurrent.VolatileBox;
import org.autojs.autojs.core.looper.Timer;
import org.autojs.autojs.core.looper.TimerThread;
import org.autojs.autojs.runtime.ScriptRuntime;
/**
* Created by Stardust on 2017/7/21.
* Modified by SuperMonster003 as of May 26, 2022.
* Modified by aiselp as of Jun 10, 2023.
*/
public class Timers {
private final VolatileBox<Long> mMaxCallbackUptimeMillisForAllThreads = new VolatileBox<>(0L);
private final Threads mThreads;
private final Timer mMainTimer;
private final Timer mUiTimer;
private static final String LOG_TAG = "Timers";
// private VolatileBox<Long> mMaxCallbackUptimeMillisForAllThreads = new VolatileBox<>(0L);
private Threads mThreads;
private Timer mUiTimer;
private ScriptRuntime mRuntime;
public Timers(ScriptRuntime runtime) {
mMainTimer = new Timer(runtime, mMaxCallbackUptimeMillisForAllThreads);
mUiTimer = new Timer(runtime, mMaxCallbackUptimeMillisForAllThreads, Looper.getMainLooper());
mUiTimer = new Timer(runtime, Looper.getMainLooper());
mThreads = runtime.threads;
mRuntime = runtime;
}
public Timer getMainTimer() {
return mMainTimer;
}
VolatileBox<Long> getMaxCallbackUptimeMillisForAllThreads() {
return mMaxCallbackUptimeMillisForAllThreads;
return mRuntime.loopers.getTimer();
}
public Timer getTimerForCurrentThread() {
@@ -39,19 +36,27 @@ public class Timers {
public Timer getTimerForThread(Thread thread) {
if (thread == mThreads.getMainThread()) {
return mMainTimer;
return mRuntime.loopers.getTimer();
}
Timer timer = TimerThread.getTimerForThread(thread);
if (timer == null && Looper.myLooper() == Looper.getMainLooper()) {
return mUiTimer;
}
return timer;
if (timer == null) {
return mRuntime.loopers.getTimer();
} else {
return timer;
}
}
public int setTimeout(Object callback, long delay, Object... args) {
return getTimerForCurrentThread().setTimeout(callback, delay, args);
}
public int setTimeout(Object callback) {
return setTimeout(callback, 1);
}
public boolean clearTimeout(int id) {
return getTimerForCurrentThread().clearTimeout(id);
}
@@ -60,6 +65,10 @@ public class Timers {
return getTimerForCurrentThread().setInterval(listener, interval, args);
}
public int setInterval(Object listener) {
return setInterval(listener, 1);
}
public boolean clearInterval(int id) {
return getTimerForCurrentThread().clearInterval(id);
}
@@ -72,17 +81,8 @@ public class Timers {
return getTimerForCurrentThread().clearImmediate(id);
}
public boolean hasPendingCallbacks() {
// 如果是脚本主线程则检查所有子线程中的定时回调。mFutureCallbackUptimeMillis用来记录所有子线程中定时最久的一个。
if (mThreads.getMainThread() == Thread.currentThread()) {
return mMaxCallbackUptimeMillisForAllThreads.get() > SystemClock.uptimeMillis();
}
// 否则检查当前线程的定时回调
return getTimerForCurrentThread().hasPendingCallbacks();
}
public void recycle() {
mMainTimer.removeAllCallbacks();
mRuntime.loopers.getTimer().removeAllCallbacks();
}
}

View File

@@ -34,8 +34,8 @@ public class ThemeColorToolbar extends Toolbar implements ThemeColorMutable {
private void init() {
ThemeColorManager.add(this);
setContentInsetStartWithNavigation(getContext().getResources().getDimensionPixelSize(R.dimen.toolbar_content_inset_start_with_navigation));
setTitleTextAppearance(getContext(), R.style.TextAppearanceMainTitle);
}
@Override

View File

@@ -91,7 +91,7 @@ public class CodeGenerateDialog extends AppLevelThemeDialogBuilder {
DialogUtils.showDialog(builder
.title(R.string.text_generated_code)
.content(code)
.negativeText(R.string.dialog_button_back)
.negativeText(R.string.dialog_button_cancel)
.positiveText(R.string.dialog_button_copy)
.onPositive(((dialog, which) -> {
ClipboardUtils.setClip(mServiceContext, code);
@@ -102,7 +102,7 @@ public class CodeGenerateDialog extends AppLevelThemeDialogBuilder {
DialogUtils.showDialog(builder
.title(R.string.text_prompt)
.content(R.string.text_failed_to_generate)
.positiveText(R.string.dialog_button_back)
.positiveText(R.string.dialog_button_dismiss)
.build());
}
}

View File

@@ -4,6 +4,7 @@ import android.content.Context;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import com.afollestad.materialdialogs.MaterialDialog;
@@ -36,6 +37,7 @@ public class NotAskAgainDialog extends MaterialDialog {
checkBoxPrompt(context.getString(R.string.text_do_not_show_again), false, (buttonView, isChecked) -> setRemindState(!isChecked));
}
@Nullable
public MaterialDialog show() {
return mRemind ? super.show() : null;
}

View File

@@ -162,7 +162,7 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc
MaterialDialog.Builder(this)
.title(R.string.text_prompt)
.content(R.string.edit_exit_without_save_warn)
.neutralText(R.string.text_back)
.neutralText(R.string.dialog_button_back)
.negativeText(R.string.text_exit_directly)
.negativeColorRes(R.color.dialog_button_caution)
.positiveText(R.string.text_save_and_exit)

View File

@@ -232,7 +232,7 @@ public class EditorMenu {
// .onNeutral((dialog, which) -> {
// // Hint dialog.
// })
.negativeText(R.string.dialog_button_back)
.negativeText(R.string.dialog_button_cancel)
.onNegative((dialog, which) -> dialog.dismiss())
.positiveText(R.string.dialog_button_confirm)
.onPositive((dialog, which) -> dialog.dismiss())

View File

@@ -8,6 +8,7 @@ import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.text.TextUtils
@@ -167,9 +168,14 @@ open class EditorView : FrameLayout, OnHintClickListener, ClickCallback, Toolbar
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
@SuppressLint("UnspecifiedRegisterReceiverFlag")
override fun onAttachedToWindow() {
super.onAttachedToWindow()
context.registerReceiver(mOnRunFinishedReceiver, IntentFilter(ACTION_ON_EXECUTION_FINISHED))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(mOnRunFinishedReceiver, IntentFilter(ACTION_ON_EXECUTION_FINISHED), Context.RECEIVER_NOT_EXPORTED)
} else {
context.registerReceiver(mOnRunFinishedReceiver, IntentFilter(ACTION_ON_EXECUTION_FINISHED))
}
(context as? HostActivity)?.backPressedObserver?.registerHandler(mFunctionsKeyboardHelper)
}
@@ -595,7 +601,7 @@ open class EditorView : FrameLayout, OnHintClickListener, ClickCallback, Toolbar
private fun showErrorMessage(msg: String) {
Snackbar.make(this@EditorView, context.getString(R.string.text_error) + ": " + msg, Snackbar.LENGTH_LONG)
.setAction(R.string.text_detail) { LogActivity.launch(context) }
.setAction(R.string.text_details) { LogActivity.launch(context) }
.show()
}

View File

@@ -15,7 +15,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
/**
* Created by Stardust on 2017/5/1.
*/
public class FloatyService extends Service {
private static final CopyOnWriteArraySet<FloatyWindow> windows = new CopyOnWriteArraySet<>();

View File

@@ -11,7 +11,6 @@ import org.opencv.core.Size;
/**
* Created by Stardust on 2017/5/1.
*/
public abstract class FloatyWindow {
private WindowManager mWindowManager;
private FloatyService mFloatyService;

View File

@@ -7,7 +7,6 @@ import androidx.annotation.Nullable;
/**
* Created by Stardust on 2017/4/19.
*/
public interface ResizableExpandableFloaty {

View File

@@ -17,7 +17,6 @@ import org.autojs.autojs6.R;
/**
* Created by Stardust on 2017/4/18.
*/
public class ResizableExpandableFloatyWindow extends FloatyWindow {
private static final int INITIAL_WINDOW_PARAM_FLAG = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;

View File

@@ -6,7 +6,6 @@ import android.widget.ImageView;
/**
* Created by Stardust on 2017/4/30.
*/
public interface ResizableFloaty {
View inflateView(FloatyService floatyService, ResizableFloatyWindow service);

View File

@@ -17,7 +17,6 @@ import org.autojs.autojs6.R;
/**
* Created by Stardust on 2017/4/30.
*/
public class ResizableFloatyWindow extends FloatyWindow {
private View mView;

View File

@@ -7,7 +7,6 @@ import java.util.Stack;
/**
* Created by Stardust on 2017/3/11.
*/
public class ViewStack {
public interface CurrentViewSetter {

View File

@@ -7,7 +7,6 @@ import android.view.WindowManager;
/**
* Created by Stardust on 2017/4/18.
*/
public interface WindowBridge {
int getX();

View File

@@ -10,7 +10,6 @@ import org.autojs.autojs.ui.enhancedfloaty.WindowBridge;
/**
* Created by Stardust on 2017/4/18.
*/
public class DragGesture extends GestureDetector.SimpleOnGestureListener {
protected WindowBridge mWindowBridge;

View File

@@ -13,7 +13,6 @@ import org.autojs.autojs.ui.enhancedfloaty.WindowBridge;
/**
* Created by Stardust on 2017/4/18.
*/
public class ResizeGesture extends GestureDetector.SimpleOnGestureListener {
public static ResizeGesture enableResize(View resizer, @Nullable View resizableView, WindowBridge windowBridge) {

View File

@@ -11,7 +11,6 @@ import android.view.WindowManager;
/**
* Created by Stardust on 2017/3/10.
*/
public class FloatingWindowPermissionUtil {
public static void goToFloatingWindowPermissionSettingIfNeeded(Context context) {

View File

@@ -2,6 +2,7 @@ package org.autojs.autojs.ui.floating;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.text.TextUtils;
import android.view.ContextThemeWrapper;
import android.view.View;
@@ -43,6 +44,7 @@ import org.autojs.autojs6.databinding.CircularActionMenuBinding;
import org.greenrobot.eventbus.EventBus;
import org.jdeferred.Deferred;
import org.jdeferred.impl.DeferredObject;
import org.jetbrains.annotations.NotNull;
import java.text.MessageFormat;
@@ -166,9 +168,7 @@ public class CircularMenu implements Recorder.OnStateChangedListener, LayoutInsp
});
binding.stopAllScripts.setOnClickListener(v -> {
mWindow.collapse();
if (AutoJs.getInstance().getScriptEngineManager().getEngines().size() > 0) {
AutoJs.getInstance().getScriptEngineService().stopAllAndToast();
} else {
if (AutoJs.getInstance().getScriptEngineService().stopAllAndToast() <= 0) {
ViewUtils.showToast(mContext, R.string.text_no_scripts_to_stop_running);
}
});
@@ -225,7 +225,7 @@ public class CircularMenu implements Recorder.OnStateChangedListener, LayoutInsp
}
private void initFloaty() {
mWindow = new CircularMenuWindow(mContext, new CircularMenuFloaty() {
mWindow = new CircularMenuWindow(new CircularMenuFloaty() {
@Override
public CircularActionView inflateActionView(FloatyService service, CircularMenuWindow window) {
CircularActionView actionView = (CircularActionView) View.inflate(service, R.layout.circular_action_view, null);
@@ -283,8 +283,18 @@ public class CircularMenu implements Recorder.OnStateChangedListener, LayoutInsp
}
public void closeAndSaveState() {
boolean state = FloatyWindowManger.isCircularMenuShowing();
Pref.putBooleanSync(R.string.key_floating_menu_shown, state);
savePosition();
close();
Pref.putBoolean(R.string.key_floating_menu_shown, false);
}
public void savePosition() {
mWindow.savePosition();
}
public void savePosition(@NotNull Configuration newConfig) {
mWindow.savePosition(newConfig);
}
private AccessibilityTool getAccessibilityTool() {

View File

@@ -1,32 +1,32 @@
package org.autojs.autojs.ui.floating;
import android.content.Context;
import android.content.SharedPreferences;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.view.Gravity;
import android.view.OrientationEventListener;
import android.view.View;
import android.view.WindowManager;
import androidx.preference.PreferenceManager;
import org.autojs.autojs.pref.Pref;
import org.autojs.autojs.runtime.api.ScreenMetrics;
import org.autojs.autojs.ui.enhancedfloaty.FloatyService;
import org.autojs.autojs.ui.enhancedfloaty.FloatyWindow;
import org.autojs.autojs.ui.enhancedfloaty.WindowBridge;
import org.autojs.autojs.runtime.api.ScreenMetrics;
import org.autojs.autojs.ui.floating.gesture.BounceDragGesture;
import org.jetbrains.annotations.NotNull;
public class CircularMenuWindow extends FloatyWindow {
private static final String KEY_POSITION_X = CircularMenuWindow.class.getName() + ".position.x";
private static final String KEY_POSITION_Y = CircularMenuWindow.class.getName() + ".position.y";
private static final String KEY_POSITION_X_PERCENT = CircularMenuWindow.class.getName() + ".position.x_percent";
private static final String KEY_POSITION_Y_PERCENT = CircularMenuWindow.class.getName() + ".position.y_percent";
private static final String KEY_LAST_ORIENTATION = "key_$_last_orientation";
protected CircularMenuFloaty mFloaty;
protected CircularActionMenu mCircularActionMenu;
protected CircularActionView mCircularActionView;
protected BounceDragGesture mDragGesture;
protected OrientationAwareWindowBridge mActionViewWindowBridge;
protected WindowBridge.DefaultImpl mActionViewWindowBridge;
protected WindowBridge mMenuWindowBridge;
protected WindowManager.LayoutParams mActionViewWindowLayoutParams;
protected WindowManager.LayoutParams mMenuWindowLayoutParams;
@@ -34,12 +34,11 @@ public class CircularMenuWindow extends FloatyWindow {
protected float mKeepToSideHiddenWidthRadio;
protected float mActiveAlpha = 1.0F;
protected float mInactiveAlpha = 0.4F;
private final Context mContext;
private OrientationEventListener mOrientationEventListener;
public CircularMenuWindow(Context context, CircularMenuFloaty floaty) {
// private OrientationEventListener mOrientationEventListener;
public CircularMenuWindow(CircularMenuFloaty floaty) {
mFloaty = floaty;
mContext = context;
}
@Override
@@ -51,17 +50,21 @@ public class CircularMenuWindow extends FloatyWindow {
initGestures();
setListeners();
setInitialState();
mOrientationEventListener = new OrientationEventListener(mContext) {
@Override
public void onOrientationChanged(int orientation) {
if (mActionViewWindowBridge.isOrientationChanged(mContext.getResources().getConfiguration().orientation)) {
keepToSide();
}
}
};
if (mOrientationEventListener.canDetectOrientation()) {
mOrientationEventListener.enable();
}
// @Comment by SuperMonster003 on Aug 2, 2023.
// ! Seems useless.
// !
// mOrientationEventListener = new OrientationEventListener(mContext) {
// @Override
// public void onOrientationChanged(int orientation) {
// if (mActionViewWindowBridge.isOrientationChanged(mContext.getResources().getConfiguration().orientation)) {
// keepToSide();
// }
// }
// };
// if (mOrientationEventListener.canDetectOrientation()) {
// mOrientationEventListener.enable();
// }
}
@Override
@@ -79,9 +82,9 @@ public class CircularMenuWindow extends FloatyWindow {
}
private void setInitialState() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
int y = preferences.getInt(KEY_POSITION_Y, ScreenMetrics.getDeviceScreenHeight() / 2);
mActionViewWindowBridge.updatePosition(mActionViewWindowBridge.getX(), y);
int x = (int) (ScreenMetrics.getDeviceScreenWidth() * Pref.getFloat(KEY_POSITION_X_PERCENT, 0f));
int y = (int) (ScreenMetrics.getDeviceScreenHeight() * Pref.getFloat(KEY_POSITION_Y_PERCENT, 1 - 0.618f));
mActionViewWindowBridge.updatePosition(x, y);
keepToSide();
}
@@ -93,7 +96,7 @@ public class CircularMenuWindow extends FloatyWindow {
}
private void initWindowBridge() {
mActionViewWindowBridge = new OrientationAwareWindowBridge(mActionViewWindowLayoutParams, getWindowManager(), mCircularActionView, mContext);
mActionViewWindowBridge = new WindowBridge.DefaultImpl(mActionViewWindowLayoutParams, getWindowManager(), mCircularActionView);
mMenuWindowBridge = new WindowBridge.DefaultImpl(mMenuWindowLayoutParams, getWindowManager(), mCircularActionMenu);
}
@@ -213,14 +216,24 @@ public class CircularMenuWindow extends FloatyWindow {
close();
}
public void savePosition() {
int x = mActionViewWindowBridge.getX();
Pref.putFloatSync(KEY_POSITION_X_PERCENT, x / (float) ScreenMetrics.getDeviceScreenWidth());
int y = mActionViewWindowBridge.getY();
Pref.putFloatSync(KEY_POSITION_Y_PERCENT, y / (float) ScreenMetrics.getDeviceScreenHeight());
}
public void savePosition(@NotNull Configuration configuration) {
int orientation = configuration.orientation;
if (Pref.getInt(KEY_LAST_ORIENTATION, ORIENTATION_PORTRAIT) != orientation) {
Pref.putIntSync(KEY_LAST_ORIENTATION, orientation);
savePosition();
}
}
public void close() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
preferences.edit()
.putInt(KEY_POSITION_X, mActionViewWindowBridge.getX())
.putInt(KEY_POSITION_Y, mActionViewWindowBridge.getY())
.apply();
try {
mOrientationEventListener.disable();
// mOrientationEventListener.disable();
FloatyService.removeWindow(this);
FloatyWindowManger.clearCircularMenu();
if (mCircularActionMenu.isAttachedToWindow()) {

View File

@@ -8,7 +8,6 @@ import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.autojs.autojs.core.console.GlobalConsole;
import org.autojs.autojs.ui.enhancedfloaty.FloatyService;
import org.autojs.autojs.ui.enhancedfloaty.FloatyWindow;
@@ -28,6 +27,7 @@ public class FloatyWindowManger {
private static DisplayOverOtherAppsPermission sDisplayOverOtherAppsPerm;
private static boolean sCircularMenuShown;
public static boolean addWindow(Context context, FloatyWindow window) {
context.startService(new Intent(context, FloatyService.class));
getDisplayOverOtherAppsPerm(context).requestIfNeeded();
@@ -78,7 +78,7 @@ public class FloatyWindowManger {
context.startService(new Intent(context, FloatyService.class));
setCircularMenuContext(context);
} else {
ViewUtils.showToast(context, R.string.error_no_draw_overlays_permission);
ViewUtils.showToast(context, R.string.error_no_display_over_other_apps_permission);
getDisplayOverOtherAppsPerm(context).config();
}
sCircularMenuShown = true;
@@ -94,24 +94,23 @@ public class FloatyWindowManger {
public static void hideCircularMenuIfNeeded() {
if (isCircularMenuShowing()) {
hideCircularMenu(false);
hideCircularMenu();
}
}
public static void hideCircularMenu(boolean isSaveState) {
sCircularMenuShown = false;
if (sCircularMenu == null) {
return;
}
CircularMenu menu = sCircularMenu.get();
if (menu != null) {
if (isSaveState) {
menu.closeAndSaveState();
} else {
menu.close();
if (sCircularMenu != null) {
CircularMenu menu = sCircularMenu.get();
if (menu != null) {
if (isSaveState) {
menu.closeAndSaveState();
} else {
menu.close();
}
}
}
clearCircularMenu();
sCircularMenuShown = false;
}
public static void clearCircularMenu() {

View File

@@ -4,17 +4,15 @@ import android.content.Context
import android.view.ContextThemeWrapper
import android.view.View
import android.view.ViewGroup
import org.autojs.autojs.ui.enhancedfloaty.FloatyService
import org.autojs.autojs.app.AppLevelThemeDialogBuilder
import org.autojs.autojs.app.DialogUtils
import org.autojs.autojs.core.accessibility.NodeInfo
import org.autojs.autojs.ui.codegeneration.CodeGenerateDialog
import org.autojs.autojs.ui.enhancedfloaty.FloatyService
import org.autojs.autojs.ui.floating.layoutinspector.LayoutBoundsFloatyWindow
import org.autojs.autojs.ui.floating.layoutinspector.LayoutHierarchyFloatyWindow
import org.autojs.autojs.ui.floating.layoutinspector.NodeInfoView
import org.autojs.autojs.ui.widget.BubblePopupMenu
import org.autojs.autojs.util.ClipboardUtils
import org.autojs.autojs.util.ViewUtils
import org.autojs.autojs6.R
abstract class LayoutFloatyWindow(private val rootNode: NodeInfo?, private val context: Context, private val isServiceRelied: Boolean) : FullScreenFloatyWindow() {

View File

@@ -1,47 +0,0 @@
package org.autojs.autojs.ui.floating;
import android.content.Context;
import android.content.res.Configuration;
import android.view.View;
import android.view.WindowManager;
import org.autojs.autojs.ui.enhancedfloaty.WindowBridge;
public class OrientationAwareWindowBridge extends WindowBridge.DefaultImpl {
private final Context mContext;
private int mOrientation;
public OrientationAwareWindowBridge(WindowManager.LayoutParams windowLayoutParams, WindowManager windowManager, View windowView, Context context) {
super(windowLayoutParams, windowManager, windowView);
mContext = context;
mOrientation = mContext.getResources().getConfiguration().orientation;
}
public boolean isOrientationChanged(int newOrientation) {
if (mOrientation != newOrientation) {
mOrientation = newOrientation;
return true;
}
return false;
}
@Override
public int getScreenHeight() {
if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
return super.getScreenWidth();
} else {
return super.getScreenHeight();
}
}
@Override
public int getScreenWidth() {
if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
return super.getScreenHeight();
} else {
return super.getScreenWidth();
}
}
}

View File

@@ -8,6 +8,7 @@ import android.view.animation.BounceInterpolator;
import androidx.annotation.NonNull;
import org.autojs.autojs.ui.enhancedfloaty.WindowBridge;
import org.autojs.autojs6.R;
/**
* Created by Stardust on 2017/9/26.
@@ -37,11 +38,19 @@ public class BounceDragGesture extends DragGesture {
@Override
public void keepToEdge() {
int y = Math.min(mWindowBridge.getScreenHeight() - mView.getHeight() - MIN_DY_TO_SCREEN_BOTTOM, Math.max(MIN_DY_TO_SCREEN_TOP, mWindowBridge.getY()));
int side = mView.getContext().getResources().getDimensionPixelSize(R.dimen.side_circular_menu_icon);
int screenHeight = mWindowBridge.getScreenHeight();
int screenWidth = mWindowBridge.getScreenWidth();
int hiddenWidth = (int) (getKeepToSideHiddenWidthRadio() * (float) side);
int x = mWindowBridge.getX();
int hiddenWidth = (int) (getKeepToSideHiddenWidthRadio() * (float) mView.getWidth());
if (x > mWindowBridge.getScreenWidth() / 2) {
bounce(x, mWindowBridge.getScreenWidth() - mView.getWidth() + hiddenWidth, y);
int y = Math.min(
screenHeight - side - MIN_DY_TO_SCREEN_BOTTOM,
Math.max(MIN_DY_TO_SCREEN_TOP, mWindowBridge.getY())
);
if (x > screenWidth / 2) {
bounce(x, screenWidth - side + hiddenWidth, y);
} else {
bounce(x, -hiddenWidth, y);
}

View File

@@ -13,7 +13,11 @@ import org.autojs.autojs6.R
* Created by Stardust on 2017/3/12.
* Modified by SuperMonster003 as of Aug 31, 2022.
*/
open class LayoutBoundsFloatyWindow @JvmOverloads constructor(private val rootNode: NodeInfo?, private val context: Context, isServiceRelied: Boolean = false) : LayoutFloatyWindow(rootNode, context, isServiceRelied) {
open class LayoutBoundsFloatyWindow @JvmOverloads constructor(
private val rootNode: NodeInfo?,
private val context: Context,
isServiceRelied: Boolean = false,
) : LayoutFloatyWindow(rootNode, context, isServiceRelied) {
private lateinit var mLayoutBoundsView: LayoutBoundsView

View File

@@ -1,5 +1,6 @@
package org.autojs.autojs.ui.log;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@@ -23,6 +24,8 @@ public class LogActivity extends BaseActivity {
private GlobalConsole mConsoleImpl;
private final int RESULT_CODE_EXPORT = 33128;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -36,6 +39,7 @@ public class LogActivity extends BaseActivity {
mConsoleImpl = AutoJs.getInstance().getGlobalConsole();
mConsoleView.setConsole(mConsoleImpl);
mConsoleView.setLogActivity(this);
mConsoleView.setPinchToZoomEnabled(true);
ConsoleViewBinding consoleViewBinding = ConsoleViewBinding.inflate(getLayoutInflater());
@@ -55,10 +59,30 @@ public class LogActivity extends BaseActivity {
return true;
}
public void export(String fileName) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TITLE, fileName);
startActivityForResult(intent, RESULT_CODE_EXPORT);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RESULT_CODE_EXPORT && resultCode == Activity.RESULT_OK) {
mConsoleImpl.export(data.getData());
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_copy) {
mConsoleImpl.copyAll();
} else if (item.getItemId() == R.id.action_send) {
mConsoleImpl.send();
} else if (item.getItemId() == R.id.action_export) {
mConsoleImpl.export();
} else if (item.getItemId() == R.id.action_clear) {

View File

@@ -1,8 +1,7 @@
package org.autojs.autojs.ui.main
import android.Manifest.permission
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
@@ -20,6 +19,7 @@ import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager.SimpleOnPageChangeListener
import com.google.android.material.floatingactionbutton.FloatingActionButton
import org.autojs.autojs.App
import org.autojs.autojs.AutoJs
import org.autojs.autojs.app.FragmentPagerAdapterBuilder
import org.autojs.autojs.app.FragmentPagerAdapterBuilder.StoredFragmentPagerAdapter
@@ -62,6 +62,7 @@ import org.autojs.autojs.util.WorkingDirectoryUtils
import org.autojs.autojs6.R
import org.autojs.autojs6.databinding.ActivityMainBinding
import org.greenrobot.eventbus.EventBus
import rikka.shizuku.Shizuku
/**
* Modified by SuperMonster003 as of Dec 1, 2021.
@@ -86,12 +87,51 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity {
val requestMultiplePermissionsLauncher = registerForActivityResult(RequestMultiplePermissions()) {
it.forEach { (key: String, isGranted: Boolean) ->
Log.d(TAG, "$key: $isGranted")
if (key == "android.permission.POST_NOTIFICATIONS") {
if (key == Manifest.permission.POST_NOTIFICATIONS) {
Pref.putBoolean(R.string.key_post_notification_permission_requested, true)
}
}
}
private val shizukuBinderReceivedListener = {
// if (Shizuku.isPreV11()) {
// binding.text1.setText("Shizuku pre-v11 is not supported")
// } else {
// binding.text1.setText("Binder received")
// }
}
private val shizukuBinderDeadListener = {
// binding.text1.setText("Binder dead")
}
private val shizukuRequestPermissionResultListener: (requestCode: Int, grantResult: Int) -> Unit = { /* requestCode: */ _: Int, /* grantResult */ _: Int ->
// if (grantResult == PackageManager.PERMISSION_GRANTED) {
// switch (requestCode) {
// case REQUEST_CODE_BUTTON1: {
// getUsers();
// break;
// }
// case REQUEST_CODE_BUTTON4: {
// getSystemProperty();
// break;
// }
// case REQUEST_CODE_BUTTON7: {
// bindUserService();
// break;
// }
// case REQUEST_CODE_BUTTON8: {
// unbindUserService();
// break;
// }
// case REQUEST_CODE_BUTTON9: {
// peekUserService();
// break;
// }
// }
// } else {
// binding.text1.setText("User denied permission");
// }
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -122,6 +162,11 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity {
setUpTabViewPager()
registerBackPressHandlers()
addViewBackground(binding.appBar)
Log.d("Shizuku", "${App::class.java.simpleName} onCreate | Process=${App.getProcessNameCompat()}")
Shizuku.addBinderReceivedListenerSticky(this.shizukuBinderReceivedListener)
Shizuku.addBinderDeadListener(shizukuBinderDeadListener)
Shizuku.addRequestPermissionResultListener(shizukuRequestPermissionResultListener)
}
override fun onPostResume() {
@@ -206,16 +251,15 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity {
}
fun rebirth(view: View) {
val context = view.context as Activity
context.finish()
val context = view.context as MainActivity
context.packageManager.getLaunchIntentForPackage(context.packageName)?.let {
context.startActivity(Intent.makeRestartActivityTask(it.component))
}
Process.killProcess(Process.myPid())
context.exitCompletely()
}
fun exitCompletely() {
FloatyWindowManger.hideCircularMenu(!isFinishing)
FloatyWindowManger.hideCircularMenuAndSaveState()
ForegroundServiceUtils.stopServiceIfNeeded(this, MainActivityForegroundService::class.java)
stopService(Intent(this, FloatyService::class.java))
AutoJs.instance.scriptEngineService.stopAll()
@@ -240,7 +284,7 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity {
}
private fun getGrantResult(permissions: Array<String>, grantResults: IntArray): Int {
val i = listOf(*permissions).indexOf(permission.READ_EXTERNAL_STORAGE)
val i = listOf(*permissions).indexOf(Manifest.permission.READ_EXTERNAL_STORAGE)
return if (i < 0) 2 else grantResults[i]
}
@@ -255,7 +299,6 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity {
}
}
if (!mBackPressObserver.onBackPressed(this)) {
@Suppress("DEPRECATION")
super.onBackPressed()
}
}
@@ -341,6 +384,10 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity {
override fun onDestroy() {
super.onDestroy()
EventBus.getDefault().unregister(this)
Shizuku.removeBinderReceivedListener(this.shizukuBinderReceivedListener)
Shizuku.removeBinderDeadListener(shizukuBinderDeadListener)
Shizuku.removeRequestPermissionResultListener(shizukuRequestPermissionResultListener)
}
override fun onResume() {

View File

@@ -103,7 +103,7 @@ interface CommandBasedPermissionItemHelper : PermissionItemHelper, IPermissionRo
ViewUtils.showToast(context, resultRes)
}
}
.negativeText(R.string.text_back)
.negativeText(R.string.dialog_button_cancel)
.onNegative { dialog, _ -> dialog.dismiss() }
.positiveText(R.string.text_copy_command)
.onPositive { dialog, _ ->

View File

@@ -68,8 +68,8 @@ open class DrawerMenuToggleableItem : DrawerMenuItem, IToggleableItem {
NotAskAgainDialog.Builder(mContext, key)
.title(title)
.content(it)
.negativeText(R.string.text_back)
.positiveText(R.string.text_continue)
.negativeText(R.string.dialog_button_cancel)
.positiveText(R.string.dialog_button_continue)
.onPositive { _, _ -> toggle(aimState) }
.dismissListener { sync() }
.show()

View File

@@ -27,7 +27,6 @@ import org.autojs.autojs6.databinding.ActivityAboutItemsBinding
* Created by Stardust on 2017/2/2.
* Modified by SuperMonster003 as of Dec 1, 2021.
*/
open class AboutActivity : BaseActivity() {
private lateinit var activityBinding: ActivityAboutBinding
@@ -121,13 +120,13 @@ open class AboutActivity : BaseActivity() {
MaterialDialog.Builder(this)
.title(R.string.text_app_and_device_info)
.content(DeviceUtils.getDeviceSummaryWithSimpleAppInfo(this))
.negativeText(R.string.text_back)
.neutralText(R.string.text_copy)
.neutralText(R.string.dialog_button_copy)
.onNeutral { d, _ ->
ClipboardUtils.setClip(this, d.contentView?.text)
ViewUtils.showToast(this, R.string.text_already_copied_to_clip)
}
.neutralColorRes(R.color.dialog_button_hint)
.negativeText(R.string.dialog_button_dismiss)
.build()
.apply { window?.setBackgroundDrawableResource(R.color.about_app_dev_info_dialog_background) }
.show()
@@ -149,7 +148,7 @@ open class AboutActivity : BaseActivity() {
LicenseResolver.registerLicense(MozillaPublicLicense20.instance)
LicensesDialog.Builder(this)
.setTitle(R.string.text_licenses)
.setCloseText(R.string.dialog_button_back)
.setCloseText(R.string.dialog_button_dismiss)
.setNotices(R.raw.licenses)
.setIncludeOwnLicense(true)
.setEnableDarkMode(true)
@@ -168,8 +167,8 @@ open class AboutActivity : BaseActivity() {
NotAskAgainDialog.Builder(this, key)
.title(R.string.text_prompt)
.content(R.string.content_github_feedback)
.negativeText(R.string.text_back)
.positiveText(R.string.text_continue)
.negativeText(R.string.dialog_button_cancel)
.positiveText(R.string.dialog_button_continue)
.onNegative { d, _ -> d.dismiss() }
.onPositive { _, _ -> launchGithubIssuesPage() }
.cancelable(false)

View File

@@ -35,6 +35,6 @@ class CheckForUpdatesPreference : MaterialPreference, OnSharedPreferenceChangeLi
super.onClick()
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) = notifyChanged()
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) = notifyChanged()
}

View File

@@ -119,11 +119,11 @@ class WorkingDirectoryPreference : MaterialPreference {
mContentView.setText(text)
}
}
.negativeText(R.string.dialog_button_back)
.negativeText(R.string.dialog_button_cancel)
.show()
}
}
.negativeText(R.string.dialog_button_back)
.negativeText(R.string.dialog_button_cancel)
.negativeColorRes(R.color.dialog_button_default)
.onNegative { dHistories, _ -> dHistories.dismiss() }
.autoDismiss(false)

View File

@@ -7,7 +7,6 @@ import android.view.View;
/**
* Created by Stardust on 2017/3/11.
*/
public class ViewSwitcher extends android.widget.ViewSwitcher {
private View mCurrentView;

View File

@@ -11,8 +11,8 @@ import java.lang.reflect.Field;
public class JavaUtils {
@NonNull
public static Class<?> getClass(@NonNull Class<?> Clazz) {
return Clazz;
public static Class<?> getClass(@NonNull Class<?> clazz) {
return clazz;
}
@NonNull

View File

@@ -0,0 +1,126 @@
@file:Suppress("unused")
package org.autojs.autojs.util
import android.app.AppOpsManager
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.Process
import android.provider.Settings
import org.autojs.autojs.app.GlobalAppContext
import org.autojs.autojs.pref.Language
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
/**
* Created by SuperMonster003 on Aug 17, 2023.
*/
// @Reference to https://blog.csdn.net/liao_fu_yun/article/details/114971424
object RomUtils {
@JvmStatic
@JvmOverloads
fun isBackgroundStartGranted(context: Context = GlobalAppContext.get()) = when {
isMiui() -> isMiuiBgStartPermissionGranted(context)
isVivo() -> isVivoBgStartPermissionGranted(context)
isOppo() -> Settings.canDrawOverlays(context)
else -> true
}
private fun isMiuiBgStartPermissionGranted(context: Context): Boolean {
val ops = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
try {
val op = 10021
val method = ops.javaClass.getMethod(
"checkOpNoThrow",
Int::class.javaPrimitiveType,
Int::class.javaPrimitiveType,
String::class.java,
)
val result = method.invoke(ops, op, Process.myUid(), context.packageName) as Int
return result == AppOpsManager.MODE_ALLOWED
} catch (e: Exception) {
e.printStackTrace()
}
return false
}
private fun isVivoBgStartPermissionGranted(context: Context): Boolean {
return getVivoBgStartPermissionStatus(context) == 0
}
/**
* 判断 Vivo 后台弹出界面状态. 1: 无权限; 0: 有权限.
*/
private fun getVivoBgStartPermissionStatus(context: Context): Int {
val uri = Uri.parse("content://com.vivo.permissionmanager.provider.permission/start_bg_activity")
val selection = "pkgname = ?"
val selectionArgs = arrayOf(context.packageName)
var state = 1
try {
context.contentResolver.query(uri, null, selection, selectionArgs, null)?.use {
if (it.moveToFirst()) {
val columnIndex = it.getColumnIndex("currentstate")
if (columnIndex >= 0) {
state = it.getInt(columnIndex)
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return state
}
/* -=-=- --------------------------------------- -=-=- */
/* -=-=- Android-based OS, including custom ROMs -=-=- */
/* -=-=- --------------------------------------- -=-=- */
fun isMiui() = getSystemProperty("ro.miui.ui.version.name").isNotEmpty()
fun isEmui() = getSystemProperty("ro.build.version.emui").isNotEmpty()
fun isOppo() = getSystemProperty("ro.build.version.opporom").isNotEmpty()
fun isSmartisan() = getSystemProperty("ro.smartisan.version").isNotEmpty()
fun isVivo() = getSystemProperty("ro.vivo.os.version").isNotEmpty()
/** 金立. */
fun isGionee() = getSystemProperty("ro.gn.sv.version").isNotEmpty()
fun isLenovo() = getSystemProperty("ro.lenovo.lvp.version").isNotEmpty()
fun isFlyme() = Build.DISPLAY.lowercase(Language.getPrefLanguage().locale).contains("flyme")
private fun getSystemProperty(propName: String): String = try {
BufferedReader(
InputStreamReader(
Runtime.getRuntime().exec("getprop $propName").inputStream
), 1024
).use { it.readLine() }
} catch (ex: IOException) {
""
}
/* -=-=- --------------- -=-=- */
/* -=-=- Brand / Product -=-=- */
/* -=-=- --------------- -=-=- */
object Brand {
/** Sony (索尼) Xperia. */
fun isXperia() = getSystemProperty("ro.semc.product.name").contains("xperia", true)
/** Redmi (红米). */
fun isRedmi() = getSystemProperty("ro.product.model").contains("redmi", true)
/** Mi Mix. */
fun isMiMix() = Build.MODEL.contains("mi mix", true)
}
}

View File

@@ -127,12 +127,12 @@ object UpdateUtils {
.onNeutral { dVersionInfo, _ ->
showRemoveIgnoredVersionPrompt(context, dialog, dVersionInfo, text)
}
.positiveText(R.string.dialog_button_back)
.positiveText(R.string.dialog_button_cancel)
.onPositive { dVersionInfo, _ -> dVersionInfo.dismiss() }
.autoDismiss(false)
.show()
}
.positiveText(R.string.dialog_button_back)
.positiveText(R.string.dialog_button_cancel)
.onPositive { dialog, _ -> dialog.dismiss() }
.autoDismiss(false)
.build()

View File

@@ -5,8 +5,7 @@ import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.TIRAMISU
import android.webkit.WebView
import androidx.webkit.WebSettingsCompat
import androidx.webkit.WebSettingsCompat.FORCE_DARK_OFF
import androidx.webkit.WebSettingsCompat.FORCE_DARK_ON
import androidx.webkit.WebViewFeature.ALGORITHMIC_DARKENING
import androidx.webkit.WebViewFeature.FORCE_DARK
import androidx.webkit.WebViewFeature.isFeatureSupported
@@ -26,13 +25,17 @@ class WebViewUtils {
if (isFeatureSupported(FORCE_DARK) && SDK_INT < TIRAMISU) {
webView.settings.let { webSettings ->
webSettings.setSupportMultipleWindows(true)
@Suppress("DEPRECATION")
WebSettingsCompat.setForceDark(
webSettings, when (ViewUtils.isNightModeYes(context)) {
true -> FORCE_DARK_ON
else -> FORCE_DARK_OFF
}
)
// @Comment by SuperMonster003 on Aug 14, 2023.
// @Suppress("DEPRECATION")
// WebSettingsCompat.setForceDark(
// webSettings, when (ViewUtils.isNightModeYes(context)) {
// true -> FORCE_DARK_ON
// else -> FORCE_DARK_OFF
// }
// )
if (isFeatureSupported(ALGORITHMIC_DARKENING)) {
WebSettingsCompat.setAlgorithmicDarkeningAllowed(webSettings, ViewUtils.isNightModeYes(context))
}
}
}
}

View File

@@ -5,7 +5,6 @@ import java.util.Map;
/**
* Created by Stardust on 2017/10/23.
*/
public class DumpEditor extends DumpAdapter {
private Map<String, String> mValueModifications;