How do I check if an app is a non-system app in Android?
Asked Answered
B

14

46

I am getting a list of ApplicationInfo Objects with packageManager.getInstalledApplications(0) and attempting to categorize them by whether or not they are a system application.

For a while I have been using the technique described here, however after seeing that in my application, some of the apps were not in the non-system apps list (such as Facebook, which when available asks the system to install itself on the SD card). After next reading the actual documentation for ApplicationInfo.FLAG_SYSTEM, and understanding that it doesn't actually filter system apps, I am now looking for a new approach.

My guess is that there is a large gap between UIDs of System and non-system apps that I can gather to make this distinction, but as of yet I have not found an answer. I also looked into other flags, such as ApplicationInfo.FLAG_EXTERNAL_STORAGE, however I am supporting API 1.5.

Does anyone have a real solution to this (not involving FLAG_SYSTEM)?

Beefburger answered 9/1, 2012 at 6:6 Comment(0)
B
7

Well, it's a sloppy solution in my opinion (what if /data/app isn't the apps directory on all devices?), but after a thorough search, this is what I have come up with:

for (ApplicationInfo ai : appInfo) {
    if (ai.sourceDir.startsWith("/data/app/")) {
        //Non-system app
    }
    else {
        //System app
    }
}
Beefburger answered 9/1, 2012 at 17:19 Comment(5)
If you update a system app, it will put the update into the /data/app directory. But it still is a system app.Nursemaid
@IonAalbers: I had this doubt too.Most places its written that system apps reside in system/app and user apps in data/app.Why is it that when system/app is updated then its apk is stored in data/app ?Could you pls explain a bit more?Spheroidal
@Spheroidal The application which location is /system/app/ does not mean that, it is system app. It means that application is pre-loaded. And for update application doubt, I can guess that, they have written this logic to keep factory-reset in mind. If application will be updated at same location, it will hard to find original application state while factory reset.Photo
NOT WOKING AT ALL. 3rd party apps has also dir starts with /data/app/Girdle
About what Ion said, the APK still remains on system folders (it remains on both folders). In that case, it should be checked first if the app is in system folders (like /system/app or /system/priv-app) and only then on /data/app. That way could get this answer to work, but with inverted logic.Shaner
W
34
PackageManager pm = mcontext.getPackageManager();
List<PackageInfo> list = pm.getInstalledPackages(0);

for(PackageInfo pi : list) {
    ApplicationInfo ai = pm.getApplicationInfo(pi.packageName, 0);

    System.out.println(">>>>>>packages is<<<<<<<<" + ai.publicSourceDir);

    if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
        System.out.println(">>>>>>packages is system package"+pi.packageName);          
    }
}
Wear answered 9/1, 2012 at 6:36 Comment(4)
@Ocool, I specifically state in my question that FLAG_SYSTEM does not actually work, so this is not what I am looking for.Beefburger
@Beefburger I know FLAG_SYSTEM doesnt work on its own that why the "&" condition, try it, it worked for me.Wear
@Ocool, this is what I have been using for a while, and it appears to work, until you notice that some apps are missing. The fact is that FLAG_SYSTEM simply gets the apps that are installed in the device's system image. This means that it will not get apps installed on the SD card, etc. The best way to get all the apps is unfortunately to check their directory ("/data/apps").Beefburger
@Beefburger Apps on the SD Card are always user apps and updates to system apps also go to /data/appsAurelioaurelius
A
30

I was under the impression that all apps in the system image are system apps (and normally installed in /system/app).

If FLAG_SYSTEM is only set to system applications, this will work even for apps in external storage:

boolean isUserApp(ApplicationInfo ai) {
    int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
    return (ai.flags & mask) == 0;
}

An alternative is to use the pm command-line program in your phone.

Syntax:

pm list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_ID] [FILTER]

