install / uninstall APKs programmatically (PackageManager vs Intents) - android

My application installs other applications, and it needs to keep track of what applications it has installed. Of course, this could be achieved by simply keeping a list of installed applications. But this should not be necessary! It should be the responsibility of the PackageManager to maintain the installedBy(a, b) relationship. In fact, according to the API it is:
public abstract String getInstallerPackageName(String packageName) -
Retrieve the package name of the application that installed a package. This identifies which market the package came from.
The current approach
Install APK using Intent
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(intent);
Uninstall APK using Intent:
Intent intent = new Intent(Intent.ACTION_DELETE, Uri.fromParts("package",
getPackageManager().getPackageArchiveInfo(apkUri.getPath(), 0).packageName,null));
startActivity(intent);
This is obviously not the way e.g. Android Market installs / uninstalls packages. They use a richer version of the PackageManager. This can bee seen by downloading the Android source code from the Android Git repository. Below are the two hidden methods that corresponds to the Intent approach. Unfortunately they are not available to external developers. But perhaps they will be in the future?
The better approach
Installing APK using the PackageManager
/**
* #hide
*
* Install a package. Since this may take a little while, the result will
* be posted back to the given observer. An installation will fail if the calling context
* lacks the {#link android.Manifest.permission#INSTALL_PACKAGES} permission, if the
* package named in the package file's manifest is already installed, or if there's no space
* available on the device.
*
* #param packageURI The location of the package file to install. This can be a 'file:' or a
* 'content:' URI.
* #param observer An observer callback to get notified when the package installation is
* complete. {#link IPackageInstallObserver#packageInstalled(String, int)} will be
* called when that happens. observer may be null to indicate that no callback is desired.
* #param flags - possible values: {#link #INSTALL_FORWARD_LOCK},
* {#link #INSTALL_REPLACE_EXISTING}, {#link #INSTALL_ALLOW_TEST}.
* #param installerPackageName Optional package name of the application that is performing the
* installation. This identifies which market the package came from.
*/
public abstract void installPackage(
Uri packageURI, IPackageInstallObserver observer, int flags,
String installerPackageName);
Uninstalling APK using the PackageManager
/**
* Attempts to delete a package. Since this may take a little while, the result will
* be posted back to the given observer. A deletion will fail if the calling context
* lacks the {#link android.Manifest.permission#DELETE_PACKAGES} permission, if the
* named package cannot be found, or if the named package is a "system package".
* (TODO: include pointer to documentation on "system packages")
*
* #param packageName The name of the package to delete
* #param observer An observer callback to get notified when the package deletion is
* complete. {#link android.content.pm.IPackageDeleteObserver#packageDeleted(boolean)} will be
* called when that happens. observer may be null to indicate that no callback is desired.
* #param flags - possible values: {#link #DONT_DELETE_DATA}
*
* #hide
*/
public abstract void deletePackage(
String packageName, IPackageDeleteObserver observer, int flags);
Differences
When using intents the local package manager is not made aware of which application the installation originated from. Specifically, getInstallerPackageName(...) returns null.
The hidden method installPackage(...) takes the installer package name as a parameter, and is most likely capable of setting this value.
Question
Is it possible to specify package installer name using intents?
(Maybe the name of the installer package can be added as an extra to the installation intent?)
Tip: If you want to download the Android source code you can follow the steps described here: Downloading the Source Tree. To extract the *.java files and put them in folders according to the package hierarchy you can check out this neat script: View Android Source Code in Eclipse.

Android P+ requires this permission in AndroidManifest.xml
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
Then:
Intent intent = new Intent(Intent.ACTION_DELETE);
intent.setData(Uri.parse("package:com.example.mypackage"));
startActivity(intent);
to uninstall. Seems easier...

