java - 获取所有用户的应用列表

标签 java android android-package-managers android-multiple-users android-work-profile

如果我想检索当前用户的所有应用程序的 ApplicationInfo 列表,我可以运行:

PackageManager pkgmanager = ctx.getPackageManager();
List<ApplicationInfo> installedApps = pkgmanager.getInstalledApplications(PackageManager.GET_META_DATA);

但我正在寻找一种方法来为每个用户获取这些列表,而不仅仅是当前用户。顺便说一句,我正在开发的应用程序具有 root 权限!

据我了解,用户 ID 可以这样检索:

List<Integer> userIds = new ArrayList<>();
PackageManager pkgmanager = ctx.getPackageManager();
final UserManager um = (UserManager) ctx.getSystemService(Context.USER_SERVICE);
List<UserHandle> list = um.getUserProfiles();

for (UserHandle user : list) {
    Matcher m = p.matcher(user.toString());
    if (m.find()) {
        int id = Integer.parseInt(m.group(1));
            userIds.add(id);
    }
}

现在我正在寻找这样的东西:

for (int i=0; i<userIds.size(); i++) {
    Integer userId = userIds.get(i)
    List<ApplicationInfo> installedApps = pkgmanager.getInstalledApplicationsByUserId(userId, PackageManager.GET_META_DATA);
}

显然 getInstalledApplicationsByUserId不存在。

一开始我以为getPackagesForUid可以解决问题,但事实证明,Linux 意义上的“用户”和用户配置文件之间存在差异。通常,出于隔离目的,每个 Android 应用程序都在其自己的用户下运行。但是可以在同一用户下运行两个应用程序,以便他们可以轻松地访问彼此的数据。 getPackagesForUid仅返回在给定用户 id 下运行的所有应用程序的名称,通常正好是一个。除了“用户”之外,还有“用户配置文件”,这是我希望该方法所指的内容。也许我也应该写userProfileId而不是 userId在我的代码中。

编辑: 使用 adb shell,我可以像这样检索应用程序 ID:

# Get user profile IDs
USER_PROFILE_IDS="$(pm list users | grep UserInfo | cut -d '{' -f2 | cut -d ':' -f1)"

# Iterate over user profile IDs
while read -r USER_PROFILE_ID ; do
    # List the packages for each user profile by ID
    PACKAGE_IDS_FOR_THIS_USER="$(pm list packages --user "$USER_PROFILE_ID" | cut -d ':' -f2)"
    echo "#######################################################################"
    echo "The user with id $USER_PROFILE_ID has the following packages installed:"
    echo "$PACKAGE_IDS_FOR_THIS_USER"
done <<< "$USER_PROFILE_IDS"

但那是 Bash 而不是 Java...

编辑2: 如果我错了,请纠正我(这是我第一次用 Java 编写代码),但似乎没有 Java API 可以做到这一点。因此,唯一的方法是使用 shell。所以这就是我想出的:

import com.stericson.rootshell.RootShell;
import com.stericson.rootshell.execution.Command;
import com.stericson.rootshell.execution.Shell;
import com.stericson.roottools.RootTools;
...
public final class Api {
    ...
    /**
     * @param ctx application context (mandatory)
     * @return a list of user profile ids
     */
    private static List<Integer> getUserIds(Context ctx) {
        List<Integer> userIds = new ArrayList<>();
        PackageManager pkgmanager = ctx.getPackageManager();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            //this code will be executed on devices running ICS or later
            final UserManager um = (UserManager) ctx.getSystemService(Context.USER_SERVICE);
            List<UserHandle> list = um.getUserProfiles();

            for (UserHandle user : list) {
                Matcher m = p.matcher(user.toString());
                if (m.find()) {
                    int id = Integer.parseInt(m.group(1));
                    //if (id > 0) {
                        userIds.add(id);
                    //}
                }
            }
        } else {
            userIds.add(0);
        }
        return userIds;
    }

    /**
     * @param ctx application context (mandatory)
     * @return a list of user profile ids
     */
    private static List<String> getPackageIdsByUserProfileId(Integer userId) {
        List<String> packageIds = new ArrayList<>();
        Command command = new Command(0, "pm list packages --user " + userId + " | cut -d ':' -f2 ")
        {
            @Override
            public void commandOutput(int id, String line) { 
                packageIds.add(line);
                super.commandOutput(id, line);
            }
        };
        Shell shell = RootTools.getShell(true);
        shell.add(command);

        while (!command.isFinished()) {
            Thread.sleep(100);
        }
        return packageIds;
    }
