Si je veux récupérer une liste ApplicationInfo pour toutes les applications de l'utilisateur actuel, je peux simplement exécuter:
PackageManager pkgmanager = ctx.getPackageManager();
List<ApplicationInfo> installedApps = pkgmanager.getInstalledApplications(PackageManager.GET_META_DATA);
Mais je cherche un moyen d'obtenir ces listes pour chaque utilisateur et pas seulement celui actuel. L'application sur laquelle je travaille a des autorisations root btw!
D'après ce que je comprends, les identifiants d'utilisateur peuvent être récupérés comme ceci:
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);
}
}
Maintenant je cherche quelque chose comme ça:
for (int i=0; i<userIds.size(); i++) {
Integer userId = userIds.get(i)
List<ApplicationInfo> installedApps = pkgmanager.getInstalledApplicationsByUserId(userId, PackageManager.GET_META_DATA);
}
De toute évidence, getInstalledApplicationsByUserId
n'existe pas.
Au début, je pensais que getPackagesForUid pourrait résoudre le problème, mais il s'avère qu'il y a une différence entre les "utilisateurs" au sens Linux et les profils utilisateur. Généralement, chaque application Android s'exécute sous son propre utilisateur à des fins d'isolement. Mais il est possible d'exécuter deux applications sous le même utilisateur afin qu'elles puissent accéder facilement aux données de l'autre. getPackagesForUid
renvoie simplement les noms de toutes les applications qui s'exécutent sous l'ID utilisateur donné, qui est généralement exactement un. En plus des "utilisateurs", il existe également des "Profils utilisateur", ce à quoi j'espérais que la méthode faisait référence. aurait également dû écrire userProfileId
au lieu de userId
dans mon code.
Modifier : En utilisant adb Shell, je peux récupérer les ID d'application comme ceci:
# 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"
Mais c'est Bash et pas Java ...
Edit2 : Corrigez-moi si je me trompe (c'est la première fois que j'écris du code en Java) mais il n'apparaît pas comme il y en a a Java API pour ce faire. Donc, la seule façon de le faire serait d'utiliser un shell. Voici donc ce que j'ai trouvé:
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)
}
...
}
}
Mais je n'ai aucune idée si cela est même proche de quelque chose qui fonctionnerait réellement. Et en plus de cela, je ne reçois que les ID de package ("com.whatsapp", etc.) pour chaque profil utilisateur, mais je voudrais obtenir une liste de ApplicationInfo tout comme getInstalledApplications
le renvoie . Je ne peux tout simplement pas penser à une bonne façon de procéder. Peut-être serait-il possible de charger les manifestes de packages, puis de créer des instances ApplicationInfo en fonction de ceux-ci?
Edit3 : Je pense avoir trouvé le code source pour l'exécutable pm
.
Curieusement, je n'ai pas pu trouver une seule mention du drapeau --user
Dedans.
Les parties les plus pertinentes du code sont:
import Android.os.ServiceManager;
...
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
...
int getFlags = 0;
...
final List<PackageInfo> packages = getInstalledPackages(mPm, getFlags);
La méthode getInstalledPackages
appelle simplement mPm.getInstalledPackages
Puis:
@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;
}
Cela me laisse plus de questions qu'auparavant. Tout d'abord, je me demande si je ne pourrais pas simplement importer la classe com.Android.commands.pm
. Deuxièmement, je me demande comment je pourrais lui dire de renvoyer les packages d'un profil utilisateur spécifique ou si c'est même le bon morceau de code source en premier lieu. Et enfin, je me demande si j'ai même besoin d'autorisations root pour l'utiliser. Après tout, les vérifications if (Process.myUid() != ROOT_UID)
ne sont exécutées que pour runRemoveProfile
, runCreateProfile
et runListProfiles
.
Edit4 : Je n'ai pas pu trouver le code source du service package
. Je n'ai pu trouver que ce fichier: /data/system/packages.xml
. Il contient des informations de base sur tous les packages (pour tous les profils utilisateur), mais il ne contient ni les noms des applications actuall, ni les informations sur les profils utilisateur auxquels ils appartiennent.
Edit5 : Je pense avoir trouvé le code source du service de package . Je pense que cette méthode est la plus importante:
public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId)
Malheureusement, je ne comprends tout simplement pas le code. Pour moi, il semble que les paquets proviennent en quelque sorte de mSettings.mPackages
. La variable est expliquée comme suit dans un commentaire de code:
{@link #mPackages} est utilisé pour protéger tous les détails du package analysé en mémoire et tout autre état associé. C'est une serrure à grain fin qui ne devrait être maintenue que momentanément, car c'est l'une des serrures les plus contestées du système.
Edit6 : Je viens de trouver une autre méthode encore plus intéressante dans ce fichier:
public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags, int userId)
Je ne sais pas ce qu'est ParceledListSlice, mais il est dit <ApplicationInfo>
Je suppose que c'est vraiment proche de mon format List<ApplicationInfo>
Voulu. Mais encore, je ne sais absolument pas comment je pourrais accéder à cette fonctionnalité.
J'ai trouvé le code de ParceledListSlice
ici , et le code de l'interface Parcelable
qu'il implémente ici . Il semble qu'il utilise une certaine forme de tableau en interne, vous devriez donc être en mesure de répéter cela. Je ne peux pas vraiment dire si cela vous sera utile ou non, car je ne connais pas très bien ces bibliothèques, mais j'espère que c'est un point de départ. Bonne chance!
Vous pouvez appliquer l'approche que vous avez mentionnée dans vos premiers EDIT et EDIT2 et utiliser le shell d'Android, car vous avez mentionné que votre appareil dispose des autorisations root.
Soit dit en passant, je ne connais pas la bibliothèque de "stericson" que vous utilisez, mais je suis d'accord avec l'utilisation des bibliothèques existantes pour vous faciliter la vie; J'utilise libsuperuser . Sinon, vous pouvez écrire du code pour exécuter vous-même des commandes Shell à l'aide de Processus, etc. (mais il y a beaucoup de choses à considérer avec la gestion de la sortie d'erreur, la fermeture des objets, etc.).
private List<String> listAllInstalledApps(){
String user = "0"; //can modify to iterate over all users
if (Shell.SU.available()) {
//grab user installed package names
List<String> output =
Shell.SU.run("pm list packages --user " + user + " -3");
StringBuilder csvBuilder = new StringBuilder();
for(String item : output){
csvBuilder.append(item);
csvBuilder.append(", ");
}
Log.info(TAG, "Obtained installed apps: " + csvBuilder.toString());
return output;
}
return new ArrayList<>();
}
Bien sûr, vous pouvez utiliser d'autres arguments pour pm si nécessaire, tels que -e, -d
voir documentation .
Une fois que vous avez extrait les noms des packages, obtenez toutes les informations requises (comme celles contenues dans ApplicationInfo
) en utilisant dumpsys
//package names extracted from List<String> you returned earlier
for( String name : packageNames ){
List<String> appInfo =
Shell.SU.run("dumpsys package " + name);
//search appInfo for info you need e.g. grantedPermissions, targetSdk...
}
Créez des objets ApplicationInfo comme requis, si vous avez du code en aval qui requiert spécifiquement ce type d'objet
ApplicationInfo info = new ApplicationInfo();
info.uid = uid;
info.processName = processName;
info.className = classname;
info.packageName = packageName;
info.minSdkVersion = minSdkVersion;
//etc...
(les champs de la classe ApplicationInfo
sont publics). Cela est également vrai pour PackageInfo
qui est un objet qui contient un champ ApplicationInfo
, en plus il contient des détails sur le temps d'installation, etc. qui sont également des détails que vous pouvez extraire de la sortie de dumpsys. Vous pouvez donc créer un tableau de ceux-ci comme vous le souhaitez et pouvez être rempli avec vos nouveaux objets ApplicationInfo
.
Obtenir la liste des applications installées par programme
ajouter du code à activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:tools="http://schemas.Android.com/tools"
Android:id="@+id/rl"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:padding="16dp"
tools:context=".MainActivity"
Android:background="#8fa485">
<ListView
Android:id="@+id/lv"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"/>
</RelativeLayout>
et ajoutez du code à MainActivity.Java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Get the application context
mContext = getApplicationContext();
// Get the activity
mActivity = MainActivity.this;
// Get the widgets reference from XML layout
mRelativeLayout = (RelativeLayout) findViewById(R.id.rl);
mListView = (ListView) findViewById(R.id.lv);
// Initialize a new Intent which action is main
Intent intent = new Intent(Intent.ACTION_MAIN,null);
// Set the newly created intent category to launcher
intent.addCategory(Intent.CATEGORY_LAUNCHER);
// Set the intent flags
intent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK|
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
);
// Generate a list of ResolveInfo object based on intent filter
List<ResolveInfo> resolveInfoList = getPackageManager().queryIntentActivities(intent,0);
// Initialize a new ArrayList for holding non system package names
List<String> packageNames = new ArrayList<>();
// Loop through the ResolveInfo list
for(ResolveInfo resolveInfo : resolveInfoList){
// Get the ActivityInfo from current ResolveInfo
ActivityInfo activityInfo = resolveInfo.activityInfo;
// If this is not a system app package
if(!isSystemPackage(resolveInfo)){
// Add the non system package to the list
packageNames.add(activityInfo.applicationInfo.packageName);
}
}
// Initialize an ArrayAdapter using non system package names
ArrayAdapter adapter = new ArrayAdapter<String>(
mContext,
Android.R.layout.simple_list_item_1,
packageNames
);
// DataBind the ListView with adapter
mListView.setAdapter(adapter);
// Set an item click listener for the ListView widget
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
// Get the ListView selected item
String selectedItem = (String) adapterView.getItemAtPosition(i);
// Display a Toast notification for clicked item
Toast.makeText(mContext,"CLICKED : " + selectedItem,Toast.LENGTH_SHORT).show();
// Get the intent to launch the specified application
Intent intent = getPackageManager().getLaunchIntentForPackage(selectedItem);
if(intent != null){
startActivity(intent);
}else {
Toast.makeText(mContext,selectedItem + " Launch Error.",Toast.LENGTH_SHORT).show();
}
}
});
}
// Custom method to determine an app is system app
private boolean isSystemPackage(ResolveInfo resolveInfo){
return ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
}
}
Vous pouvez facilement voir les icônes des applications installées sur votre téléphone avec un petit effort et en ajoutant ImageView.
private static JSONArray getAllAppNames() {
JSONArray appNameList = new JSONArray();
List<PackageInfo> packs = GlobalApplication.getContextOfApplication().getPackageManager().getInstalledPackages(0);
for (int i = 0; i < packs.size(); i++) {
PackageInfo p = packs.get(i);
if ((!isSystemPackage(p))) {
String appName = p.applicationInfo.loadLabel(GlobalApplication.getContextOfApplication().getPackageManager()).toString();
appNameList.put(appName);
}
}
return appNameList;
}
private static boolean isSystemPackage(PackageInfo pkgInfo) {
return (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
Cela devrait plus ou moins résoudre votre problème de base