// // anyRemote android client // a bluetooth/wi-fi remote control for Linux. // // Copyright (C) 2011-2016 Mikhail Fedotov <anyremote@mail.ru> // // 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, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // package anyremote.client.android; import java.io.File; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Enumeration; import java.util.TreeMap; import java.util.Vector; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.app.Activity; import android.app.ProgressDialog; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.Window; import android.widget.Toast; import anyremote.client.android.util.Address; import anyremote.client.android.util.ProtocolMessage; import anyremote.client.android.R; public class anyRemote extends Activity implements Handler.Callback { public static final int DISCONNECTED = 0; public static final int CONNECTING = 1; public static final int CONNECTED = 2; public static final int LOSTFOCUS = 4; public static final int COMMAND = 5; public static final int DO_EXIT = 6; public static final int DO_CONNECT = 7; public static final int DO_DISCONNECT = 8; //public static final int SHOW_LOG = 8; public static final int SWIPE_MIN_DISTANCE = 120; public static final int SWIPE_THRESHOLD_VELOCITY = 200; static final int NO_FORM = 0; static final int SEARCH_FORM = 1; static final int CONTROL_FORM = 2; static final int FMGR_FORM = 3; static final int TEXT_FORM = 4; static final int LIST_FORM = 5; static final int EDIT_FORM = 6; static final int WMAN_FORM = 7; static final int LOG_FORM = 8; static final int MOUSE_FORM = 9; static final int KEYBOARD_FORM = 10; static final int WEB_FORM = 11; static final int DUMMY_FORM = 12; static final int LOG_CAPACITY = 16384; static final String CONN_ADDR = "ADR"; static final String CONN_NAME = "CNM"; static final String CONN_PASS = "CNP"; static final String ACTION = "ACT"; static final String SWITCHTO = "SWT"; int prevForm = NO_FORM; private static int currForm = NO_FORM; static int status; static Dispatcher protocol; public static boolean finishFlag = false; public static boolean firstConnect = true; static TreeMap<String,Bitmap> iconMap = new TreeMap<String,Bitmap>(); static TreeMap<String,Bitmap> coverMap = new TreeMap<String,Bitmap>(); private static Handler globalHandler = null; private static DateFormat now_format = new SimpleDateFormat("HH:mm:ss"); private static Date teraz = new Date(); // Logging stuff public static StringBuilder logData; // Wait indicator stuff private static ProgressDialog waiting = null; private static int numeratorVar = 0; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); logData = new StringBuilder(LOG_CAPACITY); _log("onCreate "+android.os.Build.MODEL+ " " +android.os.Build.VERSION.CODENAME+" "+android.os.Build.VERSION.RELEASE); protocol = new Dispatcher(this); //requestWindowFeature(Window.FEATURE_NO_TITLE); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.main); currForm = DUMMY_FORM; status = DISCONNECTED; finishFlag = false; if (globalHandler == null) { globalHandler = new Handler(this); } MainLoop.enable(); } @Override protected void onStart() { _log("onStart "+currForm); super.onStart(); } @Override protected void onPause() { _log("onPause "+currForm); super.onPause(); } @Override protected void onResume() { //logData = ""; // remove old log _log("onResume "+currForm); super.onResume(); if (finishFlag) { doExit(); return; } if (currForm != LOG_FORM && status == DISCONNECTED) { currForm = NO_FORM; setCurrentView(SEARCH_FORM,""); } } @Override protected void onStop() { _log("onStop"); super.onStop(); } @Override protected void onDestroy() { _log("onDestroy"); super.onDestroy(); finishFlag = true; currForm = NO_FORM; status = DISCONNECTED; protocol.disconnect(true); MainLoop.disable(); } public void setPrevView(int which) { _log("setPrevView " + which); prevForm = which; } public void setCurrentView(int which, String subCommand) { _log("setCurrentView " + getScreenStr(which) + " (was " + getScreenStr(currForm) + ") finish="+finishFlag); if (which == LOG_FORM || which == MOUSE_FORM || which == KEYBOARD_FORM || which == WEB_FORM) { _log("setCurrentView wrong switch option. Skip it."); return; } if (finishFlag == true) { return; // on destroy } if (currForm == which) { _log("setCurrentView TRY TO SWITCH TO THE SAME FORM ???"); //if (currForm != SEARCH_FORM) { _log("setCurrentView SKIP SWITCH TO THE SAME FORM ???"); return; //} } prevForm = currForm; currForm = which; if (currForm != prevForm) { // finish current form switch (prevForm) { case SEARCH_FORM: _log("[AR] setCurrentView mess SEARCH_FORM with some other"); break; case CONTROL_FORM: case LIST_FORM: case TEXT_FORM: case WMAN_FORM: _log("setCurrentView stop "+prevForm); protocol.sendToActivity(prevForm, Dispatcher.CMD_CLOSE,ProtocolMessage.FULL); break; //case LOG_FORM: //case MOUSE_FORM: //case KEYBOARD_FORM: //case WEB_FORM: case DUMMY_FORM: break; } } if (prevForm != LOG_FORM && prevForm != MOUSE_FORM && prevForm != KEYBOARD_FORM && prevForm != WEB_FORM) { protocol.menuReplaceDefault(currForm); } switch (currForm) { case SEARCH_FORM: final Intent doSearch = new Intent(getBaseContext(), SearchForm.class); String id = String.format("%d",numerator()); doSearch.putExtra("SUBID", id); _log("setCurrentView start SearchForm "+id); startActivity(doSearch); break; case CONTROL_FORM: _log("setCurrentView start ControlScreen"); final Intent control = new Intent(getBaseContext(), ControlScreen.class); startActivity(control); break; case LIST_FORM: _log("setCurrentView start ListScreen"); final Intent showList = new Intent(getBaseContext(), ListScreen.class); startActivity(showList); break; case TEXT_FORM: _log("setCurrentView start TextScreen"); final Intent showText = new Intent(getBaseContext(), TextScreen.class); showText.putExtra("SUBID", subCommand); startActivity(showText); break; case WMAN_FORM: _log("setCurrentView start WinManager"); final Intent showWman = new Intent(getBaseContext(), WinManager.class); startActivity(showWman); break; /* ??? case MOUSE_FORM: _log("setCurrentView start MouseWin"); final Intent showMou = new Intent(getBaseContext(), MouseScreen.class); startActivity(showMou); break; case KEYBOARD_FORM: _log("setCurrentView start KeyboardWin"); final Intent showKbd = new Intent(getBaseContext(), KeyboardScreen.class); startActivity(showKbd); break; case LOG_FORM: _log("setCurrentView start TextScreen (LOG)"); final Intent showLog = new Intent(getBaseContext(), TextScreen.class); showLog.putExtra("SUBID", "__LOG__"); startActivity(showLog); break; */ } } @Override public boolean onCreateOptionsMenu(Menu menu) { menu.clear(); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { menu.clear(); MenuInflater mi = getMenuInflater(); if (status == DISCONNECTED) { mi.inflate(R.menu.menu, menu); } else { mi.inflate(R.menu.menu2, menu); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case R.id.connect_main: _log("onOptionsItemSelected connect_main"); setCurrentView(SEARCH_FORM,""); return true; case R.id.disconnect_main: _log("onOptionsItemSelected disconnect_main"); protocol.disconnect(true); return true; case R.id.exit_main: _log("onOptionsItemSelected exit_main"); doExit(); return true; case R.id.log_main: _log("onOptionsItemSelected log_main"); setCurrentView(LOG_FORM,""); return true; } // else - user defined items //cScreen.commandAction(item.getTitle().toString()); return true; } public static void sendGlobal(int id, Object obj) { anyRemote._log("sendGlobal: "+id); if (globalHandler != null) { Message msg = globalHandler.obtainMessage(id, obj); msg.sendToTarget(); } } // get messages sent from sendGlobal() public boolean handleMessage(Message msg) { switch(msg.what){ case CONNECTED: anyRemote._log("handleMessage: CONNECTED"); //Toast.makeText(client, R.string.connection_successful, Toast.LENGTH_SHORT).show(); try { protocol.connected((Connection) msg.obj); } catch (Exception e) { // once got ClassCastException here anyRemote._log("handleMessage: CONNECTED got exception "); protocol.disconnect(true); return true; } handleEvent(CONNECTED); break; case CONNECTING: anyRemote._log("handleMessage: CONNECTING"); handleEvent(CONNECTING); break; case DISCONNECTED: anyRemote._log("handleMessage: DISCONNECTED"); if (msg.obj != null ) { Resources res = getResources(); String alert = res.getString(R.string.connection_failed); if (((String) msg.obj).length() > 0) { alert += "\n"+(String) msg.obj; } Toast.makeText(this, alert, Toast.LENGTH_LONG).show(); } protocol.disconnected(true); handleEvent(DISCONNECTED); break; case LOSTFOCUS: anyRemote._log("handleMessage: LOST FOCUS"); protocol.disconnected(false); handleEvent(LOSTFOCUS); break; case anyRemote.COMMAND: anyRemote._log("handleMessage: COMMAND"); protocol.handleCommand((ProtocolMessage) msg.obj); break; case anyRemote.DO_CONNECT: anyRemote._log("handleMessage: DO_CONNECT"); if (msg.obj != null ) { Address conn = (Address) msg.obj; setProgressBarIndeterminateVisibility(true); status = CONNECTING; protocol.doConnect(conn.name, conn.URL, conn.pass); } else { setCurrentView(DUMMY_FORM, ""); } break; case anyRemote.DO_EXIT: anyRemote._log("handleMessage: DO_EXIT"); //doExit(); -- do exit from onResume() currForm = NO_FORM; finishFlag = true; break; case anyRemote.DO_DISCONNECT: anyRemote._log("handleMessage: DO_DISCONNECT"); protocol.disconnect(true); break; /*case anyRemote.SHOW_LOG: anyRemote._log("handleMessage: SHOW_LOG"); setCurrentView(LOG_FORM, ""); break;*/ } return true; } private void handleEvent(int what) { _log("handleEvent"); switch (what) { case CONNECTED: _log("handleEvent: Connection established"); status = CONNECTED; setProgressBarIndeterminateVisibility(false); if (currForm != LOG_FORM) { _log("handleEvent: switch to CONTROL_FORM"); setCurrentView(CONTROL_FORM,""); } break; case CONNECTING: // should not happens (did not send such messages) status = CONNECTING; case DISCONNECTED: case LOSTFOCUS: _log("handleEvent: Connection or focus lost"); status = DISCONNECTED; //protocol.closeCurrentScreen(currForm); if (!finishFlag) { // this happens on exit // send quit to all registered activity protocol.sendToActivity(-1, Dispatcher.CMD_CLOSE,ProtocolMessage.FULL); if (currForm != LOG_FORM) { currForm = DUMMY_FORM; // trick } if (currForm != LOG_FORM) { _log("handleEvent: switch to SEARCH_FORM"); setCurrentView(SEARCH_FORM,""); } } break; default: _log("handleEvent: unknown event"); } } public static int icon2int(String btn) { if (btn == null) return R.drawable.icon; if (btn.equals("default")) return R.drawable.def; if (btn.equals("down")) return R.drawable.down; if (btn.equals("file")) return R.drawable.file; if (btn.equals("fit")) return R.drawable.fit; if (btn.equals("folder")) return R.drawable.folder; if (btn.equals("forward")) return R.drawable.forward; if (btn.equals("fullscreen")) return R.drawable.fullscreen; if (btn.equals("info")) return R.drawable.info; if (btn.equals("left")) return R.drawable.left; if (btn.equals("minus")) return R.drawable.minus; if (btn.equals("mute")) return R.drawable.mute; if (btn.equals("next")) return R.drawable.next; if (btn.equals("no")) return R.drawable.no; if (btn.equals("pause")) return R.drawable.pause; if (btn.equals("play")) return R.drawable.play; if (btn.equals("plus")) return R.drawable.plus; if (btn.equals("prev")) return R.drawable.prev; if (btn.equals("question")) return R.drawable.question; if (btn.equals("refresh")) return R.drawable.refresh; if (btn.equals("rewind")) return R.drawable.rewind; if (btn.equals("right")) return R.drawable.right; if (btn.equals("stop")) return R.drawable.stop; if (btn.equals("up")) return R.drawable.up; if (btn.equals("vol_down")) return R.drawable.vol_down; if (btn.equals("vol_up")) return R.drawable.vol_up; if (btn.equals("click_icon")) return R.drawable.click_icon; if (btn.equals("transparent")) return R.drawable.transparent; return R.drawable.icon; } public static Bitmap getIconBitmap(Resources resources, String icon) { if (icon.equals("none")) { return null; } synchronized (iconMap) { if (iconMap.containsKey(icon)) { return (Bitmap) iconMap.get(icon); } int iconId = icon2int(icon); //_log("getIconBitmap "+icon+" "+iconId); if (iconId == R.drawable.icon) { File dir = Environment.getExternalStorageDirectory(); File iFile = new File(dir, "Android/data/anyremote.client.android/files/icons/"+icon+".png"); if(iFile.canRead()) { _log("getIconBitmap", icon+" found on SDCard"); Bitmap ic = BitmapFactory.decodeFile(iFile.getAbsolutePath()); if (ic == null) { _log("getIconBitmap", "seems image "+icon+" is broken"); iFile.delete(); } else { iconMap.put(icon,ic); } return ic; } else { _log("getIconBitmap", iFile.getAbsolutePath()+" absent on SDCard"); // try to auto upload it protocol.autoUploadIcon(icon); return null; } } Bitmap ic = BitmapFactory.decodeResource(resources, icon2int(icon)); iconMap.put(icon,ic); return ic; } } public static Bitmap getCoverBitmap(Resources resources, String name) { if (name.equals("none")) { return null; } synchronized (coverMap) { if (coverMap.containsKey(name)) { return (Bitmap) coverMap.get(name); } File dir = Environment.getExternalStorageDirectory(); File iFile = new File(dir, "Android/data/anyremote.client.android/files/covers/"+name+".png"); if(iFile.canRead()) { _log("getCoverBitmap", name+" found on SDCard"); Bitmap ic = BitmapFactory.decodeFile(iFile.getAbsolutePath()); if (ic == null) { _log("getCoverBitmap", "seems image "+name+" is broken"); iFile.delete(); } else { coverMap.put(name,ic); } return ic; } _log("getCoverBitmap", iFile.getAbsolutePath()+" absent on SDCard"); } // try to auto upload it protocol.autoUploadCover(name); return null; } public static void clearCache() { synchronized (iconMap) { iconMap.clear(); } synchronized (coverMap) { coverMap.clear(); } } public static int parseColor(Vector vR, int start) { if (vR.size() < start + 3) { // what about "yellow" ? //if (!c.startsWith("#")) { // c = "#" + c; //} try { return Color.parseColor((String) vR.elementAt(start)); } catch (Exception e) { return Color.parseColor("#000000"); } } return parseColor((String) vR.elementAt(start), (String) vR.elementAt(start+1), (String) vR.elementAt(start+2)); } private static int parseColor(String r, String g, String b) { int[] RGB = new int[3]; try { RGB[0] = Integer.parseInt(r); RGB[1] = Integer.parseInt(g); RGB[2] = Integer.parseInt(b); int i; for (i=0;i<2;i++) { if (RGB[i]<0 ) RGB[i] = 0; if (RGB[i]>255) RGB[i] = 255; } } catch (Exception e) { RGB[0] = 0; RGB[1] = 0; RGB[2] = 0; } return Color.rgb(RGB[0], RGB[1], RGB[2]); } void doExit() { // how to do exit ? _log("doExit"); finishFlag = true; currForm = NO_FORM; protocol.disconnect(true); //super.onBackPressed(); finish(); } public static int getCurScreen() { return currForm; } public static String getScreenStr(int form) { switch (form) { case NO_FORM: return "NO"; case SEARCH_FORM: return "SEARCH"; case CONTROL_FORM: return "CONTROL"; case FMGR_FORM: return "FMGR"; case TEXT_FORM: return "TEXT"; case LIST_FORM: return "LIST"; case EDIT_FORM: return "EDIT"; case WMAN_FORM: return "WMAN"; case LOG_FORM: return "LOG"; case WEB_FORM: return "WEB"; case DUMMY_FORM: return "DUMMY"; } return "UNKNOWN"; } public static void popup(Activity cxt, boolean show, boolean update, String msg) { _log("popup " + show + " " +msg); //cxt.setProgressBarIndeterminateVisibility(show); if (show && !update && waiting != null) { // do not recreate return; } if (waiting != null) { waiting.dismiss(); waiting = null; } if (show) { waiting = new ProgressDialog(cxt, ProgressDialog.STYLE_HORIZONTAL); waiting.setMessage(msg); waiting.show(); } } public static String getLocalIpAddress() { try { for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) { NetworkInterface intf = en.nextElement(); for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) { InetAddress inetAddress = enumIpAddr.nextElement(); if (!(inetAddress.isLoopbackAddress() || inetAddress.getHostAddress().contains(":"))) { // avoid IPv6 addresses return inetAddress.getHostAddress(); } } } } catch (SocketException ex) { return null; } return null; } public static boolean logVisible() { return (currForm == LOG_FORM); } private static void _log(String log) { _log("anyRemote", log); } public static void _log(String prefix, String msg) { //synchronized (logData) { if (logData.length() > LOG_CAPACITY) { logData.delete(0,LOG_CAPACITY); } teraz.setTime(java.lang.System.currentTimeMillis()); logData.append("\n" + "[" + now_format.format(teraz) + "] ["+prefix+"] "+msg); Log.i(prefix,msg); //} } public static int numerator() { numeratorVar++; return numeratorVar; } }