installer / désinstaller des APK par programme (PackageManager vs Intents)

142

Mon application installe d'autres applications et doit garder une trace des applications qu'elle a installées. Bien sûr, cela pourrait être réalisé en conservant simplement une liste des applications installées. Mais cela ne devrait pas être nécessaire! Il devrait être de la responsabilité du PackageManager de maintenir la relation InstallBy (a, b). En fait, selon l'API c'est:

public abstract String getInstallerPackageName (String packageName) - Récupère le nom du package de l'application qui a installé un package. Cela identifie de quel marché provient le colis.

L'approche actuelle

Installer l'APK en utilisant l'intention

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(intent);

Désinstallez l'APK avec Intent:

Intent intent = new Intent(Intent.ACTION_DELETE, Uri.fromParts("package",
getPackageManager().getPackageArchiveInfo(apkUri.getPath(), 0).packageName,null));
startActivity(intent);

Ce n'est évidemment pas la façon dont, par exemple, Android Market installe / désinstalle les packages. Ils utilisent une version plus riche de PackageManager. Cela peut être vu en téléchargeant le code source Android à partir du référentiel Android Git. Vous trouverez ci-dessous les deux méthodes cachées qui correspondent à l'approche Intent. Malheureusement, ils ne sont pas disponibles pour les développeurs externes. Mais peut-être le seront-ils dans le futur?

La meilleure approche

Installer l'APK à l'aide de 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);

Désinstaller l'APK à l'aide de 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);

Différences

  • Lors de l'utilisation d'intents, le gestionnaire de packages local n'est pas informé de l'application d'origine de l'installation. Plus précisément, getInstallerPackageName (...) renvoie null.

  • La méthode masquée installPackage (...) prend le nom du package du programme d'installation comme paramètre et est probablement capable de définir cette valeur.

Question

Est-il possible de spécifier le nom du programme d'installation du package à l'aide d'intents? (Peut-être que le nom du package d'installation peut être ajouté en supplément à l'intention d'installation?)

Conseil: Si vous souhaitez télécharger le code source Android, vous pouvez suivre les étapes décrites ici: Téléchargement de l'arborescence source. Pour extraire les fichiers * .java et les placer dans des dossiers selon la hiérarchie des packages, vous pouvez consulter ce script soigné: Afficher le code source Android dans Eclipse .

Håvard Geithus
la source
Certains des URI sont manquants dans le texte. Je les ajouterai dès que j'y serai autorisé (les nouveaux utilisateurs ont certaines restrictions pour éviter le spam).
Håvard Geithus le
1
comment désactiver la fonctionnalité de désinstallation?
2
@ user938893: "comment désactiver la fonctionnalité de désinstallation?" - Nous travaillons sur des logiciels malveillants difficiles à désinstaller, n'est-ce pas?
Daniel

Réponses:

66

Ce n'est actuellement pas disponible pour les applications tierces. Notez que même l'utilisation de la réflexion ou d'autres astuces pour accéder à installPackage () n'aidera pas, car seules les applications système peuvent l'utiliser. (C'est parce qu'il s'agit du mécanisme d'installation de bas niveau, une fois que les autorisations ont été approuvées par l'utilisateur, il n'est donc pas sûr pour les applications normales d'y accéder.)

De plus, les arguments de la fonction installPackage () ont souvent changé entre les versions de la plate-forme, donc tout ce que vous essayez d'y accéder échouera sur diverses autres versions de la plate-forme.

ÉDITER:

