package com.google.android.diskusage.datasource.fast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import com.google.android.diskusage.datasource.DataSource;
public class NativeScannerStream extends InputStream {
private final InputStream is;
private final Process process;
public NativeScannerStream(InputStream is, Process process) {
this.is = is;
this.process = process;
}
@Override
public int read() throws IOException {
return is.read();
}
@Override
public int read(byte[] buffer) throws IOException {
return is.read(buffer);
}
@Override
public int read(byte[] buffer, int byteOffset, int byteCount)
throws IOException {
return is.read(buffer, byteOffset, byteCount);
}
@Override
public void close() throws IOException {
is.close();
try {
process.waitFor();
} catch (InterruptedException e) {
throw new IOException(e.getMessage());
}
}
static class Factory {
private final Context context;
private static boolean remove = true;
Factory(Context context) {
this.context = context;
}
public NativeScannerStream create(String path, boolean rootRequired)
throws IOException, InterruptedException {
return runScanner(path, rootRequired);
}
private NativeScannerStream runScanner(String root,
boolean rootRequired) throws IOException, InterruptedException {
String binaryName = "scan";
final int sdkVersion = DataSource.get().getAndroidVersion();
if (sdkVersion >= 21 /* Lollipop */) {
binaryName = "scan5";
}
setupBinary(binaryName);
boolean deviceIsRooted = DataSource.get().isDeviceRooted();
Process process = null;
if (!(rootRequired && deviceIsRooted)) {
process = Runtime.getRuntime().exec(new String[] {
getScanBinaryPath(binaryName), root});
} else {
IOException e = null;
for (String su : new String[] { "su", "/system/bin/su", "/system/xbin/su" }) {
try {
process = Runtime.getRuntime().exec(new String[] { su });
break;
} catch(IOException newe) {
e = newe;
}
}
if (process == null) {
throw e;
}
OutputStream os = process.getOutputStream();
os.write((getScanBinaryPath(binaryName) + " " + root).getBytes("UTF-8"));
os.flush();
os.close();
}
InputStream is = process.getInputStream();
return new NativeScannerStream(is, process);
}
public void setupBinary(String binaryName)
throws IOException, InterruptedException {
// Remove 'scan' binary every run. TODO: do clean update on package update
// if (remove) {
new File(getScanBinaryPath(binaryName)).delete();
// remove = false;
// }
File binary = new File(getScanBinaryPath(binaryName));
if (binary.isFile()) return;
unpackScanBinary(binaryName);
runChmod(binaryName);
}
private String getScanBinaryPath(String binaryName) {
return context.getDir("binary", Context.MODE_PRIVATE).getAbsolutePath()
+ "/" + binaryName;
}
private void runChmod(String binaryName)
throws IOException, InterruptedException {
if (Integer.parseInt(Build.VERSION.SDK) >= VERSION_CODES.GINGERBREAD) {
try {
setExecutable(binaryName);
return;
} catch (Exception e) {
// fall back to legacy way
}
}
Process process;
try {
process = Runtime.getRuntime().exec(
"chmod 0555 " + getScanBinaryPath(binaryName));
} catch (IOException e) {
try {
process = Runtime.getRuntime().exec(
"/system/bin/chmod 0555 " + getScanBinaryPath(binaryName));
} catch (IOException ee ) {
throw new RuntimeException("Failed to chmod", ee);
}
}
process.waitFor();
}
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
private void setExecutable(String binaryName) {
if (new File(binaryName).setExecutable(true, true) == false) {
throw new RuntimeException("Failed to setExecutable");
}
}
private void unpackScanBinary(String binaryName) throws IOException {
InputStream is = context.getAssets().open(binaryName);
FileOutputStream os = new FileOutputStream(getScanBinaryPath(binaryName));
StreamCopy.copyStream(is, os);
}
}
}