package andraus.bluetoothhidemu.spoof;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.ParcelUuid;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import andraus.bluetoothhidemu.R;
import andraus.bluetoothhidemu.spoof.Spoof.SpoofMode;
import andraus.bluetoothhidemu.spoof.jni.BluetoothSocketJni;
import andraus.bluetoothhidemu.util.DoLog;
public class BluetoothAdapterSpooferGeneric extends BluetoothAdapterSpoofer {
private static final String CMD_SU = "su";
private static final String CMD_ID = "id\n";
private static final String CMD_ID_RESP = "uid=0(root)";
private static final String CMD_READ_CLASS = " read_class\n";
private static final String CMD_READ_CLASS_RESP = "class";
private static final String CMD_SPOOF_CLASS = " spoof_class 0x%06X\n";
private static final String CMD_SPOOF_CLASS_RESP = "class spoofed.";
private static final String CMD_ADD_HID_SDP_GENERIC = " add_hid_generic\n";
private static final String CMD_ADD_HID_SDP_BDREMOTE = " add_hid_bdremote\n";
private static final String CMD_ADD_HID_SDP_PS3KEYPAD = " add_hid_ps3keypad\n";
private static final String CMD_ADD_HID_SDP_RESP = "handle";
private static final String CMD_DEL_HID_SDP = " del_hid 0x%06X\n";
private String mHidEmuPath = null;
private int mHidSdpHandle;
/**
*
* @param appContext
* @param adapter
*/
protected BluetoothAdapterSpooferGeneric(Context appContext,
BluetoothAdapter adapter) {
super(appContext, adapter);
}
/**
*
*/
private class ShellResponse {
static final int ERROR = -0x1;
static final int SUCCESS = 0x0;
static final int NON_ROOT = 0xff;
int code = -1;
String msg;
}
private String getHidEmuPath() {
return "/data/data/" + mContext.getPackageName() + "/hid_emu";
}
private ShellResponse installHidEmu() {
boolean result = true;
ShellResponse shellResp = null;
mHidEmuPath = getHidEmuPath();
DoLog.d(TAG, "Checking existence of " + mHidEmuPath);
File hidEmu = new File(mHidEmuPath);
if (!hidEmu.exists()) {
DoLog.d(TAG, "hid_emu does not exist. Installing...");
InputStream inStream = null;
OutputStream outStream = null;
try {
inStream = mContext.getAssets().open("hid_emu");
outStream = new FileOutputStream(mHidEmuPath);
byte[] buffer = new byte[1024];
int size;
while ((size = inStream.read(buffer)) > 0) {
outStream.write(buffer, 0, size);
}
outStream.flush();
} catch (IOException e) {
DoLog.e(TAG, "Failed to install hid_emu:", e);
result = false;
} finally {
if (outStream != null)
try {
outStream.close();
} catch (IOException e) {
DoLog.e(TAG, "Failed to close output stream: ", e);
}
if (inStream != null)
try {
inStream.close();
} catch (IOException e) {
DoLog.e(TAG, "Failed to close input stream: ", e);
}
}
DoLog.d(TAG, "hid_emu installed. Setting permissions...");
shellResp = executeShellCmd("chmod 744 " + mHidEmuPath + "\n");
result = (shellResp.code == 0);
}
if (result) {
shellResp = executeShellCmd(mHidEmuPath + "\n");
if (shellResp.code == 0) {
DoLog.d(TAG, "hid_emu version: " + shellResp.msg);
}
}
return shellResp;
}
private ShellResponse executeShellCmd(String cmd) {
ShellResponse shellResponse = new ShellResponse();
Process process = null;
try {
process = Runtime.getRuntime().exec(CMD_SU);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
writer.write(cmd);
writer.flush();
writer.write("exit\n");
writer.flush();
String line = null;
while ((line = reader.readLine()) != null) {
shellResponse.msg = line;
DoLog.d(TAG, "shell: " + shellResponse.msg);
}
shellResponse.code = process.waitFor();
DoLog.d(TAG, "process exit value: " + shellResponse.code);
} catch (IOException e) {
DoLog.d(TAG, "ioexception: ", e);
// Probably su doesn't exist.
shellResponse.code = ShellResponse.NON_ROOT;
} catch (InterruptedException e) {
DoLog.e(TAG, "interrupted exception: ", e);
shellResponse.code = ShellResponse.ERROR;
} finally {
if (process != null) {
process.destroy();
}
}
return shellResponse;
}
@Override
protected int getBluetoothDeviceClass() {
int[] clazz = BluetoothSocketJni.readBluetoothDeviceClass();
/**
* result is 0xaabbcc
* so:
* clazz[2] = aa
* clazz[1] = bb
* clazz[0] = cc
*/
return (clazz[2] << 16) + (clazz[1] << 8) + (clazz[0]);
}
@Override
protected int spoofBluetoothDeviceClass(int deviceClass) {
mOriginalDeviceClass = getBluetoothDeviceClass();
DoLog.d(TAG, String.format("original class stored: 0x%06X", mOriginalDeviceClass));
ShellResponse shellResp = executeShellCmd(mHidEmuPath + String.format(CMD_SPOOF_CLASS, deviceClass));
if (shellResp.code != 0) {
throw new IllegalStateException("Unexpected failure");
}
if (CMD_SPOOF_CLASS_RESP.equals(shellResp.msg)) {
return 0;
} else {
return -1;
}
}
@Override
protected int addHidDeviceSdpRecord(SpoofMode mode) {
if (mHidSdpHandle != 0) {
DoLog.w(TAG, String.format("HID SDP record already present. Handle: 0x%06X",mHidSdpHandle));
return mHidSdpHandle;
}
String cmd;
switch (mode) {
case HID_BDREMOTE:
cmd = CMD_ADD_HID_SDP_BDREMOTE;
break;
case HID_PS3KEYPAD:
cmd = CMD_ADD_HID_SDP_PS3KEYPAD;
break;
case HID_GENERIC:
default:
cmd = CMD_ADD_HID_SDP_GENERIC;
break;
}
ShellResponse shellResp = executeShellCmd(mHidEmuPath + cmd);
if (shellResp.code != 0) {
throw new IllegalStateException("Unexpected failure");
}
/*
* response from hid_emu add_hid is like:
* handle: 0xaabbcc
*
* using ": 0x" as reg. exp for split, will result in
* token[0] = handle
* token[1] = aabbcc
*/
String[] token = shellResp.msg.split(": 0x");
if (CMD_ADD_HID_SDP_RESP.equals(token[0])) {
mHidSdpHandle = Integer.parseInt(token[1], 16);
}
return mHidSdpHandle;
}
/**
* Removes the SDP record of mHidSdpHandle
*/
protected void delHidDeviceSdpRecord() {
if (mHidSdpHandle == 0) {
DoLog.w(TAG, "No HID SDP record handle present.");
return;
}
ShellResponse shellResp = executeShellCmd(mHidEmuPath + String.format(CMD_DEL_HID_SDP, mHidSdpHandle));
if (shellResp.code != 0) {
throw new IllegalStateException("Unexpected failure");
}
mHidSdpHandle = 0;
/*
* response from hid_emu del_hid <handle> is like:
* Removed handle: 0xaabbcc
*/
}
@Override
public BluetoothSocket connectL2capSocket(BluetoothDevice device, int port,
boolean auth, boolean encrypt) throws IOException {
int fd = BluetoothSocketJni.createL2capFileDescriptor(auth, encrypt);
DoLog.d(TAG, "L2CAP socket File descriptor: " + fd);
return createL2capBluetoothSocket(device, port, auth, encrypt, fd);
}
private BluetoothSocket createL2capBluetoothSocket(BluetoothDevice device, int port,
boolean auth, boolean encrypt, int fd) throws IOException {
final int TYPE_L2CAP = 3;
BluetoothSocket socket = null;
Class<?>[] argsClasses = new Class[] { int.class /* type */,
int.class /* fd */,
boolean.class /* auth */,
boolean.class /* encrypt */,
BluetoothDevice.class /* device */,
int.class /* port */,
ParcelUuid.class /* uuid */ };
Object[] args = new Object[] { Integer.valueOf(TYPE_L2CAP),
Integer.valueOf(fd),
Boolean.valueOf(auth),
Boolean.valueOf(encrypt),
device,
Integer.valueOf(port),
null };
try {
Class<BluetoothSocket> bluetoothSocketClass = BluetoothSocket.class;
Constructor<BluetoothSocket> bluetoothSocketConstructor;
bluetoothSocketConstructor = bluetoothSocketClass.getDeclaredConstructor(argsClasses);
bluetoothSocketConstructor.setAccessible(true);
socket = bluetoothSocketConstructor.newInstance(args);
} catch (SecurityException e) {
DoLog.e(TAG, "Reflection error:", e);
throw new IOException("Reflection error", e);
} catch (NoSuchMethodException e) {
DoLog.e(TAG, "Reflection error:", e);
throw new IOException("Reflection error", e);
} catch (IllegalArgumentException e) {
DoLog.e(TAG, "Reflection error:", e);
throw new IOException("Reflection error", e);
} catch (InstantiationException e) {
DoLog.e(TAG, "Reflection error:", e);
throw new IOException("Reflection error", e);
} catch (IllegalAccessException e) {
DoLog.e(TAG, "Reflection error:", e);
throw new IOException("Reflection error", e);
} catch (InvocationTargetException e) {
DoLog.e(TAG, "Reflection error:", e);
throw new IOException("Reflection error", e);
}
return socket;
}
@Override
public boolean requirementsCheck() {
ShellResponse shellResp = executeShellCmd(CMD_ID);
switch (shellResp.code) {
case ShellResponse.NON_ROOT:
mSetupErrorMsg = mContext.getResources().getString(R.string.msg_no_root);
return false;
case ShellResponse.SUCCESS:
if (shellResp.msg != null && shellResp.msg.contains(CMD_ID_RESP)) {
mSetupErrorMsg = null;
shellResp = installHidEmu();
if (shellResp.code != 0) {
String errorMsg = shellResp.msg == null ? Integer.toString(shellResp.code) : shellResp.msg;
mSetupErrorMsg = mContext.getResources().getString(R.string.msg_generic_failure, errorMsg);
}
return (shellResp.code == 0);
} else {
mSetupErrorMsg = mContext.getResources().getString(R.string.msg_no_root);
return false;
}
case ShellResponse.ERROR:
default:
mSetupErrorMsg = shellResp.msg;
return false;
}
}
}