Il convient également de noter que ce programme d'installation n'a été ajouté que récemment à la plate-forme (2.2?) Et n'était à l'origine pas utilisé pour suivre qui a installé l'application - il est utilisé par la plate-forme pour déterminer qui lancer lors du signalement de bogues avec l'application, pour mettre en œuvre Android Feedback. (Ce fut également l'une des fois où les arguments de la méthode API ont changé.) Pendant au moins un long moment après son introduction, Market ne l'a toujours pas utilisé pour suivre les applications qu'il a installées (et il se peut très bien qu'il ne l'utilise toujours pas. ), mais l'a simplement utilisé pour définir l'application Android Feedback (qui était distincte de Market) en tant que "propriétaire" pour prendre en charge les commentaires.

hackbod
la source
"Notez que même utiliser la réflexion ou d'autres astuces pour accéder à installPackage () n'aidera pas, car seules les applications système peuvent l'utiliser." Supposons que je crée une application d'installation / suppression / gestion de package pour une plate-forme donnée, autre qu'Android natif lui-même. Comment dois-je accéder à l'installation / suppression?
dascandy
startActivity () avec une intention correctement formée. (Je suis sûr que cela a été répondu ailleurs sur StackOverflow, donc je n'essaierai pas de donner la réponse exacte ici au risque de me tromper.)
hackbod
mmmkay, qui fait apparaître les boîtes de dialogue d'installation / suppression standard d'Android. Ces détails ont déjà été traités - je recherche les fonctions "juste **** installer ce paquet" et "juste **** supprimer ce paquet", littéralement aucune question posée.
dascandy
2
Comme je l'ai dit, ceux-ci ne sont pas disponibles pour les applications tierces. Si vous créez votre propre image système, vous disposez de l'implémentation de la plate-forme et vous pouvez y trouver les fonctions, mais elles ne font pas partie des API disponibles pour les applications tierces normales.
hackbod
Je crée un explorateur de fichiers apk avec des fonctionnalités d'installation, de suppression et de sauvegarde, Google me permet-il donc de continuer à publier mon application sur Google Play? et quelle politique allons-nous rompre?
Rahul Mandaliya
86

Android P + nécessite cette autorisation dans AndroidManifest.xml

<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />

Ensuite:

Intent intent = new Intent(Intent.ACTION_DELETE);
intent.setData(Uri.parse("package:com.example.mypackage"));
startActivity(intent);

pour désinstaller. Cela semble plus facile ...

JohnyTex
la source
Cela peut-il être l'application exécutant le code? comme dans la onDestroy()méthode?
Mahdi-Malv
et ACTION_INSTALL_PACKAGE? pouvons-nous télécharger et installer la dernière version de notre application à partir du Play Store?
MAS. John
3
Étant donné qu'Android P, la suppression d'applications nécessite l'autorisation manifeste "android.permission.REQUEST_DELETE_PACKAGES", peu importe si vous utilisez "ACTION_DELETE" ou "ACTION_UNINSTALL_PACKAGE" developer.android.com/reference/android/content/…
Darklord5
Merci d'avoir mentionné l'autorisation Android P, j'étais bloqué et je n'étais pas sûr de ce qui se passait auparavant.
Avi Parshan
43

Le niveau 14 de l'API a introduit deux nouvelles actions: ACTION_INSTALL_PACKAGE et ACTION_UNINSTALL_PACKAGE . Ces actions vous permettent de passer EXTRA_RETURN_RESULT booléen supplémentaire pour obtenir une notification de (dés) installation de résultat.

Exemple de code pour appeler la boîte de dialogue de désinstallation:

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

Et recevez la notification dans votre méthode Activity # onActivityResult :

@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");
        }
    }
}
Pir Fahim Shah
la source
comment puis-je confirmer à partir de cette boîte de dialogue d'action que l'un ou l'autre utilisateur a appuyé sur ok ou annuler afin que je puisse prendre la décision en fonction de cela
Erum
2
@Erum J'ai ajouté un exemple pour ce que vous avez demandé
Alex Lipov
Lors de l'installation, le bouton d'annulation n'a pas renvoyé de résultat à la méthode
onActivityResult
2
À partir de l'API 25, l'appel ACTION_INSTALL_PACKAGEnécessitera l' REQUEST_INSTALL_PACKAGESautorisation au niveau de la signature . De même, à partir de l'API 28 (Android P), l'appel ACTION_UNINSTALL_PACKAGEnécessitera l' REQUEST_DELETE_PACKAGESautorisation non dangereuse . Au moins selon la documentation.
Steve Blackwell
22

