Android权限探究——获取正在运行的应用/进程列表

Android5.0以前,可以通过ActivityManagerService.getRunningAppProcesses接口获取系统中正在运行的app进程信息。

但之后的Android版本,此接口只能获取到调用者自己的进程信息,这是为什么呢?本文将一探究竟。

1. ActivityManagerService源码分析

首先看一下Android 8.1 的ActivityManagerService源码。不难理解其逻辑:通过遍历mLruProcesses列表获取正在运行当进程信息,对进程进行过滤,构造RunningAppProcessInfo对象返回给调用者。而对进程信息过滤的过程,就是权限检测的过程。本文分析的重点就是理清权限检测的规则,以及在无法获取相应权限的情况下,如何获取running process。

    public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses() {
        enforceNotIsolatedCaller("getRunningAppProcesses");

        final int callingUid = Binder.getCallingUid();
        final int clientTargetSdk = mPackageManagerInt.getUidTargetSdkVersion(callingUid);

        // Lazy instantiation of list
        List<ActivityManager.RunningAppProcessInfo> runList = null;
        final boolean allUsers = ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL,
                callingUid) == PackageManager.PERMISSION_GRANTED;
        final int userId = UserHandle.getUserId(callingUid);
        final boolean allUids = isGetTasksAllowed(
                "getRunningAppProcesses", Binder.getCallingPid(), callingUid);

        synchronized (this) {
            // Iterate across all processes
            for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
                ProcessRecord app = mLruProcesses.get(i);
                if ((!allUsers && app.userId != userId)
                        || (!allUids && app.uid != callingUid)) {
                    continue;
                }
                if ((app.thread != null) && (!app.crashing && !app.notResponding)) {
                    // Generate process state info for running application
                    ActivityManager.RunningAppProcessInfo currApp =
                        new ActivityManager.RunningAppProcessInfo(app.processName,
                                app.pid, app.getPackageList());
                    fillInProcMemInfo(app, currApp, clientTargetSdk);
                    if (app.adjSource instanceof ProcessRecord) {
                        currApp.importanceReasonPid = ((ProcessRecord)app.adjSource).pid;
                        currApp.importanceReasonImportance =
                                ActivityManager.RunningAppProcessInfo.procStateToImportance(
                                        app.adjSourceProcState);
                    } else if (app.adjSource instanceof ActivityRecord) {
                        ActivityRecord r = (ActivityRecord)app.adjSource;
                        if (r.app != null) currApp.importanceReasonPid = r.app.pid;
                    }
                    if (app.adjTarget instanceof ComponentName) {
                        currApp.importanceReasonComponent = (ComponentName)app.adjTarget;
                    }
                    //Slog.v(TAG, "Proc " + app.processName + ": imp=" + currApp.importance
                    //        + " lru=" + currApp.lru);
                    if (runList == null) {
                        runList = new ArrayList<>();
                    }
                    runList.add(currApp);
                }
            }
        }
        return runList;
    }

2. 权限探究

以上源码中,过滤进程信息依靠如下逻辑:

if ((!allUsers && app.userId != userId)
    || (!allUids && app.uid != callingUid)) {
        continue;
}

只有当allUsers 为真,且allUids为真时,处理所有running process. 否则只处理调用者本身的进程信息。而allUsers 和allUids何时为真呢?

1). allUsers

allUsers的值通过调用ActivityManager.checkUidPermission接口,判断是否调用者是否具备INTERACT_ACROSS_USERS_FULL权限,源码如下:

ActivityManager.java

    public static int checkUidPermission(String permission, int uid) {
        try {
            return AppGlobals.getPackageManager()
                    .checkUidPermission(permission, uid);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

其最终调用的是PackageManagerService.checkUidPermission()接口。

    public int checkUidPermission(String permName, int uid) {
        final int callingUid = Binder.getCallingUid();
        final int callingUserId = UserHandle.getUserId(callingUid);
        final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
        final boolean isUidInstantApp = getInstantAppPackageName(uid) != null;
        final int userId = UserHandle.getUserId(uid);
        if (!sUserManager.exists(userId)) {
            return PackageManager.PERMISSION_DENIED;
        }

        synchronized (mPackages) {
            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
            if (obj != null) {
                if (obj instanceof SharedUserSetting) {
                    if (isCallerInstantApp) {
                        return PackageManager.PERMISSION_DENIED;
                    }
                } else if (obj instanceof PackageSetting) {
                    final PackageSetting ps = (PackageSetting) obj;
                    if (filterAppAccessLPr(ps, callingUid, callingUserId)) {
                        return PackageManager.PERMISSION_DENIED;
                    }
                }
                final SettingBase settingBase = (SettingBase) obj;
                final PermissionsState permissionsState = settingBase.getPermissionsState();
                if (permissionsState.hasPermission(permName, userId)) {
                    if (isUidInstantApp) {
                        BasePermission bp = mSettings.mPermissions.get(permName);
                        if (bp != null && bp.isInstant()) {
                            return PackageManager.PERMISSION_GRANTED;
                        }
                    } else {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                }
                // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
                if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
                        .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
            } else {
                ArraySet<String> perms = mSystemPermissions.get(uid);
                if (perms != null) {
                    if (perms.contains(permName)) {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                    if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
                            .contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                }
            }
        }

        return PackageManager.PERMISSION_DENIED;
    }

不难发现,有两种情况能够获取此权限:

1. 调用者为用户应用 

当调用者是用户应用,首先通过getInstantAppPackageName接口判断其是否是Google 小程序,若是,则检查其base app 是否具有INTERACT_ACROSS_USERS_FULL权限。如果是普通应用,则直接检查其权限。

2. 调用者为系统应用

检查/frameworks/base/data/etc/platform.xml中是否定义了此权限,若有则返回成功。

2) allUids

allUids判断逻辑相对简单,通过isGetTasksAllowed方法判断。

当调用者具有REAL_GET_TASKS,返回成功。

当调用者具有GET_TASKS权限且为系统应用时,返回成功。不过GET_TASKS权限即将被Google抛弃,此处只是为兼容性加的temporary代码。

    private boolean isGetTasksAllowed(String caller, int callingPid, int callingUid) {
        boolean allowed = checkPermission(android.Manifest.permission.REAL_GET_TASKS,
                callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
        if (!allowed) {
            if (checkPermission(android.Manifest.permission.GET_TASKS,
                    callingPid, callingUid) == PackageManager.PERMISSION_GRANTED) {
                // Temporary compatibility: some existing apps on the system image may
                // still be requesting the old permission and not switched to the new
                // one; if so, we'll still allow them full access.  This means we need
                // to see if they are holding the old permission and are a system app.
                try {
                    if (AppGlobals.getPackageManager().isUidPrivileged(callingUid)) {
                        allowed = true;
                        if (DEBUG_TASKS) Slog.w(TAG, caller + ": caller " + callingUid
                                + " is using old GET_TASKS but privileged; allowing");
                    }
                } catch (RemoteException e) {
                }
            }
        }
        if (!allowed) {
            if (DEBUG_TASKS) Slog.w(TAG, caller + ": caller " + callingUid
                    + " does not hold REAL_GET_TASKS; limiting output");
        }
        return allowed;
    }

而要获取REAL_GET_TASKS则需要系统签名。

    <!-- @deprecated No longer enforced. -->
    <permission android:name="android.permission.GET_TASKS"
        android:label="@string/permlab_getTasks"
        android:description="@string/permdesc_getTasks"
        android:protectionLevel="normal" />

    <!-- New version of GET_TASKS that apps can request, since GET_TASKS doesn't really
         give access to task information.  We need this new one because there are
         many existing apps that use add libraries and such that have validation
         code to ensure the app has requested the GET_TASKS permission by seeing
         if it has been granted the permission...  if it hasn't, it kills the app
         with a message about being upset.  So we need to have it continue to look
         like the app is getting that permission, even though it will never be
         checked, and new privileged apps can now request this one for real access.
         @hide
         @SystemApi -->
    <permission android:name="android.permission.REAL_GET_TASKS"
        android:protectionLevel="signature|privileged" />

至此,权限已经理清,开发者若想调用getRunningAppProcesses接口获取正在运行进程列表,必须将app集成为系统应用,或者具有系统签名。显而易见,对于大部分第三方开发者,这是不现实的,那么还有没有其他的方法呢?答案是肯定的,不过稍复杂些。

3. 获取正在运行process的其他方法

1)Android M

①Android 6.0系统可参考github : https://github.com/jaredrummler/AndroidProcesses 此方法不需要任何权限。

②可以使用UsageStatsManager,但某些系统APP返回同样的包名。

2)Android N

在更高版本的安卓系统上,可以使用AccessibilityService不过此方法违反Google Play规范,可能被踢出Google Play市场。

使用AccessibilityServices

  • 通过AccessibilityService获取当前活跃窗口
  • 在onAccessibilityEvent回调中,监测TYPE_WINDOW_STATE_CHANGED消息,来检查current window的改变。
  • 通过packageManager.getActivityInfo()检查window是否是一个activity

优点

  • 经测试 Android 2.2 (API 8) 到Android 7.1 (API 25)系统均可用
  • 不需要轮询.
  • 不需要REAL_GET_TASKS/ GET_TASKS权限.

缺点

  • 每个用户都需要在辅助功能中打开服务.
  • 服务会一直运行
  • 当用户尝试打开AccessibilityService, 如果一个应用覆盖或者overlay屏幕,他们将不能点击确认按钮. 
  • 直到第一次activity改变,AccessibilityService才能检测到。

例子

service

public class WindowChangeDetectingService extends AccessibilityService {

    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();

        //Configure these here for compatibility with API 13 and below.
        AccessibilityServiceInfo config = new AccessibilityServiceInfo();
        config.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
        config.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;

        if (Build.VERSION.SDK_INT >= 16)
            //Just in case this helps
            config.flags = AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;

        setServiceInfo(config);
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            if (event.getPackageName() != null && event.getClassName() != null) {
                ComponentName componentName = new ComponentName(
                    event.getPackageName().toString(),
                    event.getClassName().toString()
                );

                ActivityInfo activityInfo = tryGetActivity(componentName);
                boolean isActivity = activityInfo != null;
                if (isActivity)
                    Log.i("CurrentActivity", componentName.flattenToShortString());
            }
        }
    }

    private ActivityInfo tryGetActivity(ComponentName componentName) {
        try {
            return getPackageManager().getActivityInfo(componentName, 0);
        } catch (PackageManager.NameNotFoundException e) {
            return null;
        }
    }

    @Override
    public void onInterrupt() {}
}

AndroidManifest.xml

<application>
    <service
        android:label="@string/accessibility_service_name"
        android:name=".WindowChangeDetectingService"
        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
        <intent-filter>
            <action android:name="android.accessibilityservice.AccessibilityService"/>
        </intent-filter>
        <meta-data
            android:name="android.accessibilityservice"
            android:resource="@xml/accessibilityservice"/>
    </service>
</application>

Service Info

在res/xml/accessibilityservice.xml中添加

<?xml version="1.0" encoding="utf-8"?>
<!-- These options MUST be specified here in order for the events to be received on first
 start in Android 4.1.1 -->
<accessibility-service
    xmlns:tools="http://schemas.android.com/tools"
    android:accessibilityEventTypes="typeWindowStateChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagIncludeNotImportantViews"
    android:description="@string/accessibility_service_description"
    xmlns:android="http://schemas.android.com/apk/res/android"
    tools:ignore="UnusedAttribute"/>

3)Android O

目前没有其他方法,只能集成到system/app目录。

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值