Search code examples
androidapkandroid-launcherpackageinstaller

How to properly install an APK file, so that the launcher will create a new app icon of it on the home screen?


Background

I have a spare time app which I've made (here), that one of its main features is to install APK files.

The problem

Users who install apps expects that a new app icon will appear on the launcher.

This can occur from the Play Store, but for some reason launchers ignore other types of installing.

Something is different in the way the Play Store installs apps, than how third party apps do it.

I'd like to know how to do it properly, like on the Play Store, so that installing an APK file will also create an app icon.

What I've tried

The only way I've found to install an APK is as such:

@Nullable
public static Intent prepareAppInstallationIntent(Context context, File file, final boolean requestResult) {
    Intent intent = null;
    try {
        intent = new Intent(Intent.ACTION_INSTALL_PACKAGE)//
                .setDataAndType(
                        VERSION.SDK_INT >= VERSION_CODES.N ?
                                android.support.v4.content.FileProvider.getUriForFile(context, context.getPackageName() + ".provider", file)
                                : Uri.fromFile(file),
                        "application/vnd.android.package-archive")
                .putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
                .putExtra(Intent.EXTRA_RETURN_RESULT, requestResult)
                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        if (VERSION.SDK_INT < VERSION_CODES.JELLY_BEAN)
            intent.putExtra(Intent.EXTRA_ALLOW_REPLACE, true);
    } catch (Throwable e) {
    }
    return intent;
}

manifest

<provider
  android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true">
  <meta-data
    android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/>
</provider>

xml/provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
  <!--<external-path name="external_files" path="."/>-->
  <external-path
    name="files_root" path="Android/data/${applicationId}"/>
  <external-path
    name="external_storage_root" path="."/>
</paths>

But this doesn't create the app icon on the launcher.

Writing about this on the issue tracker (here), I got a clue of what I should do. I was informed that :

Icons are added to the home screen if the app is installed via the new PackageInstaller APIs (and proper install reason is provided). Installing an app for command like does not support this feature.

The questions

  1. Is there an API for third party apps to install an app properly, having a new icon for it on the launcher? If so, how exactly?

  2. Is there a way to do it even using root ? And using adb command via PC ?


Solution

  • Here's how it's done using the Intent (need to know the package name of the app) :

    fun prepareAppInstallationIntent(context: Context, file: File, requestResult: Boolean, packageName: String? = null): Intent? {
        var intent: Intent? = null
        try {
            intent = Intent(Intent.ACTION_INSTALL_PACKAGE) //
                .setDataAndType(
                    if (VERSION.SDK_INT >= VERSION_CODES.N)
                        androidx.core.content.FileProvider.getUriForFile(
                            context,
                            context.packageName,
                            file
                        )
                    else
                        Uri.fromFile(file),
                    "application/vnd.android.package-archive"
                )
                .putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
                .putExtra(Intent.EXTRA_RETURN_RESULT, requestResult)
                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            if (packageName != null && VERSION.SDK_INT >= VERSION_CODES.N) {
                intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
            }
            intent.putExtra(Intent.EXTRA_ALLOW_REPLACE, true)
        } catch (e: Throwable) {
        }
        return intent
    }