...
    /**
     * @param ctx application context (mandatory)
     * @return a list of applications
     */
    public static List<PackageInfoData> getApps(Context ctx, GetAppList appList) {
        List<Integer> userIds = getUserIds();
        for (int i=0; i<userIds.size(); i++) {
            Integer userId = userIds.get(i)
            List<String> packageIds = getPackageIdsByUserProfileId(userId)
        }
        ...
    }
}

但我不知道这是否接近实际可行的东西。 除此之外,我只获得每个用户配置文件的包 ID(“com.whatsapp”等),但我想获得 ApplicationInfo 的列表就像 getInstalledApplications返回它。我只是想不出一个好的方法来做到这一点。也许可以加载包 list ,然后以某种方式基于它们创建 ApplicationInfo 实例?

编辑3: 我想我找到了source code for the pm executable .

奇怪的是,我找不到任何提及 --user 的内容。标记在里面。

代码中最相关的部分是:

import android.os.ServiceManager;
...
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
...
int getFlags = 0;
...
final List<PackageInfo> packages = getInstalledPackages(mPm, getFlags);

getInstalledPackages方法只需调用 mPm.getInstalledPackages那么:

@SuppressWarnings("unchecked")
private List<PackageInfo> getInstalledPackages(IPackageManager pm, int flags)
        throws RemoteException {
    final List<PackageInfo> packageInfos = new ArrayList<PackageInfo>();
    PackageInfo lastItem = null;
    ParceledListSlice<PackageInfo> slice;
    do {
        final String lastKey = lastItem != null ? lastItem.packageName : null;
        slice = pm.getInstalledPackages(flags, lastKey);
        lastItem = slice.populateList(packageInfos, PackageInfo.CREATOR);
    } while (!slice.isLastSlice());
    return packageInfos;
}

这给我留下了比以前更多的问题。首先我想知道我是否不能只导入 com.android.commands.pm类(class)。其次,我想知道如何告诉它返回特定用户配置文件的包,或者这是否是正确的源代码。 最后我想知道我是否需要root权限才能使用它。毕竟,if (Process.myUid() != ROOT_UID)仅对 runRemoveProfile 执行检查, runCreateProfilerunListProfiles .

编辑4: 我找不到 package 的源代码服务。我只能找到这个文件:/data/system/packages.xml .它包含所有包的一些基本包信息(对于所有用户配置文件),但它既不包含实际的应用程序名称,也不包含有关它们所属的用户配置文件的信息。

编辑5: 我想我找到了package service source code .我认为这个方法很重要:

public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId)

不幸的是,我只是不懂代码。在我看来,这些包裹似乎来自mSettings.mPackages .该变量在代码注释中解释如下:

{@link #mPackages} is used to guard all in-memory parsed package details and other related state. It is a fine-grained lock that should only be held momentarily, as it's one of the most contended locks in the system.

编辑6: 我刚刚在该文件中发现了另一种更有趣的方法:

public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags, int userId)

我不知道 ParceledListSlice 是什么,但是它说 <ApplicationInfo>我假设这真的很接近我想要的 List<ApplicationInfo>格式。但是,我仍然完全不知道如何访问该功能。

最佳答案

我找到了 ParceledListSlice here 的代码,以及它实现的Parcelable接口(interface)的代码here .看起来它在内部使用了某种形式的数组,因此您应该能够对其进行迭代。我实际上不能说这是否对您有帮助,因为我对这些库并不是很熟悉,但我希望这是一个起点。祝你好运!

关于java - 获取所有用户的应用列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57266451/

相关文章:

java - gRPC-java - .proto 文件的编译

java - 如何在不删除格式设置的情况下将 Html 转换为字符串?

java - 如何在 JavaFX 中重新运行程序

android - Google plus 登录在 AuthLogin 上显示虚拟白色 AlertDialog

android - 使用 NDK 构建 Android openssl 无法正确生成 arm4 程序集文件

android - PackageManager 为所有应用程序抛出 NameNotFoundException,甚至是我自己的应用程序

android - 如何检测 Android 设备中的新应用

java - 在 ListView 中获取搜索数据的问题

java - Android - 使用不同高度的 child 更新 ViewPager

java - 我怎样才能反序列化这个JSON