pm list packages: prints all packages, optionally only
  those whose package name contains the text in FILTER.  Options:
    -f: see their associated file.
    -d: filter to only show disbled packages.
    -e: filter to only show enabled packages.
    -s: filter to only show system packages.
    -3: filter to only show third party packages.
    -i: see the installer for the packages.
    -u: also include uninstalled packages.

Code:

ProcessBuilder builder = new ProcessBuilder("pm", "list", "packages", "-s");
Process process = builder.start();

InputStream in = process.getInputStream();
Scanner scanner = new Scanner(in);
Pattern pattern = Pattern.compile("^package:.+");
int skip = "package:".length();

Set<String> systemApps = new HashSet<String>();
while (scanner.hasNext(pattern)) {
    String pckg = scanner.next().substring(skip);
    systemApps.add(pckg);
}

scanner.close();
process.destroy();

Then:

boolean isUserApp(String pckg) {
    return !mSystemApps.contains(pckg);
}
Aurelioaurelius answered 2/2, 2013 at 19:3 Comment(0)
P
21

You can check the signature of application which it signed with system. Like below

/**
 * Match signature of application to identify that if it is signed by system
 * or not.
 * 
 * @param packageName
 *            package of application. Can not be blank.
 * @return <code>true</code> if application is signed by system certificate,
 *         otherwise <code>false</code>
 */
public boolean isSystemApp(String packageName) {
    try {
        // Get packageinfo for target application
        PackageInfo targetPkgInfo = mPackageManager.getPackageInfo(
                packageName, PackageManager.GET_SIGNATURES);
        // Get packageinfo for system package
        PackageInfo sys = mPackageManager.getPackageInfo(
                "android", PackageManager.GET_SIGNATURES);
        // Match both packageinfo for there signatures
        return (targetPkgInfo != null && targetPkgInfo.signatures != null && sys.signatures[0]
                .equals(targetPkgInfo.signatures[0]));
    } catch (PackageManager.NameNotFoundException e) {
        return false;
    }
}

You can get more code on my blog How to check if application is system app or not (By signed signature)

Photo answered 8/7, 2014 at 14:17 Comment(11)
I have not had a chance to try this, but I really like this approach if it works - definitely a great way to do the comparison.Beefburger
@Beefburger It would be my pleasure to know that it worked for your expectation. Thank you.Photo
@PankajKumar : I have developed a system app for iBall tab and I used both methods as per your blog and I get return value as 'true' in both cases.(So I am hoping that my app really has become a system app :) ).Two doubts,(1)your second method 'isAppPreLoaded' does the same thing as 'isSystemApp' in addition to checking if app is preloaded or not,right? ;(2) What is the meaning of preloaded,does it mean that it checks whether the app resides in system/app or is it checking that the app came baked with the ROM or both?Spheroidal
@Spheroidal Sometime applications are pre-installed, but those are not system app. Like you developed an application, signed with your certificate and added to custom AOSP code, that is signed by manufacturer certificate. In this case your app is pre-installed but that is not a system app (considering the certificate). So the last method at blog does both, first checks if application is pre-installed, if yes then checks for the signature of application. Hope I am clear now :)\Photo
@PankajKumar: Not clear fully.Why do you say that "In this case your app is pre-installed but that is not a system app (considering the certificate)" ? My app resides in system/app and is signed by the same certificates as that of the system.So isn't it a system app now.Also I ran both 'methods' as per your blog and gave a return value of 'true'.Hence doesn't that confirm that my app has become a system app?Spheroidal
@Spheroidal Your application is system app. Look in this case, You build an application and signed by your certificate (not the same certificate which sign AOSP code). Till here your app is like a third party app. Right? Now you copy that apk into system/app. This step does not make your app as system app (there are too many API your app can not access, even if that is into system dir). For this it must signed by the same certificate AS YOU ARE DOING, and location must be /system/app. No worry about your case, you are doing the correct way.Photo
@PankajKumar. I tried your method it works in some cases but for few packages it says not system app...like-"com.google.android.gms"," com.sec.android.app.launcher","com.google.android.googlequicksearchbox" are they not system app?Shortcircuit
@Shortcircuit No they are not system apps. They are applications from Google and they are signed by Google's certificate not with the manufaturer's certificate. Even these application are installed on other location not into /system/app. Am I clear now?Photo
@Shortcircuit adb shell pm path com.google.android.gms try into command window, and you will get the location of this package.Photo
very interestingGirdle
Thank you! But possibly, shouldn't it be better to make 2 for loops one inside the other checking if any of the signatures of both apps match each other? Imagine the app is signed with more than one certificate. Also I'll point out that GET_SIGNATURES returns all the app certificates, whether they're valid or not (if I got it right, that led to FakeID). If GET_SIGNATURES must be used and this is a concern, I'd say either code some chain of trust or use this method only with Pie and above with GET_SIGNING_CERTIFICATES, which returns only valid certificates (which is what I'm doing).Shaner
H
11

There are 2 type of Non - system applications :

  1. Apps downloaded from Google Play Store
  2. Preloaded apps by device manufacturer

This code will return a list of all above applications:

ArrayList<ApplicationInfo> mAllApp = 
        mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA);