Si vous disposez de l'autorisation de propriétaire de l'appareil (ou du propriétaire du profil, je n'ai pas essayé), vous pouvez installer / désinstaller silencieusement des packages à l'aide de l'API du propriétaire de l'appareil.

pour la désinstallation:

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

et pour installer le 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;
}
Ohad Cohen
la source
Je savais qu'il devait être possible de faire cela en étant l'appareil propriétaire. Merci d'avoir répondu!
Luke Cauthen le
@sandeep il lit simplement le contenu de l'APK dans le flux de sortie
Ohad Cohen
@LukeCauthen avez-vous essayé en tant que propriétaire de l'appareil? cela a-t-il fonctionné?
NetStarter
@NetStarter Oui, je l'ai. C'est juste une douleur dans le cul d'obtenir une application pour être propriétaire d'un appareil. Une fois que vous faites cela, vous obtenez beaucoup de puissance qui nécessiterait normalement root.
Luke Cauthen
1
Veuillez noter que vous devez ajouter android.permission.DELETE_PACKAGES à votre manifeste pour que la désinstallation fonctionne (testé sur l'API niveau 22 ou moins)
benchuk
4

La seule façon d'accéder à ces méthodes est la réflexion. Vous pouvez obtenir une poignée sur un PackageManagerobjet en appelant getApplicationContext().getPackageManager()et en utilisant la réflexion pour accéder à ces méthodes. Consultez ce tutoriel.

HandlerExploit
la source
Cela fonctionne très bien avec la version 2.2, mais je n'ai pas eu de chance de l'utiliser avec la version 2.3
Someone Somewhere
3
La réflexion n'est pas stable dans toutes les versions de l'
API
3

Selon le code source de Froyo, la clé supplémentaire Intent.EXTRA_INSTALLER_PACKAGE_NAME est interrogée pour le nom du package d'installation dans PackageInstallerActivity.

njzk2
la source
1
En regardant ce commit, je pense que cela devrait fonctionner
sergio91pt
2

Sur un appareil enraciné, vous pouvez utiliser:

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() est défini ici.

18446744073709551615
la source
Existe-t-il également un moyen d'installer une application pré-téléchargée dans sdcard? Ou pouvez-vous me suggérer une page pour vérifier quelles commandes nous pouvons utiliser sur le shell sur la plate-forme Android?
yahya
1
@yahya developer.android.com/tools/help/shell.html trouvé par l'expression "pm android", pm = package manager
18446744073709551615
1
@yahya cheatography.com/citguy/cheat-sheets/android-package-manager-pm set-install-location
18446744073709551615
Merci beaucoup! Ces liens sont des guides vraiment sympas pour commencer :)
yahya
@ V.Kalyuzhnyu Il fonctionnait en 2015. IIRC c'était un Samsung Galaxy, peut-être S5.
18446744073709551615
2

Si vous passez le nom du package en tant que paramètre à l'une de vos fonctions définies par l'utilisateur, utilisez le code ci-dessous:

    Intent intent=new Intent(Intent.ACTION_DELETE);
    intent.setData(Uri.parse("package:"+packageName));
    startActivity(intent);
Rashwin SM
la source
0

Si vous utilisez Kotlin, API 14+, et que vous souhaitez simplement afficher la boîte de dialogue de désinstallation de votre application:

startActivity(Intent(Intent.ACTION_UNINSTALL_PACKAGE).apply {
    data = Uri.parse("package:$packageName")
})

Vous pouvez packageNameutiliser n'importe quel autre nom de package si vous souhaitez inviter l'utilisateur à désinstaller une autre application sur l'appareil

Louis CAD
la source
0

Prérequis:

Votre APK doit être signé par le système comme indiqué précédemment. Une façon d'y parvenir consiste à créer vous-même l'image AOSP et à ajouter le code source dans la construction.

Code:

Une fois installé en tant qu'application système, vous pouvez utiliser les méthodes du gestionnaire de packages pour installer et désinstaller un APK comme suit:

Installer:

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

Désinstaller:

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

Pour avoir un rappel une fois que votre APK est installé / désinstallé, vous pouvez utiliser ceci:

/**
 * 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);
}
phoebus
la source