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(); + } + } }