for(int i = 0; i < mAllApp.size(); i++) {
    if((mAllApp.get(i).flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
         // 1. Applications downloaded from Google Play Store
        mAllApp1.add(mAllApp.get(i));
    }

    if((mAllApp.get(i).flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
        // 2. Applications preloaded in device by manufecturer
        mAllApp1.add(mAllApp.get(i));
    }
}
Helluva answered 27/1, 2016 at 11:52 Comment(1)
Why do you say apps downloaded from Play Store are with FLAG_SYSTEM? I could be wrong, but from my personal experience, FLAG_UPDATED_SYSTEM_APP is any system app which you updated. Like, when Maps comes installed and you update it, it gets that flag. If you uninstall all updates, the flag used might by FLAG_SYSTEM or one of 2 others I found on hidden APIs: ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER (value of 1 << 9), or PRIVATE_FLAG_SYSTEM_EXT (value of 2097152). No idea what they do though.Shaner
B
7

Well, it's a sloppy solution in my opinion (what if /data/app isn't the apps directory on all devices?), but after a thorough search, this is what I have come up with:

for (ApplicationInfo ai : appInfo) {
    if (ai.sourceDir.startsWith("/data/app/")) {
        //Non-system app
    }
    else {
        //System app
    }
}
Beefburger answered 9/1, 2012 at 17:19 Comment(5)
If you update a system app, it will put the update into the /data/app directory. But it still is a system app.Nursemaid
@IonAalbers: I had this doubt too.Most places its written that system apps reside in system/app and user apps in data/app.Why is it that when system/app is updated then its apk is stored in data/app ?Could you pls explain a bit more?Spheroidal
@Spheroidal The application which location is /system/app/ does not mean that, it is system app. It means that application is pre-loaded. And for update application doubt, I can guess that, they have written this logic to keep factory-reset in mind. If application will be updated at same location, it will hard to find original application state while factory reset.Photo
NOT WOKING AT ALL. 3rd party apps has also dir starts with /data/app/Girdle
About what Ion said, the APK still remains on system folders (it remains on both folders). In that case, it should be checked first if the app is in system folders (like /system/app or /system/priv-app) and only then on /data/app. That way could get this answer to work, but with inverted logic.Shaner
L
7

If an Application is a non-system application it must have a launch Intent by which it can be launched. If the launch intent is null then its a system App.

Example of System Apps: "com.android.browser.provider", "com.google.android.voicesearch".

For the above apps you will get NULL when you query for launch Intent.

PackageManager pm = getPackageManager();
List<ApplicationInfo> packages = pm.getInstalledApplications(PackageManager.GET_META_DATA);
for(ApplicationInfo packageInfo:packages){
    if( pm.getLaunchIntentForPackage(packageInfo.packageName) != null ){
                String currAppName = pm.getApplicationLabel(packageInfo).toString();
               //This app is a non-system app
    }
}
Levator answered 19/8, 2013 at 20:9 Comment(4)
This is genius. This is a good way to find launchable applications.Kafir
Many system apps have launch intents, e.g. com.android.settingsCemetery
Very good answer - I could not, by now, figure out how launchable apps can be considered as system appsRandyranee
By far this is the best answer ever and still works good after these so many years. @alex-cohn yes there are many system apps that have launch intents, but this also works for listing only apps that have launch intents depending on a developer's project.Barncard
A
7

There is a little bit of misunderstanding here. For Android the notion of a "system app" is one that is install on the system image, it says nothing about what developer it came from. So, if an OEM decides to preload Facebook on to the system image, it is a system app and will continue to be so, regardless of where updates to the app get installed. They won't get installed on the system image, for sure, because it is read-only.

So ApplicationInfo.FLAG_SYSTEM is correct, but that doesn't seem to be the question you are asking. I think you're asking if a package is signed with the system certificate. Which is not necessarily a good indicator of anything, this may vary from device to device and some surprising components on vanilla Android are not signed with the system certificate, even though you might expect them to be.

In newer versions of Android there is a new path, /system/priv-app/ that attempts to be the install location for "real" system apps. Apps that are just pre-loaded on the system image then end up in /system/app/. See AOSP Privileged vs System app

Autarch answered 23/6, 2014 at 20:6 Comment(1)
Nice perspective.I have created a system app and it resides in system/app .I did sign it with system keys(at least thats what I am hoping).But is there a way(programatically or otherwise) to fully confirm that my app has now become a real system app (which is signed with the same keys as that were baked in the ROM)?Spheroidal
E
5

Here are different possible ways to see if the app is a system app by its package name (used some of the codes in this post)

package com.test.util;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
import java.util.regex.Pattern;

import timber.log.Timber;


public class SystemAppChecker {
    private PackageManager packageManager = null;

    public SystemAppChecker(Context context) {
        packageManager = context.getPackageManager();
    }

    /**
     * Check if system app by 'pm' command-line program
     *
     * @param packageName
     *            package name of application. Cannot be null.
     * @return <code>true</code> if package is a system app.
     */
    public boolean isSystemAppByPM(String packageName) {
        if (packageName == null) {
            throw new IllegalArgumentException("Package name cannot be null");
        }
        ProcessBuilder builder = new ProcessBuilder("pm", "list", "packages", "-s");
        Process process = null;
        try {
            process = builder.start();
        } catch (IOException e) {
            Timber.e(e);
            return false;
        }

        InputStream in = process.getInputStream();
        Scanner scanner = new Scanner(in);
        Pattern pattern = Pattern.compile("^package:.+");
        int skip = "package:".length();

        Set<String> systemApps = new HashSet<String>();
        while (scanner.hasNext(pattern)) {
            String pckg = scanner.next().substring(skip);
            systemApps.add(pckg);
        }

        scanner.close();
        process.destroy();

        if (systemApps.contains(packageName)) {
            return true;
        }
        return false;
    }

    /**
     * Check if application is preloaded.
     *
     * @param packageName
     *            package name of application. Cannot be null.
     * @return <code>true</code> if package is preloaded.
     */
    public boolean isSystemPreloaded(String packageName) {
        if (packageName == null) {
            throw new IllegalArgumentException("Package name cannot be null");
        }
        try {
            ApplicationInfo ai = packageManager.getApplicationInfo(
                    packageName, 0);
            if (ai.sourceDir.startsWith("/system/app/") || ai.sourceDir.startsWith("/system/priv-app/")) {
                return true;
            }
        } catch (NameNotFoundException e) {
            Timber.e(e);
        }
        return false;
    }

    /**
     * Check if the app is system signed or not
     *
     * @param packageName
     *            package of application. Cannot be blank.
     * @return <code>true</code> if application is signed by system certificate,
     *         otherwise <code>false</code>
     */
    public boolean isSystemSigned(String packageName) {
        if (packageName == null) {
            throw new IllegalArgumentException("Package name cannot be null");
        }
        try {
            // Get packageinfo for target application
            PackageInfo targetPkgInfo = packageManager.getPackageInfo(
                    packageName, PackageManager.GET_SIGNATURES);
            // Get packageinfo for system package
            PackageInfo sys = packageManager.getPackageInfo(
                    "android", PackageManager.GET_SIGNATURES);
            // Match both packageinfo for there signatures
            return (targetPkgInfo != null && targetPkgInfo.signatures != null && sys.signatures[0]
                    .equals(targetPkgInfo.signatures[0]));
        } catch (PackageManager.NameNotFoundException e) {
            Timber.e(e);
        }
        return false;
    }

    /**
     * Check if application is installed in the device's system image
     *
     * @param packageName
     *            package name of application. Cannot be null.
     * @return <code>true</code> if package is a system app.
     */
    public boolean isSystemAppByFLAG(String packageName) {
        if (packageName == null) {
            throw new IllegalArgumentException("Package name cannot be null");
        }
        try {
            ApplicationInfo ai = packageManager.getApplicationInfo(
                    packageName, 0);
            // Check if FLAG_SYSTEM or FLAG_UPDATED_SYSTEM_APP are set.
            if (ai != null
                    && (ai.flags & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0) {
                return true;
            }
        } catch (NameNotFoundException e) {
            Timber.e(e);
        }
        return false;
    }
}
Eulogia answered 29/12, 2017 at 22:16 Comment(3)
How can an app be "isSystemSigned"? Do I need the system key to do it?Tichon
"isSystemSigned" means signing your apk with platform keys. Refer this for creating a development build - github.com/aosp-mirror/platform_build/tree/master/target/…Eulogia
If you have more questions, then these posts may answers some of those - #37586755 and https://mcmap.net/q/262273/-how-to-sign-android-app-with-system-signatureEulogia
S
5

This is a simplified and more efficient version of other responses listed here. It is more efficient if you just iterate directly over the ApplicationInfos.

    List<ApplicationInfo> applications = context.getPackageManager()
            .getInstalledApplications(PackageManager.GET_META_DATA);
    for(ApplicationInfo appInfo : applications){
        if((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0){
            // Not a system app
        }
    }
Stealing answered 8/8, 2019 at 20:37 Comment(0)
S
2

So I'd like to put here an utility class I made with the knowledge of this thread and a few others. But before I continue, an explanation of some terms, if I got them all right, copied from that class, which are used on it.

Below KitKat 4.4, all apps in /system/app were given privileged permissions. Even the Calculator app had them. That could be a security breach. So they were separated between ordinary and privileged system apps and ordinary ones don't have privileged permissions above KitKat 4.4. So these utilities have that in mind. They also have in mind the following designations:

  • Platform-signed app: any app that is signed with the platform/system key (so they have system signature permissions), whether it is installed on the system partitions or not.
  • System app: any app that is installed on the system partitions.
  • Updated system app: any system app that was updated (meaning now it is also installed on /data/app).
  • Privileged system app: below KitKat 4.4, any app installed on /system/app; from KitKat 4.4 onwards, only the apps installed on /system/priv-app (I really mean only /system). These apps have privileged permissions.
  • Ordinary system app: only as of KitKat 4.4, those without privileged permissions, even though they're still system apps. Below KitKat 4.4, they're non-existent.

System partition notes: until Oreo 8.1, there was only one: /system. As of Pie (9), there is also /vendor and /product.

So with that in mind, here are 2 functions:

/**
 * <p>Checks if an app is installed on the system partitions and was updated.</p>
 *
 * @param applicationInfo an instance of {@link ApplicationInfo} for the package to be checked
 *
 * @return true if it is, false otherwise
 */
private static boolean isUpdatedSystemApp(@NonNull final ApplicationInfo applicationInfo) {
    return (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
}

/**
 * <p>Checks if an app is installed in the system partitions (ordinary app or privileged app, doesn't matter).</p>
 *
 * @param applicationInfo an instance of {@link ApplicationInfo} for the package to be checked
 *
 * @return true if it is, false otherwise
 */
private static boolean isSystemApp(@NonNull final ApplicationInfo applicationInfo) {
    // Below Android Pie (9), all system apps were in /system. As of Pie, they can ALSO be in /vendor and /product.
    boolean ret_value = (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        // FLAG_SYSTEM checks if it's on the system image, which means /system. So to check for /vendor and
        // /product, here are 2 special flags.
        ret_value = ret_value || (applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0;
        ret_value = ret_value || (applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0;
    }

    return ret_value;
}

To check if an app is a privileged system app or is ordinary system app, and/or is signed with the platform/system key, I'll leave 3 functions below. I believe it's off-topic to the question, but I'll put it in case anyone like me needed it.

/**
 * <p>Checks if an app is an ordinary system app (installed on the system partitions, but no privileged or signature
 * permissions granted to it).</p>
 * <p>Note: will return false for any app on KitKat 4.4 and below.</p>
 *
 * @param applicationInfo an instance of {@link ApplicationInfo} for the package to be checked
 *
 * @return true if it is, false otherwise
 */
private static boolean isOrdinarySystemApp(@NonNull final ApplicationInfo applicationInfo) {
    // It's an ordinary system app if it doesn't have any special permission privileges (it's not a Privileged app
    // nor is it signed with the system key).
    boolean ret_value = isSystemApp(applicationInfo) && !hasPrivilegedPermissions(applicationInfo);
    final boolean signed_system_key = hasSystemSignaturePermissions(applicationInfo);
    ret_value = ret_value && signed_system_key;

    return ret_value;
}

/**
 * <p>Checks if an app has signature permissions - checks if it's signed with the platform/system certificate by
 * comparing it to the "android" package.</p>
 * <br>
 * <p>ATTENTION: if the chosen app was signed multiple times and the system is running below Android Pie, this check
 * may return false wrongly, since it checks if ALL the signatures from the "android" package and the chosen
 * application match. If at least one doesn't match in both, this will return false. So use with caution in case of
 * multiple signers. With only one signer, it's all right.</p>
 *
 * @param applicationInfo an instance of {@link ApplicationInfo} for the package to be checked
 * @return true if it is, false otherwise
 */
private static boolean hasSystemSignaturePermissions(@NonNull final ApplicationInfo applicationInfo) {
    // If on Pie or above, check with a private flag (appeared on Pie only).
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        return (applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY) != 0;
    }

    // Else, check by comparing signatures of a platform-signed app and the chosen app.
    return UtilsGeneral.getContext().getPackageManager().checkSignatures(applicationInfo.packageName, "android")
            == PackageManager.SIGNATURE_MATCH;
}

/**
 * <p>"Value for {@link ApplicationInfo#flags}: set to {@code true} if the application
 * is permitted to hold privileged permissions.</p>
 *
 * {@hide}"
 * <p>NOTE: Only on API 19 through API 22.</p>
 */
private static final int FLAG_PRIVILEGED = 1 << 30;

/**
 * <p>Checks if an app is a Privileged App.</p>
 * <p>Note: will return true for any system app below KitKat 4.4.</p>
 *
 * @param applicationInfo an instance of {@link ApplicationInfo} for the package to be checked
 *
 * @return true if it is, false otherwise
 */
private static boolean hasPrivilegedPermissions(@NonNull final ApplicationInfo applicationInfo) {
    // Check if it's an app installed in the system partitions. If it is, check with methods that apply only to
    // apps installed on the system partitions.
    if (isSystemApp(applicationInfo)) {
        // If it's below KitKat 4.4 and it's a system app, it's a privileged one automatically.
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            return true;
        }

        // If on Marshmallow or above, check with a private flag.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
                return true;
            }
        }

        // If between KitKat 4.4 and Lollipop 5.1, use a deleted flag.
        if ((applicationInfo.flags & FLAG_PRIVILEGED) != 0) {
            return true;
        }
    }

    // In case none returned true above, the app may still be signed with the platform/system's key, which will
    // grant it exactly all permissions there are (which includes privileged permissions - ALL permissions).
    return hasSystemSignaturePermissions(applicationInfo);
}

If you want, you can join this last one to the ones above, but I don't really recommend it. It will only work work as long as the system app hasn't been updated.

/**
 * <p>Gets a list of folders a system app might be installed in, depending on the device's Android version.</p>
 * <p>Note that an updated system app will report as being installed in /data/app. For these locations to be
 * checked, the app must not have been updated. If it has, it's not possible to tell using the directory, I think.</p>
 *
 * @param privileged_app true if it's to return a list for privileged apps, false if it's for ordinary system apps,
 *                       null if it's to return a list for both types
 *
 * @return a list of folders its APK might be in
 */
@NonNull
private static String[] getAppPossibleFolders(@Nullable final Boolean privileged_app) {
    final Collection<String> ret_folders = new ArrayList<>(5);

    final String PRIV_APP_FOLDER = "/system/priv-app";
    final String ORD_APP_SYSTEM_FOLDER = "/system/app";
    final String ORD_APP_VENDOR_FOLDER = "/vendor/app";
    final String ORD_APP_PRODUCT_FOLDER = "/product/app";

    if (privileged_app == null) {
        ret_folders.add(PRIV_APP_FOLDER);
        ret_folders.add(ORD_APP_SYSTEM_FOLDER);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            ret_folders.add(ORD_APP_VENDOR_FOLDER);
            ret_folders.add(ORD_APP_PRODUCT_FOLDER);
        }
    } else if (privileged_app) {
        ret_folders.add(PRIV_APP_FOLDER);
    } else {
        ret_folders.add(ORD_APP_SYSTEM_FOLDER);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            ret_folders.add(ORD_APP_VENDOR_FOLDER);
            ret_folders.add(ORD_APP_PRODUCT_FOLDER);
        }
    }

    // Leave it in 0 size allocation. Or null values will appear, and I don't want to need to be careful about it.
    return ret_folders.toArray(new String[0]);

    /*
    Use with:

    // If it's an updated system app, its APK will be said to be in /data/app, and the one on the system partitions
    // will become unused. But if it's not updated, it's all fine and the APK path can be used to check if it's
    // a privileged app or not.
    if (!isUpdatedSystemApp(applicationInfo)) {
        for (final String folder : getAppPossibleFolders(false)) {
            if (applicationInfo.sourceDir.startsWith(folder)) {
                return true;
            }
        }
    }
    */
}
Shaner answered 30/7, 2021 at 20:33 Comment(0)
C
1
if (!packageInfo.sourceDir.toLowerCase().startsWith("/system/"))
Collyrium answered 12/12, 2013 at 21:28 Comment(1)
This is not necessarily true, you can always sign an app with platform key (given you have access to one) and install it with system sharedUserId and it would run as a system appSible
H
1

