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()")
+ }
+
+
+ }
+
+}