instalar / desinstalar APK mediante programación (PackageManager vs Intents)

141

Mi aplicación instala otras aplicaciones y necesita realizar un seguimiento de las aplicaciones que ha instalado. Por supuesto, esto podría lograrse simplemente manteniendo una lista de las aplicaciones instaladas. ¡Pero esto no debería ser necesario! Debería ser responsabilidad del PackageManager mantener la relación instalada ByB (a, b). De hecho, según la API es:

public abstract String getInstallerPackageName (String packageName): recupera el nombre del paquete de la aplicación que instaló un paquete. Esto identifica de qué mercado proviene el paquete.

El enfoque actual

Instalar APK usando Intención

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

Desinstalar APK usando Intención:

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

Obviamente, esta no es la forma, por ejemplo, Android Market instala / desinstala paquetes. Usan una versión más rica del PackageManager. Esto se puede ver descargando el código fuente de Android desde el repositorio de Android Git. A continuación se muestran los dos métodos ocultos que corresponden al enfoque de intención. Lamentablemente, no están disponibles para desarrolladores externos. Pero tal vez lo serán en el futuro?

El mejor enfoque

Instalar APK usando el 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);

Desinstalar APK usando el 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);

Las diferencias

  • Al usar intentos, el administrador de paquetes local no tiene conocimiento de qué aplicación se originó la instalación. Específicamente, getInstallerPackageName (...) devuelve nulo.

  • El método oculto installPackage (...) toma el nombre del paquete del instalador como parámetro y es muy probable que sea capaz de establecer este valor.

Pregunta

¿Es posible especificar el nombre del instalador del paquete usando intentos? (¿Tal vez el nombre del paquete de instalación se puede agregar como un extra a la intención de instalación?)

Consejo: Si desea descargar el código fuente de Android, puede seguir los pasos descritos aquí: Descargar el árbol de fuentes. Para extraer los archivos * .java y colocarlos en carpetas de acuerdo con la jerarquía del paquete, puede consultar este script ordenado: Ver el código fuente de Android en Eclipse .

Håvard Geithus
fuente
Faltan algunos de los URI en el texto. Los agregaré tan pronto como se me permita (los nuevos usuarios tienen algunas restricciones para evitar el spam)
Håvard Geithus
1
¿Cómo deshabilitar la funcionalidad de desinstalación?
2
@ user938893: "¿cómo deshabilitar la funcionalidad de desinstalación?" - Estamos trabajando en algún malware difícil de desinstalar, ¿verdad?
Daniel

Respuestas:

66

Actualmente no está disponible para aplicaciones de terceros. Tenga en cuenta que incluso usar la reflexión u otros trucos para acceder a installPackage () no ayudará, porque solo las aplicaciones del sistema pueden usarlo. (Esto se debe a que es el mecanismo de instalación de bajo nivel, después de que los permisos hayan sido aprobados por el usuario, por lo que no es seguro que las aplicaciones normales tengan acceso).

Además, los argumentos de la función installPackage () a menudo han cambiado entre las versiones de la plataforma, por lo que cualquier cosa que intente acceder a ella fallará en varias otras versiones de la plataforma.

EDITAR:

También vale la pena señalar que este paquete de instalador solo se agregó recientemente a la plataforma (2.2?) Y originalmente no se usó realmente para rastrear quién instaló la aplicación; la plataforma lo usa para determinar a quién lanzar cuando se informan errores con la aplicación, para implementar Android Feedback. (Esta fue también una de las veces que los argumentos del método API cambiaron). Durante al menos mucho tiempo después de que se introdujo, Market todavía no lo usó para rastrear las aplicaciones que ha instalado (y es muy posible que todavía no lo use ), pero solo usé esto para configurar la aplicación Android Feedback (que era independiente de Market) como el "propietario" para encargarse de los comentarios.

