Install / Unistall from shell command in Android
Asked Answered
M

6

13

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:

  1. 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).
  2. 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!!

Moderate answered 14/2, 2013 at 9:29 Comment(0)
M
8

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

  1. 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;
           }    
    }
}
  1. create in /src the package com.example.instuninsthelper. I have added there the ApplicationManager.java and OnInstalledPackage.java files
  2. 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);
            }
        }

    }
  1. 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);
}
  1. 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;
}
  1. build the application in Eclipse and deploy it to the emulator
  2. 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)
  3. 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):
  4. from the console, go to the /bin directory of the project, then enter: * adb push .apk /system/app
  5. finally, always from the console, enter: * adb shell am start -n com.example.silentinstuninst/com.example.silentinstuninst.MainActivity
  6. enjoy!
Moderate answered 19/2, 2013 at 10:19 Comment(1)
when you do a mount does it allow you to mount? Is your device rw permitted..in other words in your default.prop file is your value rosecure =0 instead of 1? When I try to run remount command it fails silently the ro permissions are not changed to rw permission. Pls help.Septempartite
W
2

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.

Whacky answered 14/2, 2013 at 9:56 Comment(0)
L
1

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!

Lillith answered 25/7, 2013 at 5:38 Comment(0)
E
0

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

Exterminate answered 14/2, 2013 at 9:34 Comment(3)
I'm sorry but it seems I'm not at your knowledge level: I don't understand 'has the interfaces publicly'. What you propose is exactly as per this post: paulononaka.wordpress.com/2011/07/02/…. Only that I've run his example and it fails (seems to be a signature problem). I'd appreciate if you can get into details. Why the code above is wrong? What means the 'class not found' error?Moderate
The blog is talking about something else.Exterminate
Thanks RvdK, I found the solution and explain it soon. It is based on the principles of that post, sorry for not being clearModerate
T
0

Runtime.getRuntime().exec("pm install /mnt/sdcard/HelloAndroid.apk\n");

This works for me, although two more additional details have to be done:

  1. Add android:sharedUserId="android.uid.system" in AndroidManifest.xml.

  2. 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.

Taconite answered 7/11, 2013 at 23:34 Comment(0)
C
0

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.

Currish answered 19/1, 2015 at 11:49 Comment(1)
Can you explain what is going on here?Quintie

© 2022 - 2024 — McMap. All rights reserved.