Je souhaite implémenter un package d'installation à partir d'un fichier apk et un package unistaller silencieux sous Android. Le sujet a été largement discuté sur SO et ailleurs, mais je ne peux en appliquer aucune pour une raison qui me manque. La portée est évidemment difficile à atteindre car, en cas de succès, ce serait une grave atteinte à la sécurité sous Android. MAIS, je dois l’appliquer pour un projet spécial, pas pour le marché grand public. Il y a deux approches:
Pour le premier cas, j'ai fouillé dans le code source Froyo, mais je me suis retrouvé dans une impasse avec une méthode marquée @hide. Pour la seconde, j'ai d'abord essayé les commandes du terminal
adb Shell pm install /mnt/sdcard/HelloAndroid.apk
et
adb Shell pm uninstall com.example.helloandroid
Les deux fonctionnent bien. Ensuite, j'ai utilisé le code suivant, le développement étant testé sur un émulateur enraciné (2.2 - Froyo):
@Override
public void onClick(View v) {
switch (v.getId())
{
case R.id.btnInstall:
try {
install = Runtime.getRuntime().exec("su\n");
DataOutputStream os = new DataOutputStream(install.getOutputStream());
os.writeBytes("pm install /mnt/sdcard/HelloAndroid.apk\n");
os.writeBytes("exit\n");
os.flush();
install.waitFor();
if (install.exitValue() == 0) {
Toast.makeText(MainActivity.this, "Success!", Toast.LENGTH_LONG).show();
}
else {
Toast.makeText(MainActivity.this, "Failure. Exit code: "+String.valueOf(install.exitValue()), Toast.LENGTH_LONG).show();
}
}
catch (InterruptedException e) {
logError(e);
}
catch (IOException e) {
logError(e);
}
break;
case R.id.btnUninstall:
try {
install = Runtime.getRuntime().exec("su\n");
install=Runtime.getRuntime().exec("pm uninstall "+txtPackageName.getText().toString()+"\n");
} catch (Exception e) {
logError(e);
}
break;
}
}
Pour éviter les fautes de frappe et autres corrections, j'ai codé en dur le paramètre de fichier apk de la commande pour l'installation; sur 'case R.id.btnInstall', la commande n'est pas exécutée et l'exit est sur "Failure" avec la valeur de sortie 1, ce qui signifie que "la classe ne peut pas être trouvée"; aucune idée de ce que cela signifie ... J'apprécie votre aide!
EDITED: J'ai la solution propre, je posterai la réponse de A à Z dès que j'en aurai le temps et le code sous la bonne forme !!
Comme je vous l'avais promis, voici la solution à ce problème, sans forcer le système à installer le logiciel dans le répertoire/system/app. J'ai suivi, puis fait quelques corrections sur l'excellent article ici: http://paulononaka.wordpress.com/2011/07/02/how-to-install-a-application-in-background-on-Android/ . J'ai alors téléchargé le fichier Zip référencé dans l'article ((j'ai essayé de conserver les mêmes noms de classe, si possible)):
package com.example.silentinstuninst;
import Java.io.File;
import Java.lang.reflect.InvocationTargetException;
import com.example.instuninsthelper.ApplicationManager;
import com.example.instuninsthelper.OnDeletedPackage;
import com.example.instuninsthelper.OnInstalledPackage;
import Android.os.Bundle;
import Android.os.Environment;
import Android.app.Activity;
import Android.util.Log;
import Android.view.View;
import Android.view.View.OnClickListener;
import Android.widget.Button;
import Android.widget.EditText;
import Android.widget.Toast;
public class MainActivity extends Activity implements OnClickListener {
Process install;
Button btnInstall, btnUninstall;
EditText txtApkFileName, txtPackageName;
public static final String TAG = "SilentInstall/Uninstall";
private static ApplicationManager am;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initializeValues();
}
private void initializeValues() {
btnInstall = (Button) findViewById(R.id.btnInstall);
btnUninstall = (Button) findViewById(R.id.btnUninstall);
txtApkFileName = (EditText) findViewById(R.id.txtApkFilePath);
txtPackageName = (EditText) findViewById(R.id.txtPackageName);
btnInstall.setOnClickListener(this);
btnUninstall.setOnClickListener(this);
try {
am = new ApplicationManager(this);
am.setOnInstalledPackage(new OnInstalledPackage() {
public void packageInstalled(String packageName, int returnCode) {
if (returnCode == ApplicationManager.INSTALL_SUCCEEDED) {
Log.d(TAG, "Install succeeded");
} else {
Log.d(TAG, "Install failed: " + returnCode);
}
}
});
am.setOnDeletedPackage(new OnDeletedPackage() {
public void packageDeleted(boolean succeeded) {
Log.d(TAG, "Uninstall succeeded");
}
});
} catch (Exception e) {
logError(e);
}
}
private void logError(Exception e) {
e.printStackTrace();
Toast.makeText(this, R.string.error+e.getMessage(), Toast.LENGTH_LONG).show();
}
@Override
public void onClick(View v) {
switch (v.getId())
{
case R.id.btnInstall:
// InstallUninstall.Install(txtApkFileName.getText().toString());
try {
am.installPackage(Environment.getExternalStorageDirectory() +
File.separator + txtApkFileName.getText().toString());
} catch (IllegalArgumentException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IllegalAccessException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (InvocationTargetException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} // install package
break;
case R.id.btnUninstall:
// InstallUninstall.Uninstall(txtPackageName.getText().toString());
try {
am.uninstallPackage(txtPackageName.getText().toString());
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
logError(e);
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
logError(e);
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
logError(e);
}
break;
}
}
}
private OnDeletedPackage onDeletedPackage;
class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
public void packageDeleted(boolean succeeded) throws RemoteException {
if (onDeletedPackage != null) {
onDeletedPackage.packageDeleted(succeeded);
}
}
}
package com.example.instuninsthelper;
public interface OnDeletedPackage {
public void packageDeleted(boolean succeeded);
}
package Android.content.pm;
public interface IPackageDeleteObserver extends Android.os.IInterface {
public abstract static class Stub extends Android.os.Binder implements Android.content.pm.IPackageDeleteObserver {
public Stub() {
throw new RuntimeException("Stub!");
}
public static Android.content.pm.IPackageDeleteObserver asInterface(Android.os.IBinder obj) {
throw new RuntimeException("Stub!");
}
public Android.os.IBinder asBinder() {
throw new RuntimeException("Stub!");
}
public boolean onTransact(int code, Android.os.Parcel data, Android.os.Parcel reply, int flags)
throws Android.os.RemoteException {
throw new RuntimeException("Stub!");
}
}
public abstract void packageDeleted(boolean succeeded)
throws Android.os.RemoteException;
}
Je ne sais pas, mais juste une idée:
Je pense que vous écrivez en standarout, n'exécutez pas de commande ni ne donnez plus de données au processus via son entrée. Je pense que cela devrait être:
Runtime.getRuntime().exec("pm install /mnt/sdcard/HelloAndroid.apk\n");
J'espère que cela t'aides.
L'installation dans le répertoire /system/app
est essentiellement la même chose que si vous avez besoin de root.
En supposant que vous ayez la racine, consultez RootTools . Ensuite, vous pouvez faire:
if (RootTools.isAccessGiven()) {
CommandCapture command = new CommandCapture(0, "pm install " + PATH_TO_APK);
RootTools.getShell(true).add(command).waitForFinish();
}
Notez que waitForFinish()
est un appel bloquant!
Vous pouvez aussi le faire directement avec le PackageManager (nécessite un accès root):
Voir ceci: http://forum.xda-developers.com/showthread.php?t=1711653
Runtime.getRuntime (). Exec ("pm install /mnt/sdcard/HelloAndroid.apk\n");
Cela fonctionne pour moi, bien que deux autres détails supplémentaires doivent être apportés:
Ajouter Android: sharedUserId = "Android.uid.system" dans AndroidManifest.xml.
Signé l'apk avec la clé système.
Mais de cette manière, il semble qu'il n'y ait aucun moyen de savoir si l'installation est réussie. Je vais donc essayer la méthode de @ Ginger plus tard.
Pour tous ceux qui ont encore des problèmes: vous aurez besoin d’un appareil enraciné et vous utiliserez
Process result = Runtime.getRuntime().exec("pm install -r -d MyApp.apk /system/app")
Si vous obtenez le code de résultat 9 (code d'erreur 9), vous devez supprimer votre apk de l'appareil et le repousser (ne pas appuyer sur INSTAL!).
Allez à l'appareil Shell et appuyez sur l'apk
launcher=MyApp.apk
$adb Shell su -c "mount -o remount,rw -t rfs /dev/stl5 /system"
$adb Push $launcher /sdcard/$launcher
$adb Shell su -c "chmod 644 /system/app/$launcher"
Vous pouvez maintenant utiliser pm install sans erreur. J'espère que ça va aider quelqu'un.