/**
*
*/
package com.asksven.android.common.utils;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.content.res.AssetManager;
import android.os.Build;
import android.util.Log;
import android.widget.Toast;
import com.asksven.android.common.RootShell;
import com.stericson.RootTools.RootTools;
/**
* @author sven
*
*/
public class SystemAppInstaller
{
static final String TAG = "SystemAppInstaller";
static final String SYSTEM_DIR_4_4 = "/system/priv-app";
static final String SYSTEM_DIR = "/system/app";
static final String REMOUNT_SYSTEM_RW = "mount -o rw,remount /system";
static final String REMOUNT_SYSTEM_RO = "mount -o ro,remount /system";
// returns ro or rw
// static final String CHECK_MOUNT_STATE = "mount | grep /system | awk '{print $4}' | awk -F\",\" '{print $1}'";
static final String CHECK_MOUNT_STATE = "mount | grep /system";
public static boolean mountSystemRw()
{
if (isSystemRw()) return true;
Log.i(TAG, "Remount system rw");
RootShell.getInstance().run(REMOUNT_SYSTEM_RW);
return isSystemRw();
}
public static boolean mountSystemRo()
{
if (!isSystemRw()) return true;
Log.i(TAG, "Remount system ro");
RootShell.getInstance().run(REMOUNT_SYSTEM_RO);
return !isSystemRw();
}
public static boolean isSystemRw()
{
boolean ret = false;
Log.i(TAG, "Checking if system is mounted rw");
try
{
ret = RootTools.getMountedAs("/system").equals("rw");
}
catch (Exception e)
{
Log.e(TAG, "isSystemRw failed: " + e.getMessage());
ret = false;
}
// List<String> res = RootShell.getInstance().run(CHECK_MOUNT_STATE);
// if (res.size() > 0)
// {
// String[] tokens = res.get(0).split(" |,");
// String mountState = tokens[3];
// Log.i(TAG, "Mount status: " + mountState);
// ret = (mountState.equals("rw"));
// }
return ret;
}
public static boolean isSystemApp(String apk)
{
boolean ret = false;
List<String> res;
String command = "";
if (Build.VERSION.SDK_INT >= 19)
{
command = "ls " + SYSTEM_DIR_4_4 + "/" + apk;
}
else
{
command = "ls " + SYSTEM_DIR + "/" + apk;
}
Log.i(TAG, "Checking if " + apk + " is a system app");
res = RootShell.getInstance().run(command);
if (res.size() > 0)
{
Log.i(TAG, "Command returned "+ res.get(0));
ret = !res.get(0).contains("No such file or directory");
}
return ret;
}
// static boolean installAsSystemApp(Context ctx, String apk)
// {
// String command = "";
// String commandCopyBack = "";
// String commandTouch = "";
//
// // get the original filename
// command = "ls /data/app/" + apk + "*";
// List<String> res = RootShell.getInstance().run(command);
//
// // we copy all the instances of the file to /system (preserving timestamp)
// // at that point the APK will get deleted from /data/app (by the system)
// // so we need to copy the APK back to /data/app (with a new timestamp)
// for (int i=0; i < res.size(); i++)
// {
// String fileName = res.get(0).split("/")[3];
// // remove the -1 from filename to make sure the target filename is different
//
// if (Build.VERSION.SDK_INT >= 19)
// {
// commandTouch = "touch -t 20080801 " + SYSTEM_DIR_4_4 + "/" + fileName;
// command = "cp -p /data/app/" + fileName + " " +SYSTEM_DIR_4_4
// + " && chmod 644 " + SYSTEM_DIR_4_4 + "/" + fileName
// + " && chown root:root " + SYSTEM_DIR_4_4 + "/" + fileName;
// commandCopyBack = "cp " + SYSTEM_DIR_4_4 + "/" + fileName + " /data/app/"
// + " && chmod 644 /data/app/" + fileName
// + " && chown system:system /data/app/" + fileName;
// }
// else
// {
// commandTouch = "touch -t 20080801 " + SYSTEM_DIR + "/" + fileName;
// command = "cp -p /data/app/" + fileName + " " + SYSTEM_DIR
// + " && chmod 644 " + SYSTEM_DIR + "/" + fileName
// + " && chown root:root " + SYSTEM_DIR + "/" + fileName;
// commandCopyBack = "cp " + SYSTEM_DIR + "/" + fileName + " /data/app/"
// + " && chmod 644 /data/app/" + fileName
// + " && chown system:systems /data/app/" + fileName;
// }
//
//
// Log.i(TAG, "Installing app as system app: " + command);
// RootShell.getInstance().run(command);
//
// Log.i(TAG, "Copy APK back to /data/app: " + commandCopyBack);
// RootShell.getInstance().run(commandCopyBack);
//
// Log.i(TAG, "Changing timestamp: " + commandTouch);
// RootShell.getInstance().run(commandTouch);
//
// }
// return isSystemApp(apk);
// }
static boolean installAsSystemApp(Context ctx, String apk)
{
String command = "";
String tempPath = "/sdcard/";
// actions:
// copy apk from /sdcard to /system in order to be able to set ownership and perms
// then copy the file to the target. The sequence is important as copying first and setting perms and ownership
// afterward will cause PackageParser to fail parsing the package
if (Build.VERSION.SDK_INT >= 19)
{
command = "cp " + tempPath + apk + " /system" + " && chmod 644 " + "/system/" + apk
+ " && chown root:root /system/" + apk + " && cp -p /system/" + apk + " " + SYSTEM_DIR_4_4 + " && rm " + tempPath + apk + " && rm /system/" + apk;
}
else
{
command = "cp " + tempPath + apk + " /system" + " && chmod 644 " + "/system/" + apk
+ " && chown root:root /system/" + apk + " && cp -p /system/" + apk + " " + SYSTEM_DIR + " && rm " + tempPath + apk + " && rm /system/" + apk;
}
copyAsset(ctx, apk, tempPath);
Log.i(TAG, "Copying, setting permissions and owner and cleaning up: " + command);
RootShell.getInstance().run(command);
return isSystemApp(apk);
}
static boolean uninstallAsSystemApp(String apk)
{
String command = "";
if (Build.VERSION.SDK_INT >= 19)
{
command = "rm " + SYSTEM_DIR_4_4 + "/" + apk + "*";
}
else
{
command = "rm " + SYSTEM_DIR + "/" + apk + "*";
}
Log.i(TAG, "Uninstalling system app: " + command);
RootShell.getInstance().run(command);
return !isSystemApp(apk);
}
public static Status install(Context ctx, String apk)
{
Status status = new Status();
SystemAppInstaller.mountSystemRw();
if (SystemAppInstaller.isSystemRw())
{
status.add("Mounted system rw");
SystemAppInstaller.installAsSystemApp(ctx, apk);
status.add("Install as system app");
if (SystemAppInstaller.isSystemApp(apk))
{
SystemAppInstaller.mountSystemRo();
if (!SystemAppInstaller.isSystemRw())
{
status.add("Mounted system ro. Finished");
}
else
{
status.add("An error while remounting system to ro. Warning!");
status.m_success = true;
}
}
else
{
status.add("An error while installing app. Aborted");
status.m_success = false;
}
}
else
{
status.add("An error occured mounting system rw. Aborted");
status.m_success = false;
}
return status;
}
private static void copyAsset(Context ctx, String assetName, String targetPath)
{
AssetManager assetManager = ctx.getAssets();
String[] files = null;
try
{
files = assetManager.list("");
}
catch (IOException e)
{
Log.e("tag", e.getMessage());
}
for(String filename : files)
{
if (filename.equals(assetName))
{
InputStream in = null;
OutputStream out = null;
try
{
in = assetManager.open(filename);
String strOutFile = targetPath + "/" + filename;
out = new FileOutputStream(strOutFile);
copyFile(in, out);
in.close();
in = null;
out.flush();
out.close();
out = null;
}
catch(Exception e)
{
Log.e(TAG, "An error occured while reading " + filename);
}
}
}
}
/**
* Write a single file
* @param in the source (in assets)
* @param out the target
* @throws IOException
*/
private static void copyFile(InputStream in, OutputStream out) throws IOException
{
byte[] buffer = new byte[1024];
int read;
while((read = in.read(buffer)) != -1)
{
out.write(buffer, 0, read);
}
}
/**
* Value holder for status
* @param apk
* @return
*/
public static Status uninstall(String apk)
{
Status status = new Status();
SystemAppInstaller.mountSystemRw();
if (SystemAppInstaller.isSystemRw())
{
status.add("Mounted system rw");
SystemAppInstaller.uninstallAsSystemApp(apk);
status.add("Uninstall as system app");
if (!SystemAppInstaller.isSystemApp(apk))
{
SystemAppInstaller.mountSystemRo();
if (!SystemAppInstaller.isSystemRw())
{
status.add("Mounted system ro. Finished");
}
else
{
status.add("An error while remounting system to ro. Aborted");
status.m_success = false;
}
}
else
{
status.add("An error while uninstalling app. Aborted");
status.m_success = false;
}
}
else
{
status.add("An error occured mounting system rw. Aborted");
status.m_success = false;
}
return status;
}
public static class Status
{
List<String> m_status = new ArrayList<String>();
boolean m_success = true;
void add(String text)
{
Log.i(TAG, "Status: " + text);
m_status.add(text);
}
public boolean success()
{
return m_success;
}
public boolean getSuccess()
{
return m_success;
}
public String toString()
{
String ret = "";
for (int i=0; i < m_status.size(); i++)
{
ret += m_status.get(i) + "\n";
}
return ret;
}
}
}