6.3.4 Alpha2 - 新增 Paddle Lite / WebSocket; 修复屏幕旋转浮动按钮异常; 优化日志复制与导出
This commit is contained in:
@@ -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)) {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -18,7 +18,6 @@ import pxb.android.tinysign.TinySign;
|
||||
/**
|
||||
* Created by Stardust on 2017/10/23.
|
||||
*/
|
||||
|
||||
public class ApkPackager {
|
||||
|
||||
private InputStream mApkInputStream;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)+")
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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())
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"),
|
||||
;
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
@@ -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 */
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
}
|
||||
@@ -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 */
|
||||
/* ... ... */
|
||||
|
||||
}
|
||||
@@ -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() }
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -7,7 +7,6 @@ import androidx.annotation.Nullable;
|
||||
/**
|
||||
* Created by Stardust on 2017/4/19.
|
||||
*/
|
||||
|
||||
public interface ResizableExpandableFloaty {
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.widget.ImageView;
|
||||
/**
|
||||
* Created by Stardust on 2017/4/30.
|
||||
*/
|
||||
|
||||
public interface ResizableFloaty {
|
||||
|
||||
View inflateView(FloatyService floatyService, ResizableFloatyWindow service);
|
||||
|
||||
@@ -17,7 +17,6 @@ import org.autojs.autojs6.R;
|
||||
/**
|
||||
* Created by Stardust on 2017/4/30.
|
||||
*/
|
||||
|
||||
public class ResizableFloatyWindow extends FloatyWindow {
|
||||
|
||||
private View mView;
|
||||
|
||||
@@ -7,7 +7,6 @@ import java.util.Stack;
|
||||
/**
|
||||
* Created by Stardust on 2017/3/11.
|
||||
*/
|
||||
|
||||
public class ViewStack {
|
||||
|
||||
public interface CurrentViewSetter {
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.view.WindowManager;
|
||||
/**
|
||||
* Created by Stardust on 2017/4/18.
|
||||
*/
|
||||
|
||||
public interface WindowBridge {
|
||||
int getX();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -11,7 +11,6 @@ import android.view.WindowManager;
|
||||
/**
|
||||
* Created by Stardust on 2017/3/10.
|
||||
*/
|
||||
|
||||
public class FloatingWindowPermissionUtil {
|
||||
|
||||
public static void goToFloatingWindowPermissionSettingIfNeeded(Context context) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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, _ ->
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
126
app/src/main/java/org/autojs/autojs/util/RomUtils.kt
Normal file
126
app/src/main/java/org/autojs/autojs/util/RomUtils.kt
Normal 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)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user