This is not currently available to third party applications. Note that even using reflection or other tricks to access installPackage() will not help, because only system applications can use it. (This is because it is the low-level install mechanism, after the permissions have been approved by the user, so it is not safe for regular applications to have access to.)
Also the installPackage() function arguments have often changed between platform releases, so anything you do trying access it will fail on various other versions of the platform.
EDIT:
Also it is worth pointing out that this installerPackage was only added fairly recently to the platform (2.2?) and was originally not actually used for tracking who installed the app -- it is used by the platform to determine who to launch when reporting bugs with the app, for implementing Android Feedback. (This was also one of the times the API method arguments changed.) For at least a long while after it was introduced, Market still didn't use it to track the apps it has installed (and it may very well still not use it), but instead just used this to set the Android Feedback app (which was separate from Market) as the "owner" to take care of feedback.

API level 14 introduced two new actions: ACTION_INSTALL_PACKAGE and ACTION_UNINSTALL_PACKAGE. Those actions allow you to pass EXTRA_RETURN_RESULT boolean extra to get an (un)installation result notification.
Example code for invoking the uninstall dialog:
String app_pkg_name = "com.example.app";
int UNINSTALL_REQUEST_CODE = 1;
Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
intent.setData(Uri.parse("package:" + app_pkg_name));
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
startActivityForResult(intent, UNINSTALL_REQUEST_CODE);
And receive the notification in your Activity#onActivityResult method:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == UNINSTALL_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
Log.d("TAG", "onActivityResult: user accepted the (un)install");
} else if (resultCode == RESULT_CANCELED) {
Log.d("TAG", "onActivityResult: user canceled the (un)install");
} else if (resultCode == RESULT_FIRST_USER) {
Log.d("TAG", "onActivityResult: failed to (un)install");
}
}
}

