修改截图代码,多脚本之间可以共享截图权限。被其他脚本抢占截图权限后可以自动重新获取权限

This commit is contained in:
TonyJiangWJ 2022-01-23 17:36:39 +08:00
parent 15319c6fe2
commit 673f27d2d3
16 changed files with 429 additions and 102 deletions

1
.gitignore vendored
View File

@ -14,3 +14,4 @@ build/
*.exe
.idea/caches/build_file_checksums.ser
.idea/
*.log

View File

@ -6,6 +6,9 @@ apply plugin: 'com.jakewharton.butterknife'
def AAVersion = '4.8.0'
def SupportLibVersion = '28.0.0'
def homePath = System.properties['user.home']
def storePasswd = System.getenv('autojspasswd')
def keyPasswd = System.getenv('autojspasswd')
def alias = System.getenv('autojsalias')
configurations.all {
resolutionStrategy {
@ -18,9 +21,9 @@ android {
signingConfigs {
release {
storeFile file(homePath + '/auto-js-t-pkcs12.jks')
storePassword System.getenv('autojspasswd')
keyPassword System.getenv('autojspasswd')
keyAlias = System.getenv('autojsalias')
storePassword storePasswd
keyPassword keyPasswd
keyAlias = alias
}
debug {
storeFile file('debug/autojs-pkcs12-debug.jks')
@ -53,7 +56,7 @@ android {
shrinkResources false
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
signingConfig signingConfigs.debug
applicationIdSuffix ".debug"
}
release {

View File

@ -0,0 +1,74 @@
"ui";
let pm = context.getPackageManager();
let iconCache = {};
let IconView = (function () {
// 继承ui.Widget
util.extend(IconView, ui.Widget);
function IconView() {
// 调用父类构造函数
ui.Widget.call(this);
// 自定义属性packageName
this.defineAttr("packageName", (view, name, defaultGetter) => {
return this._packageName;
}, (view, name, value, defaultSetter) => {
this._packageName = value;
view.setImageDrawable(iconCache[value]);
});
}
IconView.prototype.render = function () {
return (
<img w="*" h="*" scaleType="fitXY"/>
);
}
ui.registerWidget("icon", IconView);
return IconView;
})();
let apps = [];
ui.layout(
<vertical bg="#ffffff">
<list id="apps" layout_weight="1">
<linear bg="?selectableItemBackground" w="*" gravity="center_vertical">
<icon packageName="{{this.packageName}}" w="80" h="80" />
<vertical h="auto">
<text id="name" textSize="16sp" textColor="#000000" text="{{this.appName}}" maxLines="1" ellipsize="end" />
<text id="path" textSize="13sp" textColor="#929292" text="{{this.packageName}}" marginTop="4" maxLines="1" ellipsize="end" />
</vertical>
</linear>
</list>
<progressbar id="progressbar" indeterminate="true" style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal" />
</vertical>
);
ui.apps.setDataSource(apps);
ui.apps.on("item_click", function (item, pos) {
toast(util.inspect(item));
});
// 启动线程来扫描app
threads.start(function () {
listApps(apps);
// 切换回UI线程隐藏进度条
ui.run(() => {
ui.progressbar.attr("visibility", "gone");
});
});
function listApps(apps) {
let list = pm.getInstalledPackages(0);
list.forEach(pkgInfo => {
let appInfo = pm.getApplicationInfo(pkgInfo.packageName, android.content.pm.PackageManager.GET_META_DATA)
apps.push({
appName: appInfo.loadLabel(pm),
packageName: pkgInfo.packageName,
versionName: pkgInfo.versionName,
versionCode: pkgInfo.versionCode,
});
iconCache[pkgInfo.packageName] = pkgInfo.applicationInfo.loadIcon(pm)
});
}

View File

@ -1,71 +0,0 @@
"ui";
var IconView = (function() {
//继承ui.Widget
util.extend(IconView, ui.Widget);
function IconView() {
//调用父类构造函数
ui.Widget.call(this);
//自定义属性color定义按钮颜色
this.defineAttr("icon", (view, name, defaultGetter) => {
return this._icon;
}, (view, name, value, defaultSetter) => {
this._icon = value;
view.setImageResource(value);
});
}
IconView.prototype.render = function() {
return (
<img />
);
}
ui.registerWidget("icon", IconView);
return IconView;
})();
var apps = [];
ui.layout(
<vertical bg="#ffffff">
<list id="files" layout_weight="1">
<linear bg="?selectableItemBackground">
<icon icon="{{this.icon}}" w="50" h="70" margin="16" />
<vertical>
<text id="name" textSize="16sp" textColor="#000000" text="{{this.appName}}" marginTop="16" maxLines="1" ellipsize="end"/>
<text id="path" textSize="13sp" textColor="#929292" text="{{this.packageName}}" marginTop="8" maxLines="1" ellipsize="end"/>
</vertical>
</linear>
</list>
<progressbar id="progressbar" indeterminate="true" style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"/>
</vertical>
);
ui.apps.setDataSource(musicFiles);
ui.apps.on("item_click", function(item, pos){
toast(item);
});
//启动线程来扫描音乐文件
threads.start(function () {
listApps(apps);
ui.run(()=> {
ui.progressbar.setVisility(8);
});
});
function listApps(apps) {
var pm = context.getPackageManager();
let list = pm.getInstalledPackages(0);
for(let i = 0; i < list.size(); i++){
let p = list.get(i);
apps.push({
appName: p.applicationInfo.loadLabel(pm).toString(),
packageName: p.packageName,
versionName: p.versionName,
versionCode: p.versionCode,
icon: p.applicationInfo.loadIcon(pm)
});
}
}

View File

@ -53,6 +53,8 @@ public class DocsFragment extends ViewPagerFragment implements BackPressedHandle
@AfterViews
void setUpViews() {
mWebView = mEWebView.getWebView();
mWebView.getSettings().setSupportZoom(false);
mWebView.getSettings().setBuiltInZoomControls(false);
mEWebView.getSwipeRefreshLayout().setOnRefreshListener(() -> {
if (TextUtils.equals(mWebView.getUrl(), mIndexUrl)) {
loadUrl();

View File

@ -101,9 +101,9 @@ public abstract class AutoJs {
return exception;
});
ResourceMonitor.setUnclosedResourceDetectedHandler(mGlobalConsole::error);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mContext.startForegroundService(new Intent(mContext, CaptureForegroundService.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// mContext.startForegroundService(new Intent(mContext, CaptureForegroundService.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
// }
}
public abstract void ensureAccessibilityServiceEnabled();

View File

@ -49,6 +49,7 @@ public class CaptureForegroundService extends Service {
return new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Recording")
.setContentText("本通知为截图权限需要")
.setSmallIcon(R.drawable.autojs_material)
.setWhen(System.currentTimeMillis())
.setContentIntent(contentIntent)

View File

@ -0,0 +1,292 @@
package com.stardust.autojs.core.image.capture;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.Image;
import android.media.ImageReader;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.OrientationEventListener;
import androidx.annotation.Nullable;
import com.stardust.autojs.runtime.exception.ScriptException;
import com.stardust.autojs.runtime.exception.ScriptInterruptedException;
import com.stardust.lang.ThreadCompat;
import com.stardust.util.ScreenMetrics;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
/**
* Created by TonyJiangWJ on 2022/1/22
*/
public class GlobalScreenCapture {
public static final int ORIENTATION_AUTO = Configuration.ORIENTATION_UNDEFINED;
public static final int ORIENTATION_LANDSCAPE = Configuration.ORIENTATION_LANDSCAPE;
public static final int ORIENTATION_PORTRAIT = Configuration.ORIENTATION_PORTRAIT;
private static final String TAG = "GlobalScreenCapture";
private final ConcurrentHashMap<Thread, Boolean> registeredThreads = new ConcurrentHashMap<>();
private MediaProjectionManager mProjectionManager;
private ImageReader mImageReader;
private MediaProjection mMediaProjection;
private VirtualDisplay mVirtualDisplay;
private volatile Looper mImageAcquireLooper;
private volatile Image mUnderUsingImage;
private Context mContext;
private Intent mData;
private Handler mHandler;
private final AtomicReference<Image> mCachedImage = new AtomicReference<>();
private volatile Exception mException;
private final int mScreenDensity;
private int mOrientation = -1;
private int mDetectedOrientation;
private OrientationEventListener mOrientationEventListener;
private boolean hasPermission;
private boolean noRegister;
private GlobalScreenCapture() {
mScreenDensity = ScreenMetrics.getDeviceScreenDensity();
}
private static class Holder {
@SuppressLint("StaticFieldLeak")
private final static GlobalScreenCapture INSTANCE = new GlobalScreenCapture();
}
public static GlobalScreenCapture getInstance() {
return Holder.INSTANCE;
}
public synchronized void initCapture(Context context, Intent data, int orientation) {
Log.d(TAG, "initCapture: ");
mProjectionManager = (MediaProjectionManager) context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
mMediaProjection = mProjectionManager.getMediaProjection(Activity.RESULT_OK, (Intent) data.clone());
mContext = context;
mData = (Intent) data.clone();
new Thread(() -> {
Looper.prepare();
mHandler = new Handler(Looper.myLooper());
synchronized (GlobalScreenCapture.this) {
GlobalScreenCapture.this.notifyAll();
}
Looper.loop();
}).start();
synchronized (this) {
try {
this.wait();
} catch (InterruptedException e) {
throw new ScriptInterruptedException();
}
}
observeOrientation();
setOrientation(orientation);
hasPermission = true;
}
public synchronized boolean hasPermission() {
return hasPermission;
}
public void setOrientation(int orientation) {
if (mOrientation == orientation) {
return;
}
mOrientation = orientation;
mDetectedOrientation = mContext.getResources().getConfiguration().orientation;
refreshVirtualDisplay(mOrientation == ORIENTATION_AUTO ? mDetectedOrientation : mOrientation);
}
private void observeOrientation() {
mOrientationEventListener = new OrientationEventListener(mContext) {
@Override
public void onOrientationChanged(int o) {
int orientation = mContext.getResources().getConfiguration().orientation;
if (mOrientation == ORIENTATION_AUTO && mDetectedOrientation != orientation) {
mDetectedOrientation = orientation;
try {
refreshVirtualDisplay(orientation);
} catch (Exception e) {
e.printStackTrace();
mException = e;
}
}
}
};
if (mOrientationEventListener.canDetectOrientation()) {
mOrientationEventListener.enable();
}
}
private void refreshVirtualDisplay(int orientation) {
if (mImageAcquireLooper != null) {
mImageAcquireLooper.quit();
}
if (mImageReader != null) {
mImageReader.close();
}
if (mVirtualDisplay != null) {
mVirtualDisplay.release();
}
try {
mMediaProjection = mProjectionManager.getMediaProjection(Activity.RESULT_OK, (Intent) mData.clone());
} catch (Exception e) {
Log.d(TAG, "refreshVirtualDisplay: 获取新projection失败 可能只是MIUI的bug " + e);
}
int screenHeight = ScreenMetrics.getOrientationAwareScreenHeight(orientation);
int screenWidth = ScreenMetrics.getOrientationAwareScreenWidth(orientation);
initVirtualDisplay(screenWidth, screenHeight, mScreenDensity);
startAcquireImageLoop();
}
@SuppressLint("WrongConstant")
private void initVirtualDisplay(int width, int height, int screenDensity) {
mImageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 3);
try {
mVirtualDisplay = mMediaProjection.createVirtualDisplay(TAG,
width, height, screenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mImageReader.getSurface(), null, null);
} catch (SecurityException e) {
Log.d(TAG, "initVirtualDisplay: 获取virtualDisplay失败" + e);
release();
}
}
private void startAcquireImageLoop() {
if (mImageReader == null) {
// 初始化virtualDisplay异常
return;
}
if (mHandler != null) {
setImageListener(mHandler);
return;
}
new Thread(() -> {
Log.d(TAG, "AcquireImageLoop: start");
Looper.prepare();
mImageAcquireLooper = Looper.myLooper();
setImageListener(new Handler());
Looper.loop();
Log.d(TAG, "AcquireImageLoop: stop");
}).start();
}
private void setImageListener(Handler handler) {
mImageReader.setOnImageAvailableListener(reader -> {
try {
if (noRegister) {
return;
}
Image oldCacheImage = mCachedImage.getAndSet(null);
if (oldCacheImage != null) {
oldCacheImage.close();
}
mCachedImage.set(reader.acquireLatestImage());
} catch (Exception e) {
mException = e;
}
}, handler);
}
@Nullable
public synchronized Image capture() {
Exception e = mException;
if (e != null) {
mException = null;
throw new ScriptException(e);
}
Thread thread = ThreadCompat.currentThread();
long startTime = System.currentTimeMillis();
while (!thread.isInterrupted()) {
Image cachedImage = mCachedImage.getAndSet(null);
if (cachedImage != null) {
if (mUnderUsingImage != null) {
mUnderUsingImage.close();
}
mUnderUsingImage = cachedImage;
return cachedImage;
}
if (System.currentTimeMillis() - startTime > 1000) {
startTime = System.currentTimeMillis();
this.refreshVirtualDisplay(mOrientation);
}
}
throw new ScriptInterruptedException();
}
public synchronized void unregister(Looper looper) {
Log.d(TAG, "unregister: " + looper.getThread().getName());
registeredThreads.remove(looper.getThread());
Iterator<Thread> keyThreads = registeredThreads.keySet().iterator();
while (keyThreads.hasNext()) {
Thread thread = keyThreads.next();
if (!thread.isAlive()) {
keyThreads.remove();
}
}
noRegister = registeredThreads.size() == 0;
if (noRegister) {
Log.d(TAG, "全部引擎已注销,释放截图权限,清除通知");
release();
}
}
public synchronized void register(Looper looper) {
Log.d(TAG, "新引擎注册:" + looper.getThread().getName() + " hasPermission? " + hasPermission);
noRegister = false;
registeredThreads.put(looper.getThread(), true);
}
private void release() {
noRegister = true;
hasPermission = false;
if (mImageAcquireLooper != null) {
mImageAcquireLooper.quit();
mImageAcquireLooper = null;
}
if (mMediaProjection != null) {
mMediaProjection.stop();
mMediaProjection = null;
}
if (mVirtualDisplay != null) {
mVirtualDisplay.release();
}
if (mImageReader != null) {
mImageReader.setOnImageAvailableListener(null, null);
mImageReader.close();
mImageReader = null;
}
if (mUnderUsingImage != null) {
mUnderUsingImage.close();
mUnderUsingImage = null;
}
Image cachedImage = mCachedImage.getAndSet(null);
if (cachedImage != null) {
cachedImage.close();
}
if (mOrientationEventListener != null) {
mOrientationEventListener.disable();
mOrientationEventListener = null;
}
mContext.stopService(new Intent(mContext, CaptureForegroundService.class));
}
}

View File

@ -23,6 +23,9 @@ public class ScreenCaptureRequestActivity extends Activity {
private ScreenCaptureRequester.Callback mCallback;
public static void request(Context context, ScreenCaptureRequester.Callback callback) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
context.startForegroundService(new Intent(context, CaptureForegroundService.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
Intent intent = new Intent(context, ScreenCaptureRequestActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
IntentExtras.newExtras()

View File

@ -1,6 +1,7 @@
package com.stardust.autojs.core.image.capture;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.media.projection.MediaProjectionManager;
@ -78,6 +79,9 @@ public interface ScreenCaptureRequester {
@Override
public void request() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mActivity.startForegroundService(new Intent(mActivity, CaptureForegroundService.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
mActivity.startActivityForResult(((MediaProjectionManager) mActivity.getSystemService(Context.MEDIA_PROJECTION_SERVICE)).createScreenCaptureIntent(), REQUEST_CODE_MEDIA_PROJECTION);
}

View File

@ -24,7 +24,7 @@ public class ViewExtras {
public static ViewExtras get(View view) {
ViewExtras extras;
Object tag = view.getTag(R.id.view_tag_view_extras);
Log.d(LOG_TAG, "view = " + view + ", tag = " + tag);
// Log.d(LOG_TAG, "view = " + view + ", tag = " + tag);
if (tag instanceof ViewExtras) {
extras = (ViewExtras) tag;
} else {

View File

@ -50,5 +50,11 @@ public class AndroidContextFactory extends ShellContextFactory {
return cx;
}
@Override
protected boolean hasFeature(Context cx, int featureIndex) {
if (featureIndex == Context.FEATURE_ENABLE_XML_SECURE_PARSING) {
return false;
}
return super.hasFeature(cx, featureIndex);
}
}

View File

@ -42,14 +42,14 @@ public class InterruptibleAndroidContextFactory extends AndroidContextFactory {
protected void onContextCreated(Context cx) {
super.onContextCreated(cx);
int i = mContextCount.incrementAndGet();
Log.d(LOG_TAG, "onContextCreated: count = " + i);
// Log.d(LOG_TAG, "onContextCreated: count = " + i);
}
@Override
protected void onContextReleased(Context cx) {
super.onContextReleased(cx);
int i = mContextCount.decrementAndGet();
Log.d(LOG_TAG, "onContextReleased: count = " + i);
// Log.d(LOG_TAG, "onContextReleased: count = " + i);
}
}

View File

@ -432,16 +432,14 @@ public class ScriptRuntime {
ignoresException(threads::shutDownAll);
ignoresException(events::recycle);
ignoresException(media::recycle);
ignoresException(images::releaseScreenCapturer);
ignoresException(images::recycle);
ignoresException(loopers::recycle);
ignoresException(() -> {
if (mRootShell != null) mRootShell.exit();
mRootShell = null;
mShellSupplier = null;
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
ignoresException(images::releaseScreenCapturer);
ignoresException(images::recycle);
}
ignoresException(sensors::unregisterAll);
sensors = null;
ignoresException(timers::recycle);

View File

@ -12,14 +12,15 @@ import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Base64;
import android.util.Log;
import android.view.Gravity;
import com.stardust.autojs.annotation.ScriptVariable;
import com.stardust.autojs.core.image.ColorFinder;
import com.stardust.autojs.core.image.ImageWrapper;
import com.stardust.autojs.core.image.TemplateMatching;
import com.stardust.autojs.core.image.capture.GlobalScreenCapture;
import com.stardust.autojs.core.image.capture.ScreenCaptureRequester;
import com.stardust.autojs.core.image.capture.ScreenCapturer;
import com.stardust.autojs.core.opencv.Mat;
import com.stardust.autojs.core.opencv.OpenCVHelper;
import com.stardust.autojs.core.ui.inflater.util.Drawables;
@ -54,13 +55,14 @@ public class Images {
private WeakReference<ScriptRuntime> mScriptRuntime;
private ScreenCaptureRequester mScreenCaptureRequester;
private ScreenCapturer mScreenCapturer;
private Context mContext;
private Image mPreCapture;
private ImageWrapper mPreCaptureImage;
private ScreenMetrics mScreenMetrics;
private volatile boolean mOpenCvInitialized = false;
private final static String TAG = "Images";
@ScriptVariable
public final ColorFinder colorFinder;
@ -76,16 +78,23 @@ public class Images {
public ScriptPromiseAdapter requestScreenCapture(int orientation) {
ScriptRuntime.requiresApi(21);
ScriptPromiseAdapter promiseAdapter = new ScriptPromiseAdapter();
if (mScreenCapturer != null) {
mScreenCapturer.setOrientation(orientation);
promiseAdapter.resolve(true);
return promiseAdapter;
if (GlobalScreenCapture.getInstance().hasPermission()) {
synchronized (GlobalScreenCapture.getInstance()) {
if (GlobalScreenCapture.getInstance().hasPermission()) {
Log.d(TAG, "requestScreenCapture hasPermission 直接注册");
GlobalScreenCapture.getInstance().setOrientation(orientation);
GlobalScreenCapture.getInstance().register(mScriptRuntime.get().loopers.getMainLooper());
new Handler(mScriptRuntime.get().loopers.getMainLooper())
.post(() -> promiseAdapter.resolve(true));
return promiseAdapter;
}
}
}
Looper servantLooper = mScriptRuntime.get().loopers.getServantLooper();
mScreenCaptureRequester.setOnActivityResultCallback((result, data) -> {
if (result == Activity.RESULT_OK) {
mScreenCapturer = new ScreenCapturer(mContext, data, orientation, ScreenMetrics.getDeviceScreenDensity(),
new Handler(servantLooper));
GlobalScreenCapture.getInstance().initCapture(mContext, data, orientation);
GlobalScreenCapture.getInstance().register(mScriptRuntime.get().loopers.getMainLooper());
promiseAdapter.resolve(true);
} else {
promiseAdapter.resolve(false);
@ -97,11 +106,12 @@ public class Images {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public synchronized ImageWrapper captureScreen() {
Log.d(TAG, "captureScreen: 请求截图 " + mScriptRuntime.get().loopers.getMainLooper().getThread().getName());
ScriptRuntime.requiresApi(21);
if (mScreenCapturer == null) {
if (!GlobalScreenCapture.getInstance().hasPermission()) {
throw new SecurityException("No screen capture permission");
}
Image capture = mScreenCapturer.capture();
Image capture = GlobalScreenCapture.getInstance().capture();
if (capture == mPreCapture && mPreCaptureImage != null) {
return mPreCaptureImage;
}
@ -262,8 +272,10 @@ public class Images {
}
public void releaseScreenCapturer() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mScreenCapturer != null) {
mScreenCapturer.release();
if (GlobalScreenCapture.getInstance().hasPermission()) {
synchronized (GlobalScreenCapture.getInstance()) {
GlobalScreenCapture.getInstance().unregister(mScriptRuntime.get().loopers.getMainLooper());
}
}
}

View File

@ -55,9 +55,11 @@ public class VMBridge_custom extends VMBridge_jdk18 {
} catch (Throwable e) {
e.printStackTrace();
// notify the script thread to exit
com.stardust.autojs.runtime.ScriptRuntime runtime = engine.getRuntime();
Log.d(LOG_TAG, "runtime = " + runtime);
runtime.exit(e);
if (engine != null) {
com.stardust.autojs.runtime.ScriptRuntime runtime = engine.getRuntime();
Log.d(LOG_TAG, "runtime = " + runtime);
runtime.exit(e);
}
// even if we caught the exception, we must return a value to for the method call.
return defaultValue(method.getReturnType());
}