Get list of apps of all users
Asked Answered
A

5

27

If I want to retrieve an ApplicationInfo list for all apps of the current user, I can just run:

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

But I am looking for a way to get these lists for every user and not just the current one. The app that I'm working on has root permissions btw!

From what I understand, the user ids can be retrieved like this:

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);
    }
}

Now I'm looking for something like this:

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

Obviously getInstalledApplicationsByUserId doesn't exist.

At first I thought getPackagesForUid could solve the problem, but as it turns out there is a difference between "users" in the Linux-sense and user profiles. Generally every single Android app runs under it's own user for isolation-purposes. But it is possible to run two apps under the same user so that they can access each others data easily. getPackagesForUid merely returns the names of all apps that run under the given user id, which is usually exactly one. In addition to "users" there are also "User profiles" which is what I was hoping the method was referring to. Maybe I should have also written userProfileId instead of userId in my code.

Edit: Using adb shell, I can retrieve the app IDs like this:

# 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"

But that's Bash and not Java...

Edit2: Correct me if I'm wrong (this is the first time I've written code in Java) but it doesn't appear like there is a Java API to do this. So the only way to do this would by using a shell. So this is what I came up with:

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)
        }
        ...
    }
}

But I have no clue if this is even close to something that would actually work. And besides that, I only get the package ids ("com.whatsapp" etc) for each user profile, but I would like to get a list of ApplicationInfo just like getInstalledApplications returns it. I just can't think of a good way to do this. Maybe it would be possible to load the package manifests and then create ApplicationInfo instances based on them somehow?

Edit3: I think I found the source code for the pm executable.

Oddly I couldn't find a single mention of the --user flag in it.

The most relevant parts of the code are:

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

The getInstalledPackages method simply calls 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;
}

This leaves me with more questions than I had before. First of all I wonder if I couldn't just import the com.android.commands.pm class. Secondly I wonder how I could tell it to return the packages of a specific user profile or if this even is the right piece of source code in the first place. And finally I wonder if I even need root permissions to use that. After all, the if (Process.myUid() != ROOT_UID) checks are only executed for runRemoveProfile, runCreateProfile and runListProfiles.

Edit4: I was not able to find the source code of the package service. I was only able to find this file: /data/system/packages.xml. It contains some basic package information on all packages (for all user profiles), but it neither contains the actuall app names, nor does it contain information on which user profiles they belong to.

Edit5: I think I found the package service source code. I think this method is the important one:

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

Unfortunately, I just don't understand the code. To me it looks like the packages are somehow coming from mSettings.mPackages. The variable is explained as follows in a code comment:

{@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.

Edit6: I just found another even more interesting method in that file:

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

I don't know what ParceledListSlice is, but sicne it says <ApplicationInfo> I'm assuming that this is really close to my wanted List<ApplicationInfo> format. But still, I'm completely clueless how I could access that functionality.

Antonietta answered 30/7, 2019 at 7:30 Comment(1)
Can't you construct your solution using the code/technique in your "Edit 3?" IIRC, ServiceManager isn't public, but you should be able to call it by reflection, see https://mcmap.net/q/535278/-the-import-android-os-servicemanager-cannot-be-resolvedFlawed
D
4

I found the code for the ParceledListSlice here, and the code for the Parcelable interface it implements here. It looks like it uses some form of array internally, so you should be able to iterate over that. I can't actually say if this will be helpful to you or not, because I'm not all that familiar with these Libraries, but I hope it's a starting point. Good Luck!

Dinh answered 6/8, 2019 at 13:10 Comment(0)
R
3

You can apply the approach you mentioned in your first EDIT and EDIT2 and use Android's shell, since you mentioned your device has root permissions.

By the way, I'm not familiar with the library from "stericson" you are using, but I agree with leveraging existing libraries to make your life easier; I am using libsuperuser. Otherwise, you can write code to run shell commands using Process etc. yourself (but there are lots of things to consider with handling error output, closing objects, and so forth).

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<>();
}

Of course, you can use other arguments to pm as necessary such as -e, -d see documentation.

Once you extract the package names, get all required info (as is contained in ApplicationInfo) using 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...
}

Create ApplicationInfo objects as required, if you have downstream code that requires this object type specifically

ApplicationInfo info = new ApplicationInfo();
info.uid = uid;
info.processName = processName;
info.className = classname;
info.packageName = packageName;
info.minSdkVersion = minSdkVersion;
//etc...

(fields of the ApplicationInfo class are public). This is also true of PackageInfo which is an object that contains an ApplicationInfo field, plus it contains details of the install time etc. which are also details you can extract from the dumpsys output. So you could create an array of these as you wish and can be populated with your new ApplicationInfo objects.

Redford answered 13/8, 2019 at 16:43 Comment(2)
That was what I was originally going for, but there are like 50 properties that need to be mapped correctly. Not to mention that some of them aren't even in the sysdump output. Not to mention that it would take a VERY long time to run so many shell commands.Antonietta
Ah yes, execution time can be an issue. Perhaps better to combine as much into a single shell script, for example looping over all user IDs, rather than repeatedly invoking a new su call.Redford
T
0

If you're able to access hidden API either because you're a platform app or are using introspection (with the right permissions!), you can simply use UserManager.getUsers() and PackageManager. getInstalledApplicationsAsUser():

Set<ApplicationInfo> installedApplications = new HashSet<>();
for (UserInfo user : getSystemService(UserManager.class).getUsers()) {
    installedApplications.addAll(getPackageManager().getInstalledApplicationsAsUser(PackageManager.GET_META_DATA, user.id));
}
Trypsin answered 24/4, 2023 at 20:14 Comment(0)
S
-1
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;
    }

This should more or less solve your basic problem

Saccharin answered 7/8, 2019 at 7:38 Comment(1)
This would just return a list of app names if I understand your code correctly. Would it be possible to get a list of type List<ApplicationInfo> instead with your method?Antonietta
H
-1

Get installed apps list programmatically

add code to 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>

and add code to 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);
    }
}

You can easily see the icons of the apps installed on your phone with a little effort and adding ImageView.

Hallerson answered 16/8, 2019 at 21:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.