If having an APK file and want to check is it System app or User installed a Simple logic:- System app Files are not writable

private boolean isSystemApkFile(File file){
   return !file.canWrite();
}
Harleyharli answered 27/12, 2016 at 15:46 Comment(1)
Non-system APK files are not writable too, because they belong to another user.Doby
L
1

You can use checkSignatures to determine if an app is a system app or not.

All system apps are signed with the same key.

https://developer.android.com/reference/android/content/pm/PackageManager#checkSignatures(java.lang.String,%20java.lang.String)

And signed with the system key is the "android" package.

    val checkPackage: String = "com.package.to.check"
    val systemPackageName = "android"
    if (packageManager.checkSignatures(systemPackageName, checkPackage) == PackageManager.SIGNATURE_MATCH) {
        Log.d("TUT", "System app")
    } else {
        Log.d("TUT", "Non-System app")
    }
Labiovelar answered 19/5, 2020 at 12:28 Comment(1)
On KitKat and below, not sure I'd trust checkSignatures(). If I understood correctly, it might be wrongly implemented on those Android versions. In that case might be better to either don't check at all the signatures, or implement some chain of trust with GET_SIGNATURES to know if all the certificates are indeed valid.Shaner
B
0

Here is an AppUtil I wrote for that purpose.
Usage example:

