[Java for Android App Development] Torsionick:
Listing:

MainActivity:

package com.tsk.mk;

import android.app.*;
import android.content.*;
import android.os.*;
import android.widget.*;
import java.io.*;

public class MainActivity extends Activity {

public static TextView logView;

[MENTION=439709]override[/MENTION]
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
logView = (TextView) findViewById(R.id.log);

log("creating sercice");
final MainActivity me = this;
new Thread() {
[MENTION=439709]override[/MENTION]
public void run() {
log("thread.run");
startService(new Intent(me, MissedKeysService.class));
log("thread: service shoul'd be created");
}
}.start();
log("end of MA.onCreate method");
}

static void log(String message) {
logView.setText(logView.getText() + "\n" + message);
}

}

MissedKeysService:

package com.tsk.mk;

import android.app.*;
import android.content.*;
import android.graphics.*;
import android.os.*;
import android.view.*;
import java.io.*;

public class MissedKeysService extends Service {

private WindowManager windowManager;
private MissedKeysView mkv;
private WindowManager.LayoutParams params;
private DataOutputStream dos;

[MENTION=439709]override[/MENTION]
public IBinder onBind(Intent intent) {
MainActivity.log("onBind called o_o");
return null;
}

[MENTION=439709]override[/MENTION]
public void onCreate() {
MainActivity.log("Service.onCreate");
super.onCreate();

try {
java.lang.Process p = Runtime.getRuntime().exec("su");
dos = new DataOutputStream(p.getOutputStream());
} catch (IOException ex) {
MainActivity.log(ex.getMessage());
dos = null;
}

windowManager = (WindowManager)
getSystemService(WINDOW_SERVICE);
mkv = new MissedKeysView(this, this);
params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
params.width = MissedKeysView.keySize;
params.height = MissedKeysView.keySize;
windowManager.addView(mkv, params);
MainActivity.log("Service started");
}

[MENTION=439709]override[/MENTION]
public void onDestroy () {
super.onDestroy ();
if (mkv != null) windowManager.removeView(mkv);
MainActivity.log("Ssrvice is ended");
}

public void extend(boolean state) {
params.height = mkv.keySize * (state ? MissedKeysView.keys : 1);
windowManager.updateViewLayout(mkv, params);
}

public void sendKey(int code) {
if (dos == null) MainActivity.log("dos is null");
try {
dos.writeBytes("input keyevent " + code + "\n");
dos.flush();
MainActivity.log("" + code);
} catch (IOException e) {
MainActivity.log("wtf?");
}
}

}

MissedKeysView:

package com.tsk.mk;

import android.content.*;
import android.graphics.*;
import android.view.*;
import android.view.View.*;
import java.io.*;

public class MissedKeysView extends View
implements OnTouchListener {

final static int keySize = 64;
final static String[] labels = {":", "+", "-", "^", "v", "o", "<", ">"};
final private int[] codes = {-1, 24, 25, 19, 20, 23, 21, 22};
final static int keys = 3; //max shown keys

MissedKeysService mks;
Paint bgP; //background
Paint hbgP; //highlighted bg
Paint tP; //text
int selected = -1; //active key

public MissedKeysView(Context context, MissedKeysService mks) {
super(context);
this.mks = mks;
bgP = new Paint();
bgP.setARGB(64, 128, 128, 128);
hbgP = new Paint();
hbgP.setARGB(64, 255, 128, 0);
tP = new Paint();
tP.setARGB(128, 255, 255, 255);
tP.setTextSize(keySize);
tP.setTextAlign(Paint.Align.CENTER);
setOnTouchListener(this);
}

[MENTION=439709]override[/MENTION]
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for(int i=0; i<getHeight()/keySize; i++) {
canvas.drawCircle(keySize/2,
keySize/2 + i*keySize, keySize/2,
(i == selected ? hbgP : bgP));
canvas.drawText(labels[i], keySize/2, i*keySize + keySize*3/4, tP);
}
}

[MENTION=439709]override[/MENTION]
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
selected = (int) (event.getY()/keySize);
if (selected == 0) mks.extend((int) getHeight() <= keySize);
else mks.sendKey(codes[selected]);
break;
case MotionEvent.ACTION_UP:
selected =-1;
}
invalidate();
return super.onTouchEvent(event);
}

}

AndroidManifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tsk.mk"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="11" />

<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".MainActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<service
android:name=".MissedKeysService"
android:exported="true" >
</service>
</application>

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACCESS_SUPERUSER" />
<uses-permission android:name="android.permission.INJECT_EVENTS" />

</manifest>