diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 3aa0861..833d3b8 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -13,6 +13,7 @@ + @@ -35,6 +36,12 @@ + + + + - + + + + + + + - - - - - You need to add mapsApiKey=... to local.properties - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - - - You have to place scala-compiler.jar and scala-library.jar to tools/ - - - - You have to place scala-compiler.jar and scala-library.jar to tools/ - - - - - - - - - - - - - - - - ProGuard is required to build! Copy proguard.jar to tools/ - - - - - - - - -injars ${out.classes.absolute.dir}:${jar.libs.dir}:tools/scala-library.jar(!META-INF/MANIFEST.MF,!library.properties) - -outjars ${out.absolute.dir}/classes.min.jar - -libraryjars ${toString:project.target.class.path} - -printusage ${optimized.dir}/proguard.usage - - - - - Converting compiled files and external libraries into ${intermediate.dex.file}... - - - - - - diff --git a/custom_rules.xml b/custom_rules.xml new file mode 100644 index 0000000..a2c7eda --- /dev/null +++ b/custom_rules.xml @@ -0,0 +1,80 @@ + + + + + + + You need to add mapsApiKey=... to local.properties + + + + + + + + + + + + + + + + + + + + + + + + + + + You have to place scala-compiler.jar and scala-library.jar to tools/ + + + + You have to place scala-compiler.jar and scala-library.jar to tools/ + + + + + + + + + + + + + + + + + + + + + + + + + + -injars ${out.classes.absolute.dir}:${jar.libs.dir}:tools/scala-library.jar(!META-INF/MANIFEST.MF,!library.properties) + -outjars ${obfuscated.jar.file} + -libraryjars ${toString:project.target.class.path} + -dump "${obfuscate.absolute.dir}/dump.txt" + -printseeds "${obfuscate.absolute.dir}/seeds.txt" + -printusage "${obfuscate.absolute.dir}/usage.txt" + -printmapping "${obfuscate.absolute.dir}/mapping.txt" + + + + + + diff --git a/doc/API.mdwn b/doc/API.mdwn index 5ea3cc8..055b758 100644 --- a/doc/API.mdwn +++ b/doc/API.mdwn @@ -167,7 +167,7 @@ Start the APRSdroid tracking service for an indeterminate runtime (`SERVICE`) or for one position transmission (`ONCE`). // launch APRSdroid tracker - Intent i = new Intent("org.aprsdroid.app.SERVICE"); + Intent i = new Intent("org.aprsdroid.app.SERVICE").setPackage("org.aprsdroid.app"); startService(i); ### SERVICE_STOP @@ -175,7 +175,7 @@ Start the APRSdroid tracking service for an indeterminate runtime Stop the tracking service. // stop APRSdroid tracker - Intent i = new Intent("org.aprsdroid.app.SERVICE_STOP"); + Intent i = new Intent("org.aprsdroid.app.SERVICE_STOP").setPackage("org.aprsdroid.app"); startService(i); ### SEND_PACKET @@ -192,7 +192,7 @@ Intent extras: Example for sending a raw status packet: // send raw status packet - Intent i = new Intent("org.aprsdroid.app.SEND_PACKET"); + Intent i = new Intent("org.aprsdroid.app.SEND_PACKET").setPackage("org.aprsdroid.app"); i.putExtra("data", ">third-party APRS status app"); startService(i); diff --git a/libs/usbserial-4.0_580c736.jar b/libs/usbserial-4.0_580c736.jar new file mode 100644 index 0000000..6d8fd37 Binary files /dev/null and b/libs/usbserial-4.0_580c736.jar differ diff --git a/res/drawable-hdpi/allicons.png b/res/drawable-hdpi/allicons.png new file mode 100644 index 0000000..1e61464 Binary files /dev/null and b/res/drawable-hdpi/allicons.png differ diff --git a/res/drawable-xhdpi/allicons.png b/res/drawable-xhdpi/allicons.png new file mode 100644 index 0000000..ffc90aa Binary files /dev/null and b/res/drawable-xhdpi/allicons.png differ diff --git a/res/drawable-xxhdpi-v11/ic_status.png b/res/drawable-xxhdpi-v11/ic_status.png new file mode 100644 index 0000000..41fae1d Binary files /dev/null and b/res/drawable-xxhdpi-v11/ic_status.png differ diff --git a/res/drawable-xxhdpi/allicons.png b/res/drawable-xxhdpi/allicons.png new file mode 100644 index 0000000..e2eff10 Binary files /dev/null and b/res/drawable-xxhdpi/allicons.png differ diff --git a/res/drawable-xxhdpi/icon.png b/res/drawable-xxhdpi/icon.png new file mode 100644 index 0000000..50ec7c6 Binary files /dev/null and b/res/drawable-xxhdpi/icon.png differ diff --git a/res/drawable-xxxhdpi-v11/ic_status.png b/res/drawable-xxxhdpi-v11/ic_status.png new file mode 100644 index 0000000..d2d8676 Binary files /dev/null and b/res/drawable-xxxhdpi-v11/ic_status.png differ diff --git a/res/drawable-xxxhdpi/allicons.png b/res/drawable-xxxhdpi/allicons.png new file mode 100644 index 0000000..cd90368 Binary files /dev/null and b/res/drawable-xxxhdpi/allicons.png differ diff --git a/res/drawable-xxxhdpi/icon.png b/res/drawable-xxxhdpi/icon.png new file mode 100644 index 0000000..e6fe845 Binary files /dev/null and b/res/drawable-xxxhdpi/icon.png differ diff --git a/res/drawable/allicons.png b/res/drawable/allicons.png index a332bfa..433d58e 100644 Binary files a/res/drawable/allicons.png and b/res/drawable/allicons.png differ diff --git a/res/values/arrays.xml b/res/values/arrays.xml index f68d69b..0a18f88 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -8,6 +8,7 @@ @string/p_conn_bt @string/p_conn_kwd @string/p_conn_tcptnc + @string/p_conn_usb tcp @@ -17,6 +18,7 @@ bluetooth kenwood tcptnc + usb 0 @@ -83,4 +85,12 @@ 1440 2880 + + 4800 + 9600 + 19200 + 38400 + 57600 + 115200 + diff --git a/res/values/strings.xml b/res/values/strings.xml index 4a7e933..35f4942 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -181,6 +181,7 @@ Bluetooth TNC Kenwood GPS Port TCP/IP TNC +USB Serial Manual Position Periodic GPS/Network Position @@ -369,4 +370,8 @@ Error importing certificate: %s! Your certificate has expired! Your certificate will expire in %d days! + + +Baud Rate +Data rate of the serial port diff --git a/res/xml/backend_afsk.xml b/res/xml/backend_afsk.xml index 17b332a..8f1c56a 100644 --- a/res/xml/backend_afsk.xml +++ b/res/xml/backend_afsk.xml @@ -32,8 +32,8 @@ diff --git a/src/APRSdroid.scala b/src/APRSdroid.scala index 9851997..12e1774 100644 --- a/src/APRSdroid.scala +++ b/src/APRSdroid.scala @@ -16,6 +16,13 @@ class APRSdroid extends Activity { override def onCreate(savedInstanceState : Bundle) { super.onCreate(savedInstanceState) val prefs = PreferenceManager.getDefaultSharedPreferences(this) + + // if this is a USB device, auto-launch the service + if (getIntent.getParcelableExtra("device") != null) { + prefs.edit().putString("backend", "usb").commit(); + startService(AprsService.intent(this, AprsService.SERVICE)) + } + prefs.getString("activity", "log") match { case "hub" => replaceAct(classOf[HubActivity]) case "map" => replaceAct(classOf[MapAct]) diff --git a/src/MapAct.scala b/src/MapAct.scala index a7328a0..04e16e9 100644 --- a/src/MapAct.scala +++ b/src/MapAct.scala @@ -202,12 +202,12 @@ class StationOverlay(icons : Drawable, context : MapAct, db : StorageDatabase) e def symbol2rect(symbol : String) : Rect = { val alt_offset = if (symbol(0) == '/') 0 else symbolSize*6 - val index = symbol(1) - 32 + val index = symbol(1) - 33 // check for overflow if (index < 0 || index >= 6*16) return new Rect(0, 0, symbolSize, symbolSize) - val x = (index / 16) * symbolSize + alt_offset - val y = (index % 16) * symbolSize + val y = (index / 16) * symbolSize + alt_offset + val x = (index % 16) * symbolSize new Rect(x, y, x+symbolSize, y+symbolSize) } @@ -291,8 +291,8 @@ class StationOverlay(icons : Drawable, context : MapAct, db : StorageDatabase) e c.drawBitmap(iconbitmap, srcRect, destRect, null) // and finally the bitmap overlay, if any if (zoom >= 6 && symbolIsOverlayed(s.symbol)) { - c.drawText(s.symbol(0).toString(), p.x, p.y+ss/2, symbStrPaint) - c.drawText(s.symbol(0).toString(), p.x, p.y+ss/2, symbPaint) + c.drawText(s.symbol(0).toString(), p.x+1, p.y+ss/2+1, symbStrPaint) + c.drawText(s.symbol(0).toString(), p.x+1, p.y+ss/2+1, symbPaint) } } } diff --git a/src/SymbolView.scala b/src/SymbolView.scala index a9ab83b..5aaee32 100644 --- a/src/SymbolView.scala +++ b/src/SymbolView.scala @@ -1,17 +1,17 @@ package org.aprsdroid.app import _root_.android.content.{BroadcastReceiver, Context, Intent, IntentFilter} -import _root_.android.graphics.drawable.{Drawable, BitmapDrawable} -import _root_.android.graphics.{Canvas, Paint, Path, Point, Rect, Typeface} +import _root_.android.graphics.drawable.Drawable +import _root_.android.graphics.{Bitmap, BitmapFactory, Canvas, Matrix, Paint, Path, Point, Rect, Typeface} import _root_.android.util.AttributeSet import _root_.android.widget.ImageView class SymbolView(context : Context, attrs : AttributeSet) extends ImageView(context, attrs) { var symbol : String = "/$" - val iconbitmap = UnscaledBitmapLoader.loadFromResource(context.getResources(), - R.drawable.allicons, null) - val symbolSize = 16 + lazy val iconbitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.allicons) + lazy val symbolSize = (context.getResources().getDisplayMetrics().density * 16).toInt + def setSymbol(new_sym : String) { symbol = new_sym @@ -20,9 +20,9 @@ class SymbolView(context : Context, attrs : AttributeSet) extends ImageView(cont def symbol2rect(symbol : String) : Rect = { val alt_offset = if (symbol(0) == '/') 0 else symbolSize*6 - val index = symbol(1) - 32 - val x = (index / 16) * symbolSize + alt_offset - val y = (index % 16) * symbolSize + val index = symbol(1) - 33 + val y = (index / 16) * symbolSize + alt_offset + val x = (index % 16) * symbolSize new Rect(x, y, x+symbolSize, y+symbolSize) } @@ -57,8 +57,8 @@ class SymbolView(context : Context, attrs : AttributeSet) extends ImageView(cont if (symbolIsOverlayed(symbol)) { val x = getWidth()/2 val y = getHeight()*3/4 - canvas.drawText(symbol(0).toString(), x, y, strokePaint) - canvas.drawText(symbol(0).toString(), x, y, symbPaint) + canvas.drawText(symbol(0).toString(), x+1, y+1, strokePaint) + canvas.drawText(symbol(0).toString(), x+1, y+1, symbPaint) } } } diff --git a/src/backend/AfskUploader.scala b/src/backend/AfskUploader.scala index cda3eb7..b8c0a8d 100644 --- a/src/backend/AfskUploader.scala +++ b/src/backend/AfskUploader.scala @@ -14,7 +14,7 @@ class AfskUploader(service : AprsService, prefs : PrefsWrapper) extends AprsBack with PacketHandler with PacketCallback { val TAG = "APRSdroid.Afsk" // frame prefix: bytes = milliseconds * baudrate / 8 / 1000 - var FrameLength = prefs.getStringInt("afsk.prefix", 1000)*1200/8/1000 + var FrameLength = prefs.getStringInt("afsk.prefix", 200)*1200/8/1000 var Digis = prefs.getString("digi_path", "WIDE1-1") val use_hq = prefs.getAfskHQ() val use_bt = prefs.getAfskBluetooth() diff --git a/src/backend/AprsBackend.scala b/src/backend/AprsBackend.scala index a007fc1..d545024 100644 --- a/src/backend/AprsBackend.scala +++ b/src/backend/AprsBackend.scala @@ -57,6 +57,11 @@ object AprsBackend { (s, p) => new TcpTnc(s, p), R.xml.backend_tcptnc, CAN_DUPLEX, + PASSCODE_NONE), + "usb" -> new BackendInfo( + (s, p) => new UsbTnc(s, p), + R.xml.backend_usb, + CAN_DUPLEX, PASSCODE_NONE) ) diff --git a/src/backend/TcpUploader.scala b/src/backend/TcpUploader.scala index 5a8466a..3adbfaa 100644 --- a/src/backend/TcpUploader.scala +++ b/src/backend/TcpUploader.scala @@ -106,6 +106,8 @@ class TcpUploader(service : AprsService, prefs : PrefsWrapper) extends AprsBacke service.getString(R.string.post_connecting, host, port.asInstanceOf[AnyRef])) val socket = sc.getSocketFactory().createSocket(host, port).asInstanceOf[SSLSocket] + // enable all available cipher suites, including NULL; fixes #71 + socket.setEnabledCipherSuites(sc.getSocketFactory().getDefaultCipherSuites()) socket } catch { case e : java.io.FileNotFoundException => diff --git a/src/backend/UsbTnc.scala b/src/backend/UsbTnc.scala new file mode 100644 index 0000000..63e3d16 --- /dev/null +++ b/src/backend/UsbTnc.scala @@ -0,0 +1,171 @@ +package org.aprsdroid.app + +import android.app.PendingIntent +import android.app.Service +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.hardware.usb.UsbManager +import android.hardware.usb.UsbDevice +import android.hardware.usb.UsbDeviceConnection +import android.util.Log +import java.io.{InputStream, OutputStream} + +import net.ab0oo.aprs.parser._ + +import com.felhr.usbserial._ + +class UsbTnc(service : AprsService, prefs : PrefsWrapper) extends AprsBackend(prefs) { + val TAG = "APRSdroid.Usb" + + val USB_PERM_ACTION = "org.aprsdroid.app.UsbTnc.PERM" + val ACTION_USB_ATTACHED = "android.hardware.usb.action.USB_DEVICE_ATTACHED" + val ACTION_USB_DETACHED = "android.hardware.usb.action.USB_DEVICE_DETACHED" + + var digipath = prefs.getString("digi_path", "WIDE1-1") + + val usbManager = service.getSystemService(Context.USB_SERVICE).asInstanceOf[UsbManager]; + var thread : UsbThread = null + var dev : UsbDevice = null + var con : UsbDeviceConnection = null + var ser : UsbSerialInterface = null + var alreadyRunning = false + + val intent = new Intent(USB_PERM_ACTION) + val pendingIntent = PendingIntent.getBroadcast(service, 0, intent, 0) + + val receiver = new BroadcastReceiver() { + override def onReceive(ctx : Context, i : Intent) { + Log.d(TAG, "onReceive: " + i) + if (i.getAction() == ACTION_USB_DETACHED) { + log("USB device detached.") + ctx.stopService(AprsService.intent(ctx, AprsService.SERVICE)) + return + } + val granted = i.getExtras().getBoolean(UsbManager.EXTRA_PERMISSION_GRANTED) + if (!granted) { + service.postAbort("No permission for USB device!") + return + } + log("Obtained USB permissions.") + thread = new UsbThread() + thread.start() + } + } + + var proto : TncProto = null + + def start() = { + val filter = new IntentFilter(USB_PERM_ACTION) + filter.addAction(ACTION_USB_DETACHED) + service.registerReceiver(receiver, filter) + alreadyRunning = true + if (ser == null) + requestPermissions() + false + } + + def log(s : String) { + Log.i(TAG, s) + service.postAddPost(StorageDatabase.Post.TYPE_INFO, R.string.post_info, s) + } + + def requestPermissions() { + Log.d(TAG, "UsbTnc.requestPermissions"); + val dl = usbManager.getDeviceList(); + var requested = false + import scala.collection.JavaConversions._ + for ((name, dev) <- dl) { + val deviceVID = dev.getVendorId() + val devicePID = dev.getProductId() + if (deviceVID != 0x1d6b || (devicePID != 0x0001 || devicePID != 0x0002 || devicePID != 0x0003)) { + // this is not a USB Hub + log("Found USB device %04x:%04x, requesting permissions.".format(deviceVID, devicePID)) + this.dev = dev + usbManager.requestPermission(dev, pendingIntent) + return + } + } + service.postAbort("No USB device found!") + } + + def update(packet : APRSPacket) : String = { + // the digipeater setting here is a duplicate just for log purpose + packet.setDigipeaters(Digipeater.parseList(digipath, true)) + Log.d(TAG, "UsbTnc.update: " + packet) + //TODO + proto.writePacket(packet) + "USB OK" + } + + def stop() { + if (alreadyRunning) + service.unregisterReceiver(receiver) + alreadyRunning = false + if (ser != null) + ser.close() + if (con != null) + con.close() + if (thread == null) + return + thread.synchronized { + thread.running = false + } + //thread.shutdown() + thread.interrupt() + thread.join(50) + } + + class UsbThread() + extends Thread("APRSdroid USB connection") { + val TAG = "UsbThread" + var running = true + + def log(s : String) { + Log.i(TAG, s) + service.postAddPost(StorageDatabase.Post.TYPE_INFO, R.string.post_info, s) + } + + override def run() { + val con = usbManager.openDevice(dev) + val ser = UsbSerialDevice.createUsbSerialDevice(dev, con) + if (ser == null || !ser.open()) { + con.close() + service.postAbort("Unsupported serial port") + return + } + val baudrate = prefs.getStringInt("baudrate", 115200) + ser.setBaudRate(baudrate) + ser.setDataBits(UsbSerialInterface.DATA_BITS_8) + ser.setStopBits(UsbSerialInterface.STOP_BITS_1) + ser.setParity(UsbSerialInterface.PARITY_NONE) + ser.setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF) + + log("Opened " + ser.getClass().getSimpleName() + " at " + baudrate + "bd") + val os = new SerialOutputStream(ser) + val initstring = prefs.getString("usb.init", null) + val initdelay = prefs.getStringInt("usb.delay", 300) + if (initstring != null && initstring != "") { + log("Sending init: " + initstring) + for (line <- initstring.split("\n")) { + os.write(line.getBytes()) + os.write('\r') + os.write('\n') + Thread.sleep(initdelay) + } + } + proto = new KissProto(new SerialInputStream(ser), os, digipath) + service.postPosterStarted() + while (running) { + val line = proto.readPacket() + Log.d(TAG, "recv: " + line) + service.postSubmit(line) + } + Log.d(TAG, "terminate()") + } + + + } + +}