new AppsUtil(this).printInstalledAppPackages(AppsUtil.AppType.USER);

AppsUtil.java

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.util.Log;

public class AppsUtil
{
    public static final String  TAG = "PackagesInfo";
    private Context             _context;
    private ArrayList<PckgInfo> _PckgInfoList;

    public enum AppType 
    {
        ALL {
            @Override
            public String toString() {
              return "ALL";
            }
          },
        USER {
            @Override
            public String toString() {
              return "USER";
            }
          },
        SYSTEM {
            @Override
            public String toString() {
              return "SYSTEM";
            }
          }
    }

    class PckgInfo
    {
        private AppType appType;
        private String  appName     = "";
        private String  packageName = "";
        private String  versionName = "";
        private int     versionCode = 0;

        private void prettyPrint()
        {
            Log.i(TAG, appName + "\n  AppType: " + appType.toString() + "\n  Package: " + packageName + "\n  VersionName: " + versionName + "\n  VersionCode: " + versionCode);
        }
    }

    public AppsUtil(Context context)
    {
        super();
        this._context = context;
        this._PckgInfoList = new ArrayList<PckgInfo>();
    }

    public void printInstalledAppPackages(AppType appType)
    {
        retrieveInstalledAppsPackages();
        Log.i(TAG, "");
        for (int i = 0; i < _PckgInfoList.size(); i++)
        {
            if (AppType.ALL == appType)
            {
                _PckgInfoList.get(i).prettyPrint();
            }
            else
            {
                if (_PckgInfoList.get(i).appType == appType)
                    _PckgInfoList.get(i).prettyPrint();
            }
        }
    }

