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.
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!
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.
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
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.