If you have Device Owner (or profile owner, I haven't tried) permission you can silently install/uninstall packages using device owner API.
for uninstalling:
public boolean uninstallPackage(Context context, String packageName) {
ComponentName name = new ComponentName(MyAppName, MyDeviceAdminReceiver.class.getCanonicalName());
PackageManager packageManger = context.getPackageManager();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
PackageInstaller packageInstaller = packageManger.getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
params.setAppPackageName(packageName);
int sessionId = 0;
try {
sessionId = packageInstaller.createSession(params);
} catch (IOException e) {
e.printStackTrace();
return false;
}
packageInstaller.uninstall(packageName, PendingIntent.getBroadcast(context, sessionId,
new Intent("android.intent.action.MAIN"), 0).getIntentSender());
return true;
}
System.err.println("old sdk");
return false;
}
and to install package:
public boolean installPackage(Context context,
String packageName, String packagePath) {
ComponentName name = new ComponentName(MyAppName, MyDeviceAdminReceiver.class.getCanonicalName());
PackageManager packageManger = context.getPackageManager();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
PackageInstaller packageInstaller = packageManger.getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
params.setAppPackageName(packageName);
try {
int sessionId = packageInstaller.createSession(params);
PackageInstaller.Session session = packageInstaller.openSession(sessionId);
OutputStream out = session.openWrite(packageName + ".apk", 0, -1);
readTo(packagePath, out); //read the apk content and write it to out
session.fsync(out);
out.close();
System.out.println("installing...");
session.commit(PendingIntent.getBroadcast(context, sessionId,
new Intent("android.intent.action.MAIN"), 0).getIntentSender());
System.out.println("install request sent");
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
System.err.println("old sdk");
return false;
}

The only way to access those methods is through reflection. You can get a handle on a PackageManager object by calling getApplicationContext().getPackageManager() and using reflection access these methods. Checkout this tutorial.

According to Froyo source code, the Intent.EXTRA_INSTALLER_PACKAGE_NAME extra key is queried for the installer package name in the PackageInstallerActivity.

On a rooted device, you might use:
String pkg = context.getPackageName();
String shellCmd = "rm -r /data/app/" + pkg + "*.apk\n"
+ "rm -r /data/data/" + pkg + "\n"
// TODO remove data on the sd card
+ "sync\n"
+ "reboot\n";
Util.sudo(shellCmd);
Util.sudo() is defined here.

If you are passing package name as parameter to any of your user defined function then use the below code :
Intent intent=new Intent(Intent.ACTION_DELETE);
intent.setData(Uri.parse("package:"+packageName));
startActivity(intent);

If you're using Kotlin, API 14+, and just wish to show uninstall dialog for your app:
startActivity(Intent(Intent.ACTION_UNINSTALL_PACKAGE).apply {
data = Uri.parse("package:$packageName")
})
You can change packageName to any other package name if you want to prompt the user to uninstall another app on the device

Prerequisite:
Your APK needs to be signed by system as correctly pointed out earlier. One way to achieve that is building the AOSP image yourself and adding the source code into the build.
Code:
Once installed as a system app, you can use the package manager methods to install and uninstall an APK as following:
Install:
public boolean install(final String apkPath, final Context context) {
Log.d(TAG, "Installing apk at " + apkPath);
try {
final Uri apkUri = Uri.fromFile(new File(apkPath));
final String installerPackageName = "MyInstaller";
context.getPackageManager().installPackage(apkUri, installObserver, PackageManager.INSTALL_REPLACE_EXISTING, installerPackageName);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
Uninstall:
public boolean uninstall(final String packageName, final Context context) {
Log.d(TAG, "Uninstalling package " + packageName);
try {
context.getPackageManager().deletePackage(packageName, deleteObserver, PackageManager.DELETE_ALL_USERS);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
To have a callback once your APK is installed/uninstalled you can use this:
/**
* Callback after a package was installed be it success or failure.
*/
private class InstallObserver implements IPackageInstallObserver {
#Override
public void packageInstalled(String packageName, int returnCode) throws RemoteException {
if (packageName != null) {
Log.d(TAG, "Successfully installed package " + packageName);
callback.onAppInstalled(true, packageName);
} else {
Log.e(TAG, "Failed to install package.");
callback.onAppInstalled(false, null);
}
}
#Override
public IBinder asBinder() {
return null;
}
}
/**
* Callback after a package was deleted be it success or failure.
*/
private class DeleteObserver implements IPackageDeleteObserver {
#Override
public void packageDeleted(String packageName, int returnCode) throws RemoteException {
if (packageName != null) {
Log.d(TAG, "Successfully uninstalled package " + packageName);
callback.onAppUninstalled(true, packageName);
} else {
Log.e(TAG, "Failed to uninstall package.");
callback.onAppUninstalled(false, null);
}
}
#Override
public IBinder asBinder() {
return null;
}
}
/**
* Callback to give the flow back to the calling class.
*/
public interface InstallerCallback {
void onAppInstalled(final boolean success, final String packageName);
void onAppUninstalled(final boolean success, final String packageName);
}

Related

DocumentsUI shows "Anonymous" application when requesting access to directory

One user reported that my app fails to request directory access when selecting a folder via the ACTION_OPEN_DOCUMENT_TREE intent.
For some reason it does not show my application, instead "Anonymous":
Translated: "Allow Anonymous to access files in Camera. This will let Anonymous access current and future content stored in Camera".
The user has a MIUI 12 with Android 11 on a Mi Note 10 lite.
I have the same just with a Mi Note 10, no issues ofc.
Checked the Android source code:
https://android.googlesource.com/platform/packages/apps/DocumentsUI/+/refs/heads/master/src/com/android/documentsui/picker/ConfirmFragment.java#82
case TYPE_OEPN_TREE:
final Uri treeUri = mTarget.getTreeDocumentUri();
final BaseActivity activity = (BaseActivity) getActivity();
final String target = activity.getCurrentTitle();
final String text = getString(R.string.open_tree_dialog_title,
**getCallingAppName**(getActivity()), target);
message = getString(R.string.open_tree_dialog_message,
**getCallingAppName**(getActivity()), target);
builder.setTitle(text);
builder.setMessage(message);
builder.setPositiveButton(
R.string.allow,
(DialogInterface dialog, int id) -> {
pickResult.increaseActionCount();
mActions.finishPicking(treeUri);
});
break;
#NonNull
public static String getCallingAppName(Activity activity) {
final String anonymous = activity.getString(R.string.anonymous_application);
final String packageName = getCallingPackageName(activity);
if (TextUtils.isEmpty(packageName)) {
return anonymous;
}
final PackageManager pm = activity.getPackageManager();
ApplicationInfo ai;
try {
ai = pm.getApplicationInfo(packageName, 0);
} catch (final PackageManager.NameNotFoundException e) {
return anonymous;
}
CharSequence result = pm.getApplicationLabel(ai);
return TextUtils.isEmpty(result) ? anonymous : result.toString();
}
public static String getCallingPackageName(Activity activity) {
String callingPackage = activity.getCallingPackage();
// System apps can set the calling package name using an extra.
try {
ApplicationInfo info =
activity.getPackageManager().getApplicationInfo(callingPackage, 0);
if (isSystemApp(info) || isUpdatedSystemApp(info)) {
final String extra = activity.getIntent().getStringExtra(
Intent.EXTRA_PACKAGE_NAME);
if (extra != null && !TextUtils.isEmpty(extra)) {
callingPackage = extra;
}
}
} catch (NameNotFoundException e) {
// Couldn't lookup calling package info. This isn't really
// gonna happen, given that we're getting the name of the
// calling package from trusty old Activity.getCallingPackage.
// For that reason, we ignore this exception.
}
return callingPackage;
}
...and it seems that for whatever reason my packagename isn't found. How can can happen?
Asked him to install one of my other apps, and it happens there as well.
Asked him then to install another app from the playstore (FX File Explorer) and there it does not happen.
So it is specific to his device and my app.
So it turned out that this user having that issue turned off the MIUI Optimizations in the developer settings.
Bug report: συσκευη, εκδοση miui, Play store install (alpha 1021). It was impossible to specify a b i o s file or specify a game image directory in when MIUI optimizations are off. Turning them back on fixed the issue and directories are scanned normally. Also on the popup to allow folder access the app displays as "Anonymous" instead of AetherSX2 on my system. Some developer was talking about having the same issue here.

Issues traversing through directory hierarchy with Android Storage Access Framework / DocumentProvider using MTP

UPDATE:
My initial question may be misleading so I want to rephrase it:
I want to traverse through the hierarchy tree from an MTP connected device through Android's Storage Access Framework. I can't seem to achieve this because I get a SecurityException stating that a subnode is not a descendant of its parent node. Is there a way to workaround this issue? Or is this a known issue? Thanks.
I'm writing an Android application that attempts to traverse and access documents through the hierarchy tree using Android's Storage Access Framework (SAF) via the MtpDocumentsProvider. I am more or less following the code example described in https://github.com/googlesamples/android-DirectorySelection on how to launch the SAF Picker from my app, select the MTP data source, and then, in onActivityResult, use the returned Uri to traverse through the hierarchy. Unfortunately, this doesn't seem to work because as soon as I access a sub-folder and try to traverse that, I always get a SecurityException stating that document xx is not a descendant of yy
So my question is, using the MtpDocumentProvider, how can I successfully traverse through the hierarchy tree from my app and avoid this exception?
To be specific, in my app, first, I call the following method to launch the SAF Picker:
private void launchStoragePicker() {
Intent browseIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
browseIntent.addFlags(
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
| Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
);
startActivityForResult(browseIntent, REQUEST_CODE_OPEN_DIRECTORY);
}
The Android SAF picker then launches, and I see my connected device recognized as the MTP data source. I select said data source and I get the Uri from my onActivityResult:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_OPEN_DIRECTORY && resultCode == Activity.RESULT_OK) {
traverseDirectoryEntries(data.getData()); // getData() returns the root uri node
}
}
Then, using the returned Uri, I call DocumentsContract.buildChildDocumentsUriUsingTree to get a Uri which I can then use to query and access the tree hierarchy:
void traverseDirectoryEntries(Uri rootUri) {
ContentResolver contentResolver = getActivity().getContentResolver();
Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, DocumentsContract.getTreeDocumentId(rootUri));
// Keep track of our directory hierarchy
List<Uri> dirNodes = new LinkedList<>();
dirNodes.add(childrenUri);
while(!dirNodes.isEmpty()) {
childrenUri = dirNodes.remove(0); // get the item from top
Log.d(TAG, "node uri: ", childrenUri);
Cursor c = contentResolver.query(childrenUri, new String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME, Document.COLUMN_MIME_TYPE}, null, null, null);
try {
while (c.moveToNext()) {
final String docId = c.getString(0);
final String name = c.getString(1);
final String mime = c.getString(2);
Log.d(TAG, "docId: " + id + ", name: " + name + ", mime: " + mime);
if(isDirectory(mime)) {
final Uri newNode = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, docId);
dirNodes.add(newNode);
}
}
} finally {
closeQuietly(c);
}
}
}
// Util method to check if the mime type is a directory
private static boolean isDirectory(String mimeType) {
return DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType);
}
// Util method to close a closeable
private static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (RuntimeException re) {
throw re;
} catch (Exception ignore) {
// ignore exception
}
}
}
The first iteration on the outer while loop succeeds: the call to query returned a valid Cursor for me to traverse. The problem is the second iteration: when I try to query for the Uri, which happens to be a subnode of rootUri, I get a SecurityException stating the document xx is not a descendent of yy.
D/MyApp(19241): node uri: content://com.android.mtp.documents/tree/2/document/2/children
D/MyApp(19241): docId: 4, name: DCIM, mime: vnd.android.document/directory
D/MyApp(19241): node uri: content://com.android.mtp.documents/tree/2/document/4/children
E/DatabaseUtils(20944): Writing exception to parcel
E/DatabaseUtils(20944): java.lang.SecurityException: Document 4 is not a descendant of 2
Can anyone provide some insight as to what I'm doing wrong? If I use a different data source provider, for example, one that is from external storage (i.e. an SD Card attached via a standard USB OTG reader), everything works fine.
Additional information:
I'm running this on a Nexus 6P, Android 7.1.1, and my app minSdkVersion is 19.
I am using your code to go in sub folders with adding lines:
Uri childrenUri;
try {
//for childs and sub child dirs
childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, DocumentsContract.getDocumentId(uri));
} catch (Exception e) {
// for parent dir
childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri));
}
The flags added before start activity for result do nothing.
I do not understand that you use DocumentsContract. The user picks a directory. From the uri you get you can construct a DocumentFile for that directory.
After that use DocumentFile::listFiles() on that instance to get a list of subdirectories and files.
After different attempts, I'm not sure there is a way around this SecurityException for an MTP Data Source (unless someone can refute me on this). Looking at the DocumentProvider.java source code and the stack trace, it appears that the call to DocumentProvider#isChildDocument inside DocumentProvider#enforceTree may not have been properly overriden in the MtpDocumentProvider implementation in the Android framework. Default implementation always returns false. #sadface