hackbod
fuente
"Tenga en cuenta que incluso usar la reflexión u otros trucos para acceder a installPackage () no ayudará, porque solo las aplicaciones del sistema pueden usarlo". Supongamos que estoy haciendo una aplicación de instalación / eliminación / gestión de paquetes para una plataforma determinada, que no sea el propio Android nativo. ¿Cómo debo acceder a instalar / eliminar?
dascandy
startActivity () con un Intent adecuadamente formado. (Estoy seguro de que esto se ha respondido en otro lugar en StackOverflow, por lo que no intentaré dar la respuesta exacta aquí con riesgo de que algo
salga
mmmkay, que muestra los cuadros de diálogo estándar de instalación / eliminación de Android. Esos detalles ya se han manejado: estoy buscando las funciones "solo **** instalar este paquete" y "simplemente **** eliminar este paquete", literalmente sin preguntas.
dascandy
2
Como dije, estos no están disponibles para aplicaciones de terceros. Si está creando su propia imagen del sistema, tiene la implementación de la plataforma y puede encontrar las funciones allí, pero no forman parte de las API disponibles para las aplicaciones normales de terceros.
Hackbod
Estoy haciendo el explorador de archivos apk con la funcionalidad de instalación, eliminación y copia de seguridad, entonces ¿Google me permite publicar mi aplicación en Google Play? ¿Y qué política vamos a romper?
Rahul Mandaliya
85

Android P + requiere este permiso en AndroidManifest.xml

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

Luego:

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

para desinstalar Parece más fácil ...

JohnyTex
fuente
¿Puede ser esta la aplicación que ejecuta el código? como en el onDestroy()método?
Mahdi-Malv
¿Qué tal ACTION_INSTALL_PACKAGE? ¿podemos descargar e instalar la última versión de nuestra aplicación desde Play Store?
MAS. John
3
Dado que Android P eliminar aplicaciones requiere permiso manifiesto "android.permission.REQUEST_DELETE_PACKAGES" sin importar si usa "ACTION_DELETE" o "ACTION_UNINSTALL_PACKAGE" developer.android.com/reference/android/content/…
Darklord5
Gracias por mencionar el permiso de Android P, estaba atascado y no estaba seguro de lo que estaba pasando antes.
Avi Parshan
43

El nivel 14 de la API introdujo dos nuevas acciones: ACTION_INSTALL_PACKAGE y ACTION_UNINSTALL_PACKAGE . Esas acciones le permiten pasar EXTRA_RETURN_RESULT boolean extra para obtener una notificación de resultado (no) de instalación.

Código de ejemplo para invocar el cuadro de diálogo de desinstalación:

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

Y reciba la notificación en su método 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
fuente
¿Cómo puedo confirmar desde este cuadro de diálogo de acción que cualquiera de los usuarios ha presionado ok o cancelar para que pueda tomar la decisión basada en esto
Erum
2
@Erum He agregado un ejemplo de lo que pediste
Alex Lipov,
En la instalación, el botón cancelar no devolvió el resultado al método
onActivityResult
2
Comenzando con API 25, las llamadas ACTION_INSTALL_PACKAGErequerirán el REQUEST_INSTALL_PACKAGESpermiso de nivel de firma . Del mismo modo, comenzando con API 28 (Android P), las llamadas ACTION_UNINSTALL_PACKAGErequerirán un REQUEST_DELETE_PACKAGESpermiso no peligroso . Al menos según los documentos.
Steve Blackwell
22

Si tiene permiso de propietario del dispositivo (o propietario del perfil, no lo he probado), puede instalar / desinstalar silenciosamente paquetes utilizando la API del propietario del dispositivo.

para desinstalar:

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

e instalar el paquete:

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
fuente
Sabía que tenía que ser posible hacer esto siendo el dispositivo propietario. ¡Gracias por la respuesta!
Luke Cauthen
@sandeep solo lee el contenido del APK en la secuencia de salida
Ohad Cohen
@LukeCauthen, ¿has intentado ser propietario del dispositivo? funcionó?
NetStarter
@NetStarter Sí, lo tengo. Es simplemente un fastidio conseguir que una aplicación sea propietaria del dispositivo. Una vez que haces eso, obtienes mucha potencia que normalmente requeriría root.
Luke Cauthen
1
Tenga en cuenta que debe agregar android.permission.DELETE_PACKAGES a su manifiesto para que la desinstalación funcione (probado en Api nivel 22 o inferior)
benchuk
4

La única forma de acceder a esos métodos es a través de la reflexión. Puede obtener un identificador de un PackageManagerobjeto llamando getApplicationContext().getPackageManager()y utilizando el acceso de reflexión a estos métodos. Mira este tutorial.

HandlerExploit
fuente
Esto funciona muy bien con 2.2, pero no he tenido suerte al usarlo con 2.3
Someone Somewhere
3
La reflexión no es estable en todas las versiones de la API
HandlerExploit
3

Según el código fuente de Froyo, se consulta la clave adicional Intent.EXTRA_INSTALLER_PACKAGE_NAME para obtener el nombre del paquete del instalador en PackageInstallerActivity.

njzk2
fuente
1
Al mirar este commit , creo que debería funcionar
sergio91pt
2

En un dispositivo rooteado, puede usar:

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() se define aquí.

18446744073709551615
fuente
¿Hay alguna manera de instalar una aplicación pre-descargada también en sdcard? ¿O puede sugerirme a alguna página para verificar qué comandos podemos usar en shell en la plataforma Android?
Yahya
1
@yahya developer.android.com/tools/help/shell.html encontrado por la frase "pm android", pm = administrador de paquetes
18446744073709551615
1
@yahya cheatography.com/citguy/cheat-sheets/android-package-manager-pm set-install-location
18446744073709551615
¡Muchas gracias! Estos enlaces son guías realmente geniales para comenzar :)
yahya
@ V.Kalyuzhnyu Solía ​​funcionar en 2015. IIRC era un Samsung Galaxy, quizás S5.
18446744073709551615
2

Si está pasando el nombre del paquete como parámetro a cualquiera de las funciones definidas por el usuario, utilice el siguiente código:

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

Si está utilizando Kotlin, API 14+, y solo desea mostrar el diálogo de desinstalación para su aplicación:

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

Puede cambiar packageNamea cualquier otro nombre de paquete si desea solicitar al usuario que desinstale otra aplicación en el dispositivo

Louis CAD
fuente
0

Requisito previo:

Su APK debe estar firmado por el sistema como se señaló anteriormente correctamente. Una forma de lograrlo es construir la imagen AOSP usted mismo y agregar el código fuente a la compilación.

Código:

Una vez instalado como una aplicación del sistema, puede usar los métodos del administrador de paquetes para instalar y desinstalar un APK de la siguiente manera:

Instalar en pc:

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

Desinstalar:

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

Para recibir una devolución de llamada una vez que su APK esté instalado / desinstalado, puede usar esto:

/**
 * 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);
}
Febo
fuente