From 27e58b98fa26d9f9986d9421bec61e0eb837fc07 Mon Sep 17 00:00:00 2001 From: Steve Slaven Date: Tue, 4 Jan 2011 15:11:37 -0800 Subject: Moved main key sending stuff in to globals, added new smaller activity to receive the intents, intents are generic now allowing sending any keycode once the extras are set diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 85ac68a..bf14161 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -33,12 +33,6 @@ - - - - - - + + + + + + + + + diff --git a/src/net/hoopajoo/android/SoftKeys/Globals.java b/src/net/hoopajoo/android/SoftKeys/Globals.java index de6c76b..50f23a1 100644 --- a/src/net/hoopajoo/android/SoftKeys/Globals.java +++ b/src/net/hoopajoo/android/SoftKeys/Globals.java @@ -18,18 +18,27 @@ */ package net.hoopajoo.android.SoftKeys; +import java.io.File; +import java.io.FileOutputStream; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; import android.app.Application; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.preference.PreferenceManager; import android.provider.Settings; import android.util.Log; +import android.widget.Toast; public class Globals extends Application { private CommandShell cmd = null; private String android_id = null; + private String LOG = "SoftKeys.Global"; public boolean restartKeys = false; public int homeCounter = 0; @@ -45,7 +54,7 @@ public class Globals extends Application { // # cat /system/bin/sh > /data/tmp/su // # chmod 6755 /data/tmp/su // # mount -oremount,suid /dev/block/mtdblock1 /data - Log.d( "softkeys", "Detected emulator" ); + Log.d( LOG, "Detected emulator" ); cmd = new CommandShell( "/data/tmp/su" ); }else{ cmd = new CommandShell( "su" ); @@ -55,23 +64,61 @@ public class Globals extends Application { return( cmd ); } + // this is a string of keydown/keyup events by key id + public int sendKeys( List a ) { + return sendKeys( listToInt( a ) ); + } + + public int sendKeys( int[] keyids ) { + try { + Globals.CommandShell cmd = getCommandShell(); + + // run our key script + String wd = getFilesDir().getAbsolutePath(); + + // check if we have a dev script + File script = new File( wd + "/pushkey.dev" ); + + // check if we have a test script + if( script.exists() ) { + Log.d( LOG, "Using dev key script" ); + }else{ + // write out our default script + script = new File( wd + "/pushkey" ); + FileOutputStream out = new FileOutputStream( script ); + out.write( "for f in $* ; do input keyevent $f ; done\n".getBytes( "ASCII" ) ); + out.close(); + } + + // source the file since datadata might be noexec + String keyid = ""; + cmd.system( "sh " + script.getAbsolutePath() + " " + keyid ); + }catch( Exception e ) { + Log.e( LOG, "Error: " + e.getMessage() ); + Toast.makeText( this, "Unable to execute as root", Toast.LENGTH_LONG ).show(); + return 1; + } + + return 0; + } + public class CommandShell { Process p; OutputStream o; CommandShell( String shell ) throws Exception { - Log.d( "softkeys.cmdshell", "Starting shell: '" + shell + "'" ); + Log.d( "SoftKeys.cmdshell", "Starting shell: '" + shell + "'" ); p = Runtime.getRuntime().exec( shell ); o = p.getOutputStream(); } public void system( String cmd ) throws Exception { - Log.d( "softkeys.cmdshell", "Running command: '" + cmd + "'" ); + Log.d( "SoftKeys.cmdshell", "Running command: '" + cmd + "'" ); o.write( (cmd + "\n" ).getBytes( "ASCII" ) ); } public void close() throws Exception { - Log.d( "softkeys.cmdshell", "Destroying shell" ); + Log.d( "SoftKeys.cmdshell", "Destroying shell" ); o.flush(); o.close(); p.destroy(); @@ -80,6 +127,19 @@ public class Globals extends Application { @Override public void onCreate() { + // warn if we don't notice some binaries we need + for( String name : new String[] { "/system/bin/su", "/system/bin/input" } ) { + File check = new File( name ); + try { + if( ! check.exists() ) { + Toast.makeText( this, "Failed to find file: " + name + ", SoftKeys may not function", Toast.LENGTH_LONG ).show(); + } + }catch( Exception e ) { + Toast.makeText( this, "Unable to check for file: " + name, Toast.LENGTH_LONG ).show(); + } + + } + android_id = Settings.Secure.getString(this.getContentResolver(), Settings.Secure.ANDROID_ID); restartService(); } @@ -92,4 +152,50 @@ public class Globals extends Application { this.startService( new Intent( this, SoftKeysService.class ) ); } } + + private int[] listToInt( List a ) { + int[] ret = new int[ a.size() ]; + for( int i = 0; i < a.size(); i++ ) { + ret[ i ] = a.get( i ).intValue(); + } + return( ret ); + } + + public void doHomeAction( boolean longClick ) { + // special case + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences( this ); + + Intent ni = new Intent( Intent.ACTION_MAIN ); + String launcher = settings.getString( longClick ? "launcher2" : "launcher" , null ); + if( launcher == null ) { + launcher = getDefaultLauncher(); + } + ni.setPackage( launcher ); + ni.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity( ni ); + } + + private String getDefaultLauncher() { + // Set default launcher to the first launcher we find so we don't freak out if it's not + // set and there is no com.android.launcher + Intent i = new Intent( Intent.ACTION_MAIN ); + i.addCategory( Intent.CATEGORY_HOME ); + PackageManager p = getPackageManager(); + List packages = p.queryIntentActivities( i, 0 ); + + String defaultLauncher = null; + for( Iterator it = packages.iterator(); it.hasNext(); ) { + ResolveInfo info = it.next(); + if( defaultLauncher == null ) { + if( ! info.activityInfo.applicationInfo.packageName.equals( "net.hoopajoo.android.SoftKeys" ) ) { + defaultLauncher = info.activityInfo.applicationInfo.packageName; + } + } + } + if( defaultLauncher == null ) { + // last ditch + defaultLauncher = "com.android.launcher"; + } + return( defaultLauncher ); + } } diff --git a/src/net/hoopajoo/android/SoftKeys/Keys.java b/src/net/hoopajoo/android/SoftKeys/Keys.java index c4776f7..5f7f8c1 100644 --- a/src/net/hoopajoo/android/SoftKeys/Keys.java +++ b/src/net/hoopajoo/android/SoftKeys/Keys.java @@ -20,8 +20,12 @@ package net.hoopajoo.android.SoftKeys; import java.io.File; import java.io.FileOutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import android.app.Activity; import android.app.Notification; @@ -69,12 +73,8 @@ public class Keys extends Activity implements OnClickListener, OnLongClickListen private Runnable delayed_pretap_action ; private String homeaction; private int delayed_action_time; - - public static String ACTION_MENU = "net.hoopajoo.android.SoftKeys.KEY_MENU"; - public static String ACTION_HOME = "net.hoopajoo.android.SoftKeys.KEY_HOME"; - public static String ACTION_BACK = "net.hoopajoo.android.SoftKeys.KEY_BACK"; - public static String ACTION_SEARCH = "net.hoopajoo.android.SoftKeys.KEY_SEARCH"; - + + // simple typedef used to make the notifications a bit more generic private class NotificationButton { String mPrefKey; @@ -83,18 +83,20 @@ public class Keys extends Activity implements OnClickListener, OnLongClickListen Drawable mIcon; String mButtonText; String mAction; + String mExtraString; - NotificationButton( String text, String pref, RemoteViews view, Drawable d, int icon, String act ) { + NotificationButton( String text, String pref, RemoteViews view, Drawable d, int icon, String act, String extra ) { mButtonText = text; mPrefKey = pref; mView = view; mIconId = icon; mIcon = d; mAction = act; + mExtraString = extra; } NotificationButton( String text, String pref, int icon, String act ) { - this( text, pref, null, null, icon, act ); + this( text, pref, null, null, icon, act, null ); } } @@ -261,29 +263,29 @@ public class Keys extends Activity implements OnClickListener, OnLongClickListen theme.getRemoteViews( new String[] { "notification_menu" } ), theme.getDrawable( new String[] { "notification_menu" } ), R.drawable.button_menu, - ACTION_MENU ); + SendInput.ACTION_CODE, "menu" ); nb[ 2 ] = new NotificationButton( "Home", "nb_home", theme.getRemoteViews( new String[] { "notification_home" } ), theme.getDrawable( new String[] { "notification_home" } ), R.drawable.button_home, - ACTION_HOME ); + SendInput.ACTION_CODE, "home" ); nb[ 3 ] = new NotificationButton( "Back", "nb_back", theme.getRemoteViews( new String[] { "notification_back" } ), theme.getDrawable( new String[] { "notification_back" } ), R.drawable.button_back, - ACTION_BACK ); + SendInput.ACTION_CODE, "back" ); nb[ 4 ] = new NotificationButton( "Search", "nb_search", theme.getRemoteViews( new String[] { "notification_search" } ), theme.getDrawable( new String[] { "notification_search" } ), R.drawable.button_search, - ACTION_SEARCH ); + SendInput.ACTION_CODE, "back" ); for( NotificationButton b : nb ) { if( settings.getBoolean( b.mPrefKey, false ) ) { Notification n = new Notification( b.mIconId, null, 0 ); - PendingIntent i = PendingIntent.getActivity( this, 0, - new Intent( b.mAction, - null, this, Keys.class ), 0 ); + Intent si = new Intent( b.mAction ); + si.putExtra( "keyname", b.mExtraString ); + PendingIntent i = PendingIntent.getActivity( this, 0, si, 0 ); // if we got a drawable but no view then set up our own remote view // and add in their drawable @@ -370,7 +372,8 @@ public class Keys extends Activity implements OnClickListener, OnLongClickListen }catch( Exception e ) { } - d( "Updating last version code: " + force_level ); + + //d( "Updating last version code: " + force_level ); if( settings.getInt( "last_intro_level", 0 ) < force_level ) { Intent intent = new Intent( this, QuickDoc.class ); intent.putExtra( "type", "whats_new" ); @@ -421,33 +424,6 @@ public class Keys extends Activity implements OnClickListener, OnLongClickListen return; } } - - ///////// TODO: remove null junk - - // handle real actions - if( i != null ) { - String action = i.getAction(); - if( action != null ) { - int clickbutton = 0; - if( action.equals( ACTION_MENU ) ) { - clickbutton = R.id.menu; - } - if( action.equals( ACTION_HOME ) ) { - clickbutton = R.id.home; - } - if( action.equals( ACTION_BACK ) ) { - clickbutton = R.id.back; - } - if( action.equals( ACTION_SEARCH ) ) { - clickbutton = R.id.search; - } - if( clickbutton != 0 ) { - generic_click( clickbutton, false, false ); - // don't draw the ui - this.finish(); - } - } - } if( isPaused ) { //d( "detected paused, resetting counter" ); @@ -524,22 +500,15 @@ public class Keys extends Activity implements OnClickListener, OnLongClickListen } private boolean generic_click( int id, boolean longClick, boolean backout ) { - String keyid = ""; + List keyids = new ArrayList(); switch( id ) { case R.id.back: - // If backout=true we are in softkeys main ui so honor return to softkeys - // by pressing home after this - keyid = "4"; + keyids.add( 4 ); break; case R.id.home: // do whatever is selected - SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences( this ); - - Intent i = new Intent( Intent.ACTION_MAIN ); - i.setPackage( settings.getString( longClick ? "launcher2" : "launcher" , defaultLauncher ) ); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity( i ); + ((Globals)getApplication()).doHomeAction( longClick ); return true; case R.id.main_view: @@ -548,11 +517,11 @@ public class Keys extends Activity implements OnClickListener, OnLongClickListen return true; case R.id.menu: - keyid = "82"; + keyids.add( 82 ); break; case R.id.search: - keyid = "84"; + keyids.add( 84 ); break; case R.id.exit: @@ -560,52 +529,33 @@ public class Keys extends Activity implements OnClickListener, OnLongClickListen return true; default: - d( "Unkown click event: " + id ); + d( "Unknown click event: " + id ); return true; } + // if longclick then add another back, e.g. for apps that do something odd like pause when you + // open another app, so you can back out of that then send the intended key + if( longClick ) { + keyids.add( 0, 4 ); + } + + if( backout ) { + // if we need to back out of softkeys before we send the other keys + keyids.add( 0, 4 ); + } + + ((Globals)getApplication()).sendKeys( keyids ); + try { - Globals.CommandShell cmd = ((Globals)getApplication()).getCommandShell(); - - // run our key script - String wd = getFilesDir().getAbsolutePath(); - - // check if we have a dev script - File script = new File( wd + "/pushkey.dev" ); - - // check if we have a test script - if( script.exists() ) { - d( "Using dev key script" ); - }else{ - // write out our default script - script = new File( wd + "/pushkey" ); - FileOutputStream out = new FileOutputStream( script ); - out.write( "for f in $* ; do input keyevent $f ; done\n".getBytes( "ASCII" ) ); - out.close(); - } - - // if longclick then add another back, e.g. for apps that do something odd like pause when you - // open another app, so you can back out of that then send the intended key - if( longClick ) { - keyid = "4 " + keyid; - } - - if( backout ) { - // if we need to back out of softkeys before we send the other keys - keyid = "4 " + keyid; - } - - // source the file since datadata might be noexec - cmd.system( "sh " + script.getAbsolutePath() + " " + keyid ); - // if we sent back, and didn't backout (so it was from main ui) and they // want to return, run am to get us back if( id == R.id.back && backout && return_after_back ) { + Globals.CommandShell cmd = ((Globals)getApplication()).getCommandShell(); cmd.system( "am start -a android.intent.action.MAIN -n net.hoopajoo.android.SoftKeys/.Keys" ); } }catch( Exception e ) { - Log.e( LOG, "Error: " + e.getMessage() ); - Toast.makeText( this, "Unable to execute as root", Toast.LENGTH_LONG ).show(); + // we don't really care if this fails, they should have gotten a shell + // error from the sendkeys } return true; diff --git a/src/net/hoopajoo/android/SoftKeys/SendInput.java b/src/net/hoopajoo/android/SoftKeys/SendInput.java new file mode 100644 index 0000000..9037e82 --- /dev/null +++ b/src/net/hoopajoo/android/SoftKeys/SendInput.java @@ -0,0 +1,109 @@ +/* + * + * Copyright (c) 2010 Steve Slaven + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * +*/ +package net.hoopajoo.android.SoftKeys; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import android.app.Activity; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; +import android.view.View.OnTouchListener; +import android.widget.ImageButton; +import android.widget.RemoteViews; +import android.widget.Toast; + +// this is just a stub to handle intent calls +public class SendInput extends Activity { + public static String ACTION_CODE = "net.hoopajoo.android.SoftKeys.KEY_CODE"; + + private static final Map mKeymap; + static { + Map t = new HashMap(); + t.put( "back", 4 ); + t.put( "menu", 82 ); + t.put( "search", 84 ); + mKeymap = Collections.unmodifiableMap( t ); + } + + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + onNewIntent( getIntent() ); + } + + @Override + public void onNewIntent( Intent i ) { + Globals app = (Globals)getApplication(); + + String action = i.getAction(); + if( action.equals( ACTION_CODE ) ) { + // by key name? + int keyid = 0; + Bundle e = i.getExtras(); + boolean longClick = e.getBoolean( "longclick", false ); + if( e.getString( "keyname" ) != null ) { + String key = e.getString( "keyname" ); + if( key.equals( "home" ) ) { + ((Globals)getApplication()).doHomeAction( longClick ); + return; + }else{ + // run through resolver + if( mKeymap.containsKey( key ) ) { + keyid = mKeymap.get( key ); + } + } + } + + ((Globals)getApplication()).sendKeys( new int[] { keyid } ); + // todo: make me a broadcast receiver + this.finish(); + } + } +} diff --git a/src/net/hoopajoo/android/SoftKeys/SoftKeysService.java b/src/net/hoopajoo/android/SoftKeys/SoftKeysService.java index 1118406..bdfe63e 100644 --- a/src/net/hoopajoo/android/SoftKeys/SoftKeysService.java +++ b/src/net/hoopajoo/android/SoftKeys/SoftKeysService.java @@ -71,43 +71,14 @@ public class SoftKeysService extends Service { OnClickListener c = new OnClickListener() { @Override public void onClick( View v ) { - // send an intent to the main window - Intent i = null; - boolean hide = auto_hide; - switch( v.getId() ) { - case R.id.home: - i = new Intent( Keys.ACTION_HOME ); - break; - - case R.id.back: - i = new Intent( Keys.ACTION_BACK ); - if( hide ) { - hide = auto_hide_after_back; - } - break; - - case R.id.menu: - i = new Intent( Keys.ACTION_MENU ); - break; - - case R.id.search: - i = new Intent( Keys.ACTION_SEARCH ); - break; - - case R.id.exit: - hide = true; - break; - } - - if( i != null ) { - i.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK ); - i.setClass( v.getContext(), Keys.class ); - v.getContext().startActivity( i ); - } - - if( hide ) { - toggle_bar(); - } + genericClick( v, false ); + } + }; + + OnLongClickListener lc = new OnLongClickListener() { + @Override + public boolean onLongClick( View v ) { + return genericClick( v, true ); } }; @@ -416,6 +387,54 @@ public class SoftKeysService extends Service { } + private boolean genericClick( View v, boolean longclick) { + // send an intent to the main window + Intent i = null; + boolean hide = auto_hide; + switch( v.getId() ) { + case R.id.home: + i = new Intent( SendInput.ACTION_CODE ); + i.putExtra( "keyname", "home" ); + break; + + case R.id.back: + i = new Intent( SendInput.ACTION_CODE ); + i.putExtra( "keyname", "back" ); + if( hide ) { + hide = auto_hide_after_back; + } + break; + + case R.id.menu: + i = new Intent( SendInput.ACTION_CODE ); + i.putExtra( "keyname", "menu" ); + break; + + case R.id.search: + i = new Intent( SendInput.ACTION_CODE ); + i.putExtra( "keyname", "search" ); + break; + + case R.id.exit: + hide = true; + break; + } + + if( i != null ) { + i.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK ); + i.putExtra( "long", longclick ); + + //i.setClass( v.getContext(), Keys.class ); + v.getContext().startActivity( i ); + } + + if( hide ) { + toggle_bar(); + } + + return true; + } + @Override public void onDestroy() { super.onDestroy(); -- cgit v0.10.2