android.permission.REMOVE_TASKS : Permission denied

I want to remove recent task list. Through googling I have found some links from stackoverflow. But none of them has been properly answered. Only one link
Android, how to clear the recent task list which could get from Home button in most phone? Reflection is a possible way?
has showed a way using reflection. But now, I am facing a problem, android.permission.REMOVE_TASKS is denied. The code below is given, where you can see that in the try-catch clause, as the permission is denied,the code moves to catch() clause.
enter code here
private ActivityManager mActivityManager = null;
private Method mRemoveTask;
public MyActivityManager(Context context) {
try {
Class<?> activityManagerClass = Class.forName("android.app.ActivityManager");
mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
mRemoveTask = activityManagerClass.getMethod("removeTask", new Class[] { int.class, int.class });
mRemoveTask.setAccessible(true);
}
catch ( ClassNotFoundException e ) {
Log.i("MyActivityManager", "No Such Class Found Exception", e);
}
catch ( Exception e ) {
Log.i("MyActivityManager", "General Exception occurred", e);
}
}
/**
* If set, the process of the root activity of the task will be killed
* as part of removing the task.
*/
public static final int REMOVE_TASK_KILL_PROCESS = 0x0001;
/**
* Completely remove the given task.
*
* #param taskId Identifier of the task to be removed.
* #param flags Additional operational flags. May be 0 or
* {#link #REMOVE_TASK_KILL_PROCESS}.
* #return Returns true if the given task was found and removed.
*/
public boolean removeTask(int taskId, int flags) {
try {
return (Boolean) mRemoveTask.invoke(mActivityManager, Integer.valueOf(taskId), Integer.valueOf(flags) );
} catch (Exception ex) {
Log.i("MyActivityManager", "Task removal failed", ex);
}
return false;
}
public void clearRecentTasks() {
List<RecentTaskInfo> recents = mActivityManager.getRecentTasks(1000, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
// Start from 1, since we don't want to kill ourselves!
for( int i=1; i < recents.size(); i++ ) {
removeTask( recents.get(i).persistentId, 0);
}
}
}`
Here to be noted that, I have used this three permissions android.permission.GET_TASKS, android.permission.REORDER_TASKS, android.permission.REMOVE_TASKS
some sources saying to kill background processes, but that will not remove the recent apps list.
So I need some suggestions.
android.permission.REMOVE_TASKS is denied
That permission is a signature-level permission. You will only be able to use it if your app is signed by the same signing key as the device firmware.

how can i send a skype message from My android app?

I tried to make an intent like calling intent
Intent skype = new Intent("android.intent.action.VIEW");
skype.setData(Uri.parse("skype:" + "user_name" + "?message=ddd"));
startActivity(skype);
and it did not work.
The problem with official Android SDK, as well as Skype URI's is that they not allow to share predefined message. You can just open chat with list of users (or empty to create a new one). If you want to explicity share some text with Skype you can try to use system Intents with Skype package name (remember to check if this package name is installed, otherwise startActivity call will crash your app):
val SKYPE_PACKAGE_NAME = "com.skype.raider"
fun shareSkype(context: Context, message: String) {
if (!isAppInstalled(context, SKYPE_PACKAGE_NAME)) {
openAppInGooglePlay(context, SKYPE_PACKAGE_NAME)
return
}
val intent = context.packageManager.getLaunchIntentForPackage(SKYPE_PACKAGE_NAME)
intent.action = Intent.ACTION_SEND
intent.putExtra(Intent.EXTRA_TEXT, message)
intent.type = "text/plain"
context.startActivity(intent)
}
fun isAppInstalled(context: Context, packageName: String): Boolean {
val packageManager = context.packageManager
try {
packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
} catch (e: PackageManager.NameNotFoundException) {
return false
}
return true
}
If Skype or other Android messaging applications do not have publicly available Intents, it will not be possible to do so until they are available.
You could however try to find a proxy service that skype uses to call within your application as a means to send messages.
http://developer.skype.com/skype-uris/reference#uriChats
Notice:
Caveats:
The optional topic argument applies to multi-chats only.
Special characters in the topic argument value—specifically whitespace—must be escaped.
Mac OS X: ignores any topic argument.
iOS: not supported.
Android: recognizes the initial participant only; multi-chats are not supported.
Android Docs - http://developer.skype.com/skype-uris/skype-uri-tutorial-android
/**
* Initiate the actions encoded in the specified URI.
*/
public void initiateSkypeUri(Context myContext, String mySkypeUri) {
// Make sure the Skype for Android client is installed
if (!isSkypeClientInstalled(myContext)) {
goToMarket(myContext);
return;
}
// Create the Intent from our Skype URI
Uri skypeUri = Uri.parse(mySkypeUri);
Intent myIntent = new Intent(Intent.ACTION_VIEW, skypeUri);
// Restrict the Intent to being handled by the Skype for Android client only
myIntent.setComponent(new ComponentName("com.skype.raider", "com.skype.raider.Main"));
myIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// Initiate the Intent. It should never fail since we've already established the
// presence of its handler (although there is an extremely minute window where that
// handler can go away...)
myContext.startActivity(myIntent);
return;
}

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

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)?
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);
}
}
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);
}
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)
There are 2 type of Non - system applications :
Apps downloaded from Google Play Store
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));
}
}
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
}
}
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
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
}
}
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
}
}
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;
}
}
if (!packageInfo.sourceDir.toLowerCase().startsWith("/system/"))
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();
}
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;
}
}
}
*/
}
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;
}
}
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")
}

Categories

Resources