如果我想检索当前用户的所有应用程序的 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 ......
Edit2:如果我错了,请纠正我(这是我第一次用 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
返回它一样。我只是想不出一个好的方法来做到这一点。也许可以加载包清单,然后以某种方式基于它们创建 ApplicationInfo 实例?
奇怪的是,我在其中找不到任何提及--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
then:
@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
课程。其次,我想知道如何告诉它返回特定用户配置文件的包,或者这是否是正确的源代码。最后我想知道我是否需要root权限才能使用它。毕竟,if (Process.myUid() != ROOT_UID)
检查只针对runRemoveProfile
,runCreateProfile
和执行runListProfiles
。
Edit4:我找不到package
服务的源代码。我只能找到这个文件:/data/system/packages.xml
. 它包含所有包的一些基本包信息(对于所有用户配置文件),但它既不包含实际的应用程序名称,也不包含有关它们所属的用户配置文件的信息。
Edit5:我想我找到了包服务源代码。我认为这个方法很重要:
public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId)
不幸的是,我只是不明白代码。对我来说,这些包裹似乎来自mSettings.mPackages
. 该变量在代码注释中解释如下:
{@link #mPackages} 用于保护所有内存中解析的包详细信息和其他相关状态。这是一个细粒度的锁,只能暂时持有,因为它是系统中竞争最激烈的锁之一。
Edit6:我刚刚在该文件中发现了另一种更有趣的方法:
public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags, int userId)
我不知道 ParceledListSlice 是什么,但它说<ApplicationInfo>
我假设这真的很接近我想要的List<ApplicationInfo>
格式。但是,我仍然完全不知道如何访问该功能。