diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2aadea9b..aa77d01b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -15,6 +15,7 @@
+
diff --git a/app/src/main/java/org/autojs/autojs/ui/floating/CircularMenu.java b/app/src/main/java/org/autojs/autojs/ui/floating/CircularMenu.java
index cde99702..d5ea2e03 100644
--- a/app/src/main/java/org/autojs/autojs/ui/floating/CircularMenu.java
+++ b/app/src/main/java/org/autojs/autojs/ui/floating/CircularMenu.java
@@ -31,6 +31,7 @@ import org.autojs.autojs.ui.floating.layoutinspector.LayoutHierarchyFloatyWindow
import org.autojs.autojs.ui.main.MainActivity_;
import org.autojs.autojs.ui.explorer.ExplorerView;
import org.autojs.autojs.theme.dialog.ThemeColorMaterialDialogBuilder;
+
import com.stardust.util.ClipboardUtil;
import com.stardust.util.Func1;
import com.stardust.view.accessibility.LayoutInspector;
@@ -214,7 +215,7 @@ public class CircularMenu implements Recorder.OnStateChangedListener, LayoutInsp
}
private void inspectLayout(Func1 windowCreator) {
- if(mLayoutInspectDialog != null){
+ if (mLayoutInspectDialog != null) {
mLayoutInspectDialog.dismiss();
mLayoutInspectDialog = null;
}
@@ -260,7 +261,7 @@ public class CircularMenu implements Recorder.OnStateChangedListener, LayoutInsp
@OnClick(R.id.settings)
void settings() {
mWindow.collapse();
- mRunningPackage = AutoJs.getInstance().getInfoProvider().getLatestPackage();
+ mRunningPackage = AutoJs.getInstance().getInfoProvider().getLatestPackageByUsageStatsIfGranted();
mRunningActivity = AutoJs.getInstance().getInfoProvider().getLatestActivity();
mSettingsDialog = new OperationDialogBuilder(mContext)
.item(R.id.accessibility_service, R.drawable.ic_service_green, R.string.text_accessibility_settings)
diff --git a/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerFragment.java b/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerFragment.java
index fea4ac2c..16ebcaa5 100644
--- a/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerFragment.java
+++ b/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerFragment.java
@@ -1,14 +1,17 @@
package org.autojs.autojs.ui.main.drawer;
import android.annotation.SuppressLint;
+import android.app.AppOpsManager;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
@@ -21,6 +24,7 @@ import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.CustomViewTarget;
import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.transition.Transition;
+import com.stardust.app.AppOpsKt;
import com.stardust.app.GlobalAppContext;
import com.stardust.notification.NotificationListenerService;
@@ -113,6 +117,7 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
};
private DrawerMenuItem mNotificationPermissionItem = new DrawerMenuItem(R.drawable.ic_ali_notification, R.string.text_notification_permission, 0, this::goToNotificationServiceSettings);
+ private DrawerMenuItem mUsageStatsPermissionItem = new DrawerMenuItem(R.drawable.ic_ali_notification, R.string.text_usage_stats_permission, 0, this::goToUsageStatsSettings);
private DrawerMenuItem mForegroundServiceItem = new DrawerMenuItem(R.drawable.ic_service_green, R.string.text_foreground_service, R.string.key_foreground_servie, this::toggleForegroundService);
private DrawerMenuItem mFloatingWindowItem = new DrawerMenuItem(R.drawable.ic_robot_64, R.string.text_floating_window, 0, this::showOrDismissFloatingWindow);
@@ -163,6 +168,7 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
mStableModeItem,
mNotificationPermissionItem,
mForegroundServiceItem,
+ mUsageStatsPermissionItem,
new DrawerMenuGroup(R.string.text_script_record),
mFloatingWindowItem,
@@ -226,6 +232,17 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
}
}
+ void goToUsageStatsSettings(DrawerMenuItemViewHolder holder) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ return;
+ }
+ boolean enabled = AppOpsKt.isOpPermissionGranted(getContext(), AppOpsManager.OPSTR_GET_USAGE_STATS);
+ boolean checked = holder.getSwitchCompat().isChecked();
+ if ((checked && !enabled) || (!checked && enabled)) {
+ IntentUtil.requestAppUsagePermission(getContext());
+ }
+ }
+
void showOrDismissFloatingWindow(DrawerMenuItemViewHolder holder) {
boolean isFloatingWindowShowing = FloatyWindowManger.isCircularMenuShowing();
boolean checked = holder.getSwitchCompat().isChecked();
@@ -390,7 +407,9 @@ public class DrawerFragment extends androidx.fragment.app.Fragment {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
setChecked(mNotificationPermissionItem, NotificationListenerService.Companion.getInstance() != null);
}
-
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ setChecked(mUsageStatsPermissionItem, AppOpsKt.isOpPermissionGranted(getContext(), AppOpsManager.OPSTR_GET_USAGE_STATS));
+ }
}
private void enableAccessibilityService() {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 64e4d1e4..94dad2ea 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -273,7 +273,7 @@
重置成功
无法读取文件
服务
- 通知权限
+ 通知读取权限
打开主界面
@@ -429,4 +429,5 @@
夜间模式
key_night_mode
Auto.js启动时
+ 查看使用统计权限
diff --git a/autojs/src/main/assets/modules/__automator__.js b/autojs/src/main/assets/modules/__automator__.js
index b6d45bbd..484f0629 100644
--- a/autojs/src/main/assets/modules/__automator__.js
+++ b/autojs/src/main/assets/modules/__automator__.js
@@ -110,7 +110,8 @@ module.exports = function(runtime, global){
}
const flagsMap = {
- "findOnUiThread": 1
+ "findOnUiThread": 1,
+ "useUsageStats": 2
};
var auto = function(mode){
diff --git a/autojs/src/main/java/com/stardust/autojs/AutoJs.java b/autojs/src/main/java/com/stardust/autojs/AutoJs.java
index 707eeaed..88c45854 100644
--- a/autojs/src/main/java/com/stardust/autojs/AutoJs.java
+++ b/autojs/src/main/java/com/stardust/autojs/AutoJs.java
@@ -5,6 +5,7 @@ import android.app.Application;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
+
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -67,7 +68,7 @@ public abstract class AutoJs {
mAppUtils = createAppUtils(mContext);
mGlobalConsole = createGlobalConsole();
mNotificationObserver = new AccessibilityNotificationObserver(mContext);
- mAccessibilityInfoProvider = new AccessibilityInfoProvider(mContext.getPackageManager());
+ mAccessibilityInfoProvider = new AccessibilityInfoProvider(mContext);
mScriptEngineService = buildScriptEngineService();
ScriptEngineService.setInstance(mScriptEngineService);
init();
@@ -205,7 +206,7 @@ public abstract class AutoJs {
private class AccessibilityBridgeImpl extends AccessibilityBridge {
public AccessibilityBridgeImpl(UiHandler uiHandler) {
- super(createAccessibilityConfig(), uiHandler);
+ super(mContext, createAccessibilityConfig(), uiHandler);
}
@Override
diff --git a/autojs/src/main/java/com/stardust/autojs/core/accessibility/AccessibilityBridge.java b/autojs/src/main/java/com/stardust/autojs/core/accessibility/AccessibilityBridge.java
index abb61d7d..8adaf42f 100644
--- a/autojs/src/main/java/com/stardust/autojs/core/accessibility/AccessibilityBridge.java
+++ b/autojs/src/main/java/com/stardust/autojs/core/accessibility/AccessibilityBridge.java
@@ -1,12 +1,18 @@
package com.stardust.autojs.core.accessibility;
+import android.app.AppOpsManager;
+import android.content.Context;
import android.os.Build;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
+import com.stardust.app.AppOpsKt;
import com.stardust.autojs.runtime.accessibility.AccessibilityConfig;
+import com.stardust.util.IntentUtil;
import com.stardust.util.UiHandler;
import com.stardust.view.accessibility.AccessibilityInfoProvider;
import com.stardust.view.accessibility.AccessibilityNotificationObserver;
@@ -27,17 +33,20 @@ public abstract class AccessibilityBridge {
public static final int MODE_FAST = 1;
public static final int FLAG_FIND_ON_UI_THREAD = 1;
+ public static final int FLAG_USE_USAGE_STATS = 2;
private int mMode = MODE_NORMAL;
private int mFlags = 0;
private final AccessibilityConfig mConfig;
private WindowFilter mWindowFilter;
private final UiHandler mUiHandler;
+ private final Context mContext;
- public AccessibilityBridge(AccessibilityConfig config, UiHandler uiHandler) {
+ public AccessibilityBridge(Context context, AccessibilityConfig config, UiHandler uiHandler) {
mConfig = config;
mUiHandler = uiHandler;
mConfig.seal();
+ mContext = context;
}
public abstract void ensureServiceEnabled();
@@ -80,7 +89,6 @@ public abstract class AccessibilityBridge {
mWindowFilter = windowFilter;
}
- @NonNull
public abstract AccessibilityInfoProvider getInfoProvider();
public void setMode(int mode) {
@@ -93,6 +101,13 @@ public abstract class AccessibilityBridge {
public void setFlags(int flags) {
mFlags = flags;
+ if ((mFlags & FLAG_USE_USAGE_STATS) != 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ if (!AppOpsKt.isOpPermissionGranted(mContext, AppOpsManager.OPSTR_GET_USAGE_STATS)) {
+ IntentUtil.requestAppUsagePermission(mContext);
+ throw new SecurityException("没有\"查看使用情况\"权限");
+ }
+ }
+ getInfoProvider().setUseUsageStats((mFlags & FLAG_USE_USAGE_STATS) != 0);
}
@NonNull
diff --git a/automator/src/main/java/com/stardust/view/accessibility/AccessibilityInfoProvider.kt b/automator/src/main/java/com/stardust/view/accessibility/AccessibilityInfoProvider.kt
index 453070fb..581293f3 100644
--- a/automator/src/main/java/com/stardust/view/accessibility/AccessibilityInfoProvider.kt
+++ b/automator/src/main/java/com/stardust/view/accessibility/AccessibilityInfoProvider.kt
@@ -1,33 +1,41 @@
package com.stardust.view.accessibility
import android.accessibilityservice.AccessibilityService
+import android.app.AppOpsManager
+import android.app.usage.UsageStatsManager
import android.content.ComponentName
import android.content.Context
-import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
+import android.os.Build
import android.view.accessibility.AccessibilityEvent
-import android.view.accessibility.AccessibilityNodeInfo
-
-import com.stardust.view.accessibility.AccessibilityDelegate
-
-import java.util.Arrays
-import java.util.Collections
-import java.util.HashSet
+import androidx.annotation.RequiresApi
+import com.stardust.app.isOpPermissionGranted
/**
* Created by Stardust on 2017/3/9.
*/
-class AccessibilityInfoProvider(private val mPackageManager: PackageManager) : AccessibilityDelegate {
+class AccessibilityInfoProvider(private val context: Context) : AccessibilityDelegate {
+ private val mPackageManager: PackageManager = context.packageManager
@Volatile
- var latestPackage = ""
- private set
+ private var mLatestPackage: String = ""
+
+ val latestPackage: String
+ get() {
+ if (useUsageStats && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
+ mLatestPackage = getLatestPackageByUsageStats()
+ }
+ return mLatestPackage
+ }
+
@Volatile
var latestActivity = ""
private set
+ var useUsageStats: Boolean = true
+
override val eventTypes: Set?
get() = AccessibilityDelegate.ALL_EVENT_TYPES
@@ -38,6 +46,29 @@ class AccessibilityInfoProvider(private val mPackageManager: PackageManager) : A
return false
}
+ fun getLatestPackageByUsageStatsIfGranted(): String {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 && context.isOpPermissionGranted(AppOpsManager.OPSTR_GET_USAGE_STATS)) {
+ return getLatestPackageByUsageStats()
+ }
+ return mLatestPackage
+ }
+
+ @RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1)
+ fun getLatestPackageByUsageStats(): String {
+ val usageStatsManager = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
+ val current = System.currentTimeMillis()
+ val usageStats = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, current - 60 * 60 * 1000, current)
+ return if (usageStats.isEmpty()) {
+ ""
+ } else {
+ usageStats.sortBy {
+ it.lastTimeStamp
+ }
+ usageStats.last().packageName
+ }
+
+ }
+
private fun setLatestComponent(latestPackage: CharSequence?, latestClass: CharSequence?) {
if (latestPackage == null || latestClass == null)
return
@@ -51,7 +82,6 @@ class AccessibilityInfoProvider(private val mPackageManager: PackageManager) : A
} catch (ignored: PackageManager.NameNotFoundException) {
return
}
-
- this.latestPackage = latestPackage.toString()
+ mLatestPackage = latestPackage.toString()
}
}
diff --git a/common/src/main/java/com/stardust/app/AppOps.kt b/common/src/main/java/com/stardust/app/AppOps.kt
new file mode 100644
index 00000000..40b7bb51
--- /dev/null
+++ b/common/src/main/java/com/stardust/app/AppOps.kt
@@ -0,0 +1,20 @@
+package com.stardust.app
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import androidx.annotation.RequiresApi
+
+
+@RequiresApi(Build.VERSION_CODES.KITKAT)
+fun Context.isOpPermissionGranted(permission: String): Boolean {
+ val appOps = getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
+ val mode = appOps.checkOpNoThrow(permission, android.os.Process.myUid(), packageName)
+
+ return if (mode == AppOpsManager.MODE_DEFAULT && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ checkCallingOrSelfPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) == PackageManager.PERMISSION_GRANTED
+ } else {
+ mode == AppOpsManager.MODE_ALLOWED
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/java/com/stardust/util/IntentUtil.java b/common/src/main/java/com/stardust/util/IntentUtil.java
index 100038b2..2620b03b 100644
--- a/common/src/main/java/com/stardust/util/IntentUtil.java
+++ b/common/src/main/java/com/stardust/util/IntentUtil.java
@@ -4,8 +4,12 @@ import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import androidx.core.content.FileProvider;
+
+import android.os.Build;
import android.widget.Toast;
import com.stardust.R;
@@ -182,4 +186,15 @@ public class IntentUtil {
return false;
}
}
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ public static void requestAppUsagePermission(Context context) {
+ Intent intent = new Intent(android.provider.Settings.ACTION_USAGE_ACCESS_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ context.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
}