Если я хочу получить список ApplicationInfo для всех приложений текущего пользователя, я могу просто запустить:
PackageManager pkgmanager = ctx.getPackageManager();
List<ApplicationInfo> installedApps = pkgmanager.getInstalledApplications(PackageManager.GET_META_DATA);
Но я ищу способ получить эти списки для каждого пользователя, а не только для текущего. У приложения, над которым я работаю, есть права доступа root!
Из того, что я понимаю, идентификаторы пользователя могут быть получены следующим образом:
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
просто возвращает имена всех приложений, работающих под заданным идентификатором пользователя, который обычно равен одному. В дополнение к "пользователям" есть также "Профили пользователей", о которых я надеялся, что этот метод имел в виду. Возможно, мне следовало бы написать userProfileId
вместо userId
в моем коде.
Редактирование: Используя оболочку adb, я могу получить идентификаторы приложений следующим образом:
# 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. Так что единственный способ сделать это - использовать оболочку. Вот что я придумала:
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)
}
...
}
}
Но я понятия не имею, если это даже близко к чему-то, что действительно будет работать.
И, кроме того, я получаю только идентификаторы пакетов ("com.whatsapp" и т.д.) Для каждого профиля пользователя, но я хотел бы получить список ApplicationInfo, как getInstalledApplications
возвращает его. Я просто не могу придумать хороший способ сделать это. Может быть, можно было бы загрузить манифесты пакета, а затем как-нибудь создать экземпляры ApplicationInfo на их основе?
Edit3:
Я думаю, что нашел исходный код для исполняемого файла pm
.
Как ни странно, я не смог найти ни одного упоминания о флаге --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
. Во-вторых, мне интересно, как я мог бы сказать, чтобы он возвращал пакеты с определенным профилем пользователя или, вообще говоря, это правильный фрагмент исходного кода.
И, наконец, мне интересно, нужны ли мне для этого права root. В конце концов, проверки if (Process.myUid() != ROOT_UID)
выполняются только для runRemoveProfile
, runCreateProfile
и runListProfiles
.
Edit4:
Мне не удалось найти исходный код службы package
. Мне удалось найти только этот файл: /data/system/packages.xml
. Он содержит некоторую базовую информацию о пакетах для всех пакетов (для всех пользовательских профилей), но не содержит ни имен приложений actall, ни информации о том, к каким профилям пользователей они принадлежат.
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>
. Но, тем не менее, я совершенно не понимаю, как мне получить доступ к этой функциональности.