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

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

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

1. ActivityManagerService源码分析

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

    public List getRunningAppProcesses() {
        enforceNotIsolatedCaller("getRunningAppProcesses");

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

        // Lazy instantiation of list
        List 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 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则需要系统签名。

    
    

    
    

至此,权限已经理清,开发者若想调用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


    
        
            
        
        
    

Service Info

在res/xml/accessibilityservice.xml中添加



3)Android O

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

你可能感兴趣的:(android)