    public ArrayList<PckgInfo> getInstalledAppPackages(AppType appType)
    {
        retrieveInstalledAppsPackages();
        ArrayList<PckgInfo> resultPInfoList = new ArrayList<PckgInfo>();

        if (AppType.ALL == appType)
        {
            return _PckgInfoList;
        }
        else
        {
            for (int i = 0; i < _PckgInfoList.size(); i++)
            {
                if (_PckgInfoList.get(i).appType == appType)
                    resultPInfoList.add(_PckgInfoList.get(i));
            }
            return resultPInfoList;
        }
    }

    private void retrieveInstalledAppsPackages()
    {
        PackageManager pm = _context.getPackageManager();
        List<PackageInfo> packs = pm.getInstalledPackages(0);
        for (PackageInfo pi : packs)
        {
            try
            {
                PckgInfo newInfo = new PckgInfo();
                ApplicationInfo ai = pm.getApplicationInfo(pi.packageName, 0);

                newInfo.appType = getAppType(ai);
                newInfo.appName = pi.applicationInfo.loadLabel(pm).toString();
                newInfo.packageName = pi.packageName;
                newInfo.versionName = pi.versionName;
                newInfo.versionCode = pi.versionCode;
                _PckgInfoList.add(newInfo);
            }
            catch (NameNotFoundException e)
            {
                e.printStackTrace();
            }
        }
    }

    AppType getAppType(ApplicationInfo ai)
    {
        AppType resultType ;
        if (isUserApp(ai))
            resultType = AppType.USER;
        else
            resultType = AppType.SYSTEM;

        return resultType;
    }

    boolean isUserApp(ApplicationInfo ai)
    {
        int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
        return (ai.flags & mask) == 0;
    }
}
Bawdy answered 26/7, 2016 at 15:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.