I want to implement a silent installer-from-apk-file and unistaller-package in Android. The topic has largely been discussed on SO and elsewhere but I can't apply any for some reason that I'm missing. The scope is obviously hard to achieve because, if successful, it would be a serious security breach in Android. BUT, I need to implement it for a special project, not for the consumer market. There are two approaches:
- to generate a custom ROM from a source code (AOSP or Cyanogen mod, for example), by tweaking the PackageManager installer (in fact just to remove the user acceptance dialog boxes).
- to do it programmatically by creating a process as super user and executing an 'adb shell pm install'. I previously installed 'su' in /system/xbin and I test during run time that RootTools.rootIsAvailable().
For the first case, I digged into the Froyo source code but got into a dead end with a @hide marked method. For the second I've first tried the commands from the terminal
adb shell pm install /mnt/sdcard/HelloAndroid.apk
and
adb shell pm uninstall com.example.helloandroid
Both work OK. Then, I used the following code, the development being tested on a rooted emulator (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;
}
}
To avoid typos and other trims I hardcoded the apk file parameter of the command for the installation; on 'case R.id.btnInstall' the command is not executed and the exit is on "Failure" with exit value 1, meaning that "the class cannot be found"; no clue what that means ... I appreciate your help!
EDITED: I have the clean solution, I shall post the answer from A-Z as soon as I have the time and the code in the right form!!
As I promised here is the solution to this problem, without doing any forcing to the system other than having to install the whole application in the /system/app directory. I have followed, then did some fixing to the excellent article here: http://paulononaka.wordpress.com/2011/07/02/how-to-install-a-application-in-background-on-android/. I have downloaded the zip file referenced in the article then, (I tried to keep the same class names where possible):
- created a new project and a main activity as entry point
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;
}
}
}
- create in /src the package com.example.instuninsthelper. I have added there the ApplicationManager.java and OnInstalledPackage.java files
- inserted the following code inside the ApplicationManager class:
private OnDeletedPackage onDeletedPackage;
class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
public void packageDeleted(boolean succeeded) throws RemoteException {
if (onDeletedPackage != null) {
onDeletedPackage.packageDeleted(succeeded);
}
}
}
- created, under the same com.example.instuninsthelper package the file OnDeletedPackage.java with the following code:
package com.example.instuninsthelper;
public interface OnDeletedPackage {
public void packageDeleted(boolean succeeded);
}
- in the android.content.pm package (the namespace SHOULD not be changed) I modified the IPackageDeleteObserver.java, with this result:
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;
}
- build the application in Eclipse and deploy it to the emulator
- in the emulator: home button > Settings > applications > ...uninstall the application (because it is not installed in /system/app, and we just needed the generation of the apk file)
- do the following to root the emulator (so that we can write in /system/app; other solution, that I have used, is to generate a custom ROM with this app included into the /system/app):
- download the su file from here http://forum.xda-developers.com/showthread.php?t=682828http://forum.xda-developers.com/showthread.php?t=682828. Rename it to su.zip
- then from the console: * adb shell mount -o rw,remount -t yaffs2 /dev/block/mtdblock03 /system * adb push su.zip /system/xbin/su * adb shell chmod 06755 /system * adb shell chmod 06755 /system/xbin/su
- from the console, go to the /bin directory of the project, then enter: * adb push .apk /system/app
- finally, always from the console, enter: * adb shell am start -n com.example.silentinstuninst/com.example.silentinstuninst.MainActivity
- enjoy!
Don't know, but just a idea:
I think that you are writing in the standarout, not executing a command nor giving extra data to the process via its input. I think it should be:
Runtime.getRuntime().exec("pm install /mnt/sdcard/HelloAndroid.apk\n");
Hope this helps.
Installing in the /system/app
directory is essentially the same as requiring root.
Assuming you have root, check out RootTools. Then you can do:
if (RootTools.isAccessGiven()) {
CommandCapture command = new CommandCapture(0, "pm install " + PATH_TO_APK);
RootTools.getShell(true).add(command).waitForFinish();
}
Note that waitForFinish()
is a blocking call!
Well you can do this also with the PackageManager directly (requires root access):
- Create an app with a platform-sdk which has the interfaces publicly (create or download it, and configure eclipse)
- In the app directly call the hidden API functions which allow silent install/remove.
- Install the APK on your device as a system app by copying it to /system/app (root needed)
See this: http://forum.xda-developers.com/showthread.php?t=1711653
Runtime.getRuntime().exec("pm install /mnt/sdcard/HelloAndroid.apk\n");
This works for me, although two more additional details have to be done:
Add android:sharedUserId="android.uid.system" in AndroidManifest.xml.
Signed the apk with the system key.
But in this way it seems there is no way to tell whether the installation is succeeded, so I will try @Ginger's method later.
For all who are still having problem: you will need a rooted device and use
Process result = Runtime.getRuntime().exec("pm install -r -d MyApp.apk /system/app")
If you are getting result code 9 (error code 9) you will need to delete your apk from the device and push it back (PUSH not INSTAL!).
Go to the device shell and Push the 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"
Now you are able to use pm install without getting an error. Hope it will help somebody.
来源:https://stackoverflow.com/questions/14871570/install-unistall-